Материал открыт для чтения

Войдите, чтобы сохранять прогресс и результаты.

Войти
Linux: Продвинутый

Сеть в Linux: маршрут от curl до процесса

чтение ~19 минУрок 3 из 22

Сеть в Linux: маршрут от curl до процесса

module linux advanced Image01

В предыдущей статье мы разобрали базовые понятия: IP, порт, DNS, маршрут, localhost и gateway. Теперь смотрим, как это видно в Linux-командах.

Большая часть работы DevOps-инженера — это взаимодействие сервисов по сети. Нужно уметь проверять, какие порты открыты, куда идёт трафик, как работает HTTP-запрос и почему сервис недоступен. Главное — не просто запускать команды, а понимать, что именно они показали.


Сначала картинка: пакет до сервера через два маршрутизатора

Представь, что твой компьютер открывает сайт на сервере 203.0.113.20. Между ними два маршрутизатора.

У маршрутизаторов тоже есть IP-адреса. Обычно не один, а по одному на каждый сетевой интерфейс:

компьютер: 192.168.1.10
роутер 1:  192.168.1.1  со стороны компьютера
           10.0.0.1     со стороны роутера 2
роутер 2:  10.0.0.2     со стороны роутера 1
           203.0.113.1  со стороны сервера
сервер:    203.0.113.20

Главная мысль: IP-адрес назначения в запросе остаётся адресом сервера 203.0.113.20. IP-адреса маршрутизаторов используются как адреса "следующего шага", но они не становятся конечным адресом запроса.

Твой компьютер не знает MAC-адрес далёкого сервера в интернете. Ему он и не нужен. Компьютеру достаточно знать MAC ближайшего роутера: именно ему надо отдать данные на первом шаге.

У маршрутизатора обычно несколько сетевых интерфейсов: один смотрит в сторону твоего компьютера, другой — в сторону следующего маршрутизатора. У каждого интерфейса свой MAC-адрес. Поэтому в таблице один и тот же роутер 1 может встречаться с разными MAC: это разные стороны одного устройства.

Ниже MAC-адреса выдуманные, только чтобы показать принцип:

Участок IP назначения в запросе IP следующего шага Кто отправляет по MAC Кто получает по MAC на этом шаге
компьютер -> роутер 1 203.0.113.20 192.168.1.1 компьютер 02:00:00:00:00:10 интерфейс роутера 1 со стороны компьютера 02:00:00:00:00:01
роутер 1 -> роутер 2 203.0.113.20 10.0.0.2 интерфейс роутера 1 со стороны роутера 2 02:00:00:00:01:01 интерфейс роутера 2 со стороны роутера 1 02:00:00:00:01:02
роутер 2 -> сервер 203.0.113.20 203.0.113.20 интерфейс роутера 2 со стороны сервера 02:00:00:00:02:01 сервер 02:00:00:00:02:20

IP следующего шага нужен устройству, чтобы понять, кому передать данные сейчас. Например, твой компьютер видит, что сервер 203.0.113.20 не в локальной сети, поэтому выбирает следующий шаг 192.168.1.1 — ближайший роутер. Но внутри самого запроса конечный IP всё равно 203.0.113.20.

Что происходит на каждом маршрутизаторе:

  1. Маршрутизатор получает данные, которые на этом участке отправили на его MAC-адрес.
  2. Смотрит внутрь и видит IP назначения: 203.0.113.20.
  3. По таблице маршрутов выбирает следующий шаг.
  4. Отправляет данные дальше, но уже на MAC-адрес следующего соседнего устройства.

Поэтому фраза "трафик идёт через шлюз" означает: твоя машина отдаёт пакет ближайшему маршрутизатору, потому что конечный IP не находится в локальной сети. Шлюз не становится конечным сервером. Он просто следующий шаг.

В реальных сетях может быть NAT: домашний роутер часто меняет IP источника, чтобы много внутренних устройств выходили в интернет через один внешний адрес. Но IP назначения в этом примере всё равно остаётся адресом сервера, к которому ты подключаешься.


Пример: сервис на 8080 не отвечает

Допустим, ты проверяешь приложение:

curl http://localhost:8080/health

Оно может не отвечать по разным причинам:

  • у машины нет нужного IP-адреса;
  • нет маршрута до удалённого адреса;
  • DNS-имя резолвится не туда;
  • процесс вообще не слушает порт 8080;
  • процесс слушает только 127.0.0.1, а ты подключаешься снаружи;
  • доступ к порту запрещён сетевыми правилами;
  • HTTP отвечает, но приложение возвращает 500.

Поэтому диагностика идёт слоями: интерфейсы, маршруты, DNS, порты, потом HTTP.


Разбор ip a: какие интерфейсы и адреса есть

Команда:

ip a

или полная форма:

ip addr

показывает сетевые интерфейсы и IP-адреса, назначенные на них.

Пример вывода:

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever

2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    inet 10.0.2.15/24 brd 10.0.2.255 scope global eth0
       valid_lft forever preferred_lft forever

Что здесь важно:

Фрагмент Что означает
1: lo loopback-интерфейс, локальная сеть внутри самой машины
inet 127.0.0.1/8 localhost-адрес; доступен только с этой машины
2: eth0 обычный сетевой интерфейс VM/контейнера/сервера
<UP,LOWER_UP> интерфейс включён и есть сетевой линк
mtu 1500 максимальный размер пакета на этом интерфейсе
inet 10.0.2.15/24 IPv4-адрес интерфейса и маска сети
brd 10.0.2.255 broadcast-адрес для этой сети
scope global адрес пригоден для обычной сети, не только для localhost

Вывод из примера говорит: у машины есть localhost 127.0.0.1 и сетевой адрес 10.0.2.15 на интерфейсе eth0. Если ты пытаешься подключиться к этой VM с другой машины, адрес 127.0.0.1 не поможет: он указывает на "саму себя" для каждой машины отдельно.

В старых статьях и инструкциях ещё встречается ifconfig. В современных Linux-системах вместо него обычно используют ip addr из набора iproute2: он установлен по умолчанию в новых дистрибутивах, лучше покрывает современные сетевые возможности и работает в одном стиле с ip route, ip link и другими командами ip.

Полезные проверки:

ip a show eth0
ip -brief addr
ip link

ip -brief addr даёт короткую картину:

lo               UNKNOWN        127.0.0.1/8 ::1/128
eth0             UP             10.0.2.15/24

Для диагностики это часто удобнее длинного вывода.


Разбор ip route: куда Linux отправит пакет

Команда:

ip route

показывает таблицу маршрутизации.

Пример:

default via 10.0.2.1 dev eth0
10.0.2.0/24 dev eth0 proto kernel scope link src 10.0.2.15
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1

Разбор:

Строка Смысл
default via 10.0.2.1 dev eth0 всё, для чего нет более точного маршрута, отправлять через gateway 10.0.2.1 на интерфейсе eth0
10.0.2.0/24 dev eth0 ... src 10.0.2.15 адреса сети 10.0.2.x доступны напрямую через eth0; исходный адрес будет 10.0.2.15
172.17.0.0/16 dev docker0 ... Docker-сеть доступна через интерфейс docker0

Шлюз, или gateway, — это ближайший маршрутизатор, которому твой Linux может отдать пакет дальше. В домашней сети это обычно роутер. В виртуальной машине это может быть виртуальный роутер гипервизора. В облаке — маршрутизатор внутри VPC.

Фраза "пакет идет через шлюз" не означает, что шлюз является конечным сервером. Конечный адрес все еще 8.8.8.8, example.com или другой удаленный IP. Просто твоя машина не знает, как доставить пакет туда напрямую, поэтому отдает его следующему узлу. Шлюз принимает пакет, смотрит свои маршруты и отправляет его дальше. Так пакет может пройти через несколько маршрутизаторов, пока не доберется до нужной сети.

Если ты делаешь:

curl http://8.8.8.8/

Linux смотрит: есть ли точный маршрут к 8.8.8.8? Нет. Значит используется default via 10.0.2.1 dev eth0.

Очень полезная команда:

ip route get 8.8.8.8

Пример вывода:

8.8.8.8 via 10.0.2.1 dev eth0 src 10.0.2.15 uid 0
    cache

Она прямо отвечает: к 8.8.8.8 пакет пойдёт через шлюз 10.0.2.1, через интерфейс eth0, с исходным адресом 10.0.2.15.

Если в выводе ip route нет default, внешние адреса часто будут недоступны, даже если локальный интерфейс поднят. Локальная сеть при этом может работать: например, 10.0.2.15 может видеть соседей из 10.0.2.0/24, но не понимать, куда отправлять все остальное.


Разбор ss -tlnp: какой процесс слушает порт

Команда:

ss -tlnp

показывает слушающие TCP-сокеты.

Пример:

State   Recv-Q Send-Q Local Address:Port  Peer Address:Port Process
LISTEN  0      128    127.0.0.1:8080      0.0.0.0:*         users:(("python3",pid=1200,fd=3))
LISTEN  0      4096   0.0.0.0:22          0.0.0.0:*         users:(("sshd",pid=733,fd=3))
LISTEN  0      511    0.0.0.0:80          0.0.0.0:*         users:(("nginx",pid=1500,fd=6))

Разбор:

Поле Что означает
LISTEN процесс ждёт входящие подключения
127.0.0.1:8080 порт 8080 слушается только локально
0.0.0.0:22 порт 22 слушается на всех IPv4-интерфейсах
users:(("python3",pid=1200)) порт держит процесс python3 с PID 1200
Recv-Q / Send-Q очереди приёма и отправки; обычно в базовой диагностике не главное

Ключевой вывод из примера: если приложение слушает 127.0.0.1:8080, локальная команда curl http://localhost:8080 может работать, но снаружи этот порт будет недоступен. Для внешнего доступа сервис должен слушать адрес интерфейса или 0.0.0.0, а сетевые правила должны разрешать подключение.

Если нужно быстро проверить не HTTP, а сам TCP-порт на удалённой машине, удобно использовать nc:

nc -zv 192.168.1.50 5432

-z означает "только проверить соединение, ничего не отправлять", -v включает подробный вывод. Если nc недоступен, иногда используют telnet host port, но для диагностики портов nc -zv обычно понятнее и чище.


Разбор curl -v: что произошло на HTTP-уровне

Обычный curl показывает тело ответа:

curl http://127.0.0.1:8080/health

Если это HTML-страница, браузер превратит этот ответ в пользовательский интерфейс: разметку, стили, визуальные блоки. curl не рисует страницу, а печатает то, что пришло по HTTP. Для инженера это часто полезнее на первом шаге: не нужно открывать браузер, можно сразу увидеть код ответа, заголовки, тело и ошибки соединения.

Пример ответа: ok.

Но для диагностики часто полезнее verbose-режим:

curl -v http://127.0.0.1:8080/health

Пример вывода:

*   Trying 127.0.0.1:8080...
* Connected to 127.0.0.1 (127.0.0.1) port 8080
> GET /health HTTP/1.1
> Host: 127.0.0.1:8080
> User-Agent: curl/8.5.0
>
< HTTP/1.1 200 OK
< Content-Type: text/plain
< Content-Length: 2
<
ok

Как это читать:

Строка Что она говорит
Trying 127.0.0.1:8080 curl пытается открыть TCP-соединение к адресу и порту
Connected ... port 8080 TCP-соединение установлено; сеть до процесса дошла
> GET /health HTTP/1.1 curl отправил HTTP-запрос
> Host: 127.0.0.1:8080 curl передал имя хоста из URL; сервер может использовать его для virtual host маршрутизации
< HTTP/1.1 200 OK приложение ответило HTTP-кодом 200
ok тело ответа

Что curl отправляет кроме URL

Когда ты пишешь:

curl http://127.0.0.1:8080/health

curl не просто "стучится на порт". Он собирает HTTP-запрос:

GET /health HTTP/1.1
Host: 127.0.0.1:8080
User-Agent: curl/8.5.0
Accept: */*

Браузер делает то же самое, только добавляет больше заголовков: Accept-Language, Accept-Encoding, Cookie, иногда Authorization и другие. Пользователь их обычно не видит, потому что браузер формирует запрос автоматически из адресной строки, настроек, cookies и состояния страницы.

Host нужен HTTP-серверу, чтобы понять, какой сайт или virtual host запросил клиент. Это важно, когда один IP и порт обслуживают несколько имён. Например, app.internal и admin.internal могут указывать на один адрес, но nginx или приложение выберет разный backend по заголовку Host.

Обычно Host берётся из URL:

curl http://app.internal/

отправит Host: app.internal. А в диагностике можно подключиться к конкретному IP, но вручную сказать серверу, какое имя мы хотим проверить:

curl -H "Host: app.internal" http://127.0.0.1/

Такой запрос означает: TCP-соединение открыть к 127.0.0.1:80, но на HTTP-уровне передать Host: app.internal. Поэтому в лабораторной про Host header ты проверяешь не DNS сам по себе, а поведение приложения при разных HTTP-заголовках.

Основные параметры curl для диагностики

Что нужно Команда Что проверяешь
Увидеть тело ответа curl URL что реально вернул сервер
Увидеть подробности запроса curl -v URL соединение, отправленные и полученные заголовки
Только заголовки ответа curl -I URL статус, Content-Type, cache, service headers
Добавить заголовок curl -H "Header: value" URL Host, Authorization, feature flags, tracing
Отправить POST curl -X POST URL метод запроса
Отправить тело curl -d '{"x":1}' URL API с payload; часто автоматически включает POST
Сохранить тело в файл curl -o response.txt URL артефакт для сравнения
Не показывать progress curl -s URL чистый вывод для скриптов
Упасть на 4xx/5xx curl -f URL удобнее для healthcheck и CI
Перейти по redirect curl -L URL проверить конечный ответ после 301/302
Вывести метрики curl -w "%{http_code} %{time_total}\n" URL код, latency, размеры и timings

Частый production-паттерн:

curl -fsS -o /dev/null -w "code=%{http_code} time=%{time_total}\n" http://127.0.0.1:8080/health

Здесь -f делает 4xx/5xx ошибкой команды, -sS убирает progress, но оставляет текст ошибки, -o /dev/null отбрасывает тело, а -w печатает короткий результат для человека или скрипта.

HTTP-код сам по себе уже даёт важную границу проблемы:

Код Что обычно означает для диагностики
200 HTTP-запрос дошёл, приложение отдало успешный ответ
404 сервер жив и обработал запрос, но такого пути нет
421 запрос пришёл не тому virtual host / неверный Host
503 сервис или зависимость временно недоступны, но HTTP-уровень отвечает

Это не то же самое, что Connection refused: при HTTP-коде TCP-соединение уже состоялось, а проблема находится выше - в маршрутизации HTTP, пути URL, Host header, приложении или его backend-зависимости.

Где рядом появляется wget -qO-

В Kubernetes и минимальных debug-образах часто нет curl, зато есть wget. Поэтому в лабораторных часто используется такая форма:

wget -qO- http://web-svc

Это HTTP-запрос к URL с выводом тела ответа в терминал. По смыслу для простой проверки это похоже на:

curl http://web-svc

Разбор:

Часть Значение
-q quiet: убрать progress и лишний шум
-O output file: куда записать скачанное тело
- вместо файла использовать stdout

То есть wget -qO- http://web-svc означает: "скачай ответ с http://web-svc и напечатай его прямо в терминал". Если в ответ пришёл HTML nginx или текст healthcheck, значит HTTP-уровень ответил. В Kubernetes это дополнительно подтверждает, что имя Service разрешилось, Service выбрал endpoint, и backend Pod вернул ответ.

Ограничение: wget -qO- хорошо показывает тело ответа, но хуже подходит для детального разбора статуса, заголовков и timings. Для этого удобнее curl -v, curl -I и curl -w.

Другие типичные варианты:

curl: (7) Failed to connect to 127.0.0.1 port 8080

Обычно значит: никто не слушает порт, доступ к порту запрещён или сервис слушает другой адрес.

curl: (6) Could not resolve host: app.local

Это DNS/hosts-проблема: имя не превратилось в IP.

< HTTP/1.1 500 Internal Server Error

Сеть и порт работают, но ошибка уже на уровне приложения.

Для короткой проверки HTTP-кода:

curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:8080/health

Пример ответа: 200.

Здесь -s убирает progress, -o /dev/null не печатает тело, -w выводит только нужную метрику.


Разбор ping: отвечает ли адрес на ICMP

ping — это быстрая проверка, отвечает ли узел на ICMP Echo-запросы.

ping -c 3 8.8.8.8
ping -c 3 google.com

Если ping 8.8.8.8 работает, а ping google.com нет, проблема похожа на DNS: IP доступен, но имя не превращается в IP.

Если ping не проходит, это не всегда значит, что сайт или API недоступны. ICMP может быть запрещён сетевыми правилами, а HTTP по TCP на 80 или 443 при этом будет работать. Поэтому ping — полезный сигнал, но не финальный ответ.


Разбор traceroute: через какие узлы идёт путь

Команда:

traceroute 8.8.8.8

показывает промежуточные маршрутизаторы до адреса.

Пример:

traceroute to 8.8.8.8 (8.8.8.8), 30 hops max
 1  10.0.2.1        1.123 ms  0.944 ms  0.901 ms
 2  192.168.0.1     2.314 ms  2.101 ms  2.050 ms
 3  * * *
 4  203.0.113.5    15.200 ms 14.870 ms 15.001 ms
 5  8.8.8.8        21.400 ms 21.100 ms 20.980 ms

Как читать:

Часть Значение
1, 2, 3 номер hop, то есть очередного маршрутизатора
10.0.2.1 первый шлюз, часто gateway VM или локальный роутер
ms задержка до этого hop
* * * узел не ответил на traceroute-пакеты; это не всегда авария
последняя строка 8.8.8.8 достигли целевого адреса

traceroute полезен, когда нужно понять, где примерно пропадает путь: сразу на первом шлюзе, внутри корпоративной сети, у провайдера или ближе к удалённому сервису. Но не переоценивай его: некоторые маршрутизаторы специально не отвечают на traceroute, хотя обычный трафик через них проходит.


/etc/hosts — локальное разрешение имён

Обычно имя превращается в IP через DNS. Но перед DNS Linux может посмотреть локальный файл /etc/hosts.

/etc/hosts — это простая таблица на самой машине: слева IP, справа одно или несколько имён для этого IP.

cat /etc/hosts
127.0.0.1   localhost
192.168.1.10  myapp.local

После такой записи команда:

curl http://myapp.local

пойдёт на 192.168.1.10.

Зачем это нужно:

  • локально назвать тестовый сервер понятным именем;
  • проверить новый сервер до изменения настоящего DNS;
  • временно переопределить имя во время диагностики.

Важно: /etc/hosts работает только на той машине, где он изменён. Если ты добавил myapp.local в своей VM, другая VM или ноутбук об этом не узнают.

Ещё важно: локальная запись может переопределить DNS и направить запрос не туда, куда ты ожидаешь. Поэтому при странной DNS-проблеме полезно проверить:

cat /etc/hosts
getent hosts myapp.local

Шпаргалка

Задача Команда
Список IP-адресов ip a
Маршрут по умолчанию (шлюз) ip route show default
Открытые TCP-порты ss -tlnp
HTTP-запрос curl -s https://example.com
Только код ответа curl -s -o /dev/null -w "%{http_code}" URL
Только заголовки curl -I URL
Проверить доступность ping -c 3 host
DNS запрос dig domain.com
TCP-порт без HTTP nc -zv host port
Скачать файл curl -L -o file URL или wget URL
Вывести HTTP-тело через wget wget -qO- URL
Локальные имена /etc/hosts

Порядок диагностики: от интерфейса до HTTP

Когда сервис недоступен, полезно идти слоями.

ip addr
ip route
ss -tlnp
curl -I http://127.0.0.1:8080/
curl -s -o /dev/null -w "%{http_code} %{time_total}\n" http://127.0.0.1:8080/

ip addr отвечает, есть ли адреса на интерфейсах. ip route показывает, куда система отправляет трафик. ss проверяет, есть ли процесс, который слушает порт. curl проверяет уже прикладной уровень HTTP.

Не смешивай результаты. Если ping проходит, это не значит, что HTTP работает. Если HTTP локально возвращает 200, но снаружи недоступен, проблема может быть в bind-адресе, маршруте, сетевых правилах, security group или прокси.

DNS: сравнение резолверов

dig позволяет спросить конкретный DNS-сервер:

dig example.com A
dig @8.8.8.8 example.com A
dig +short example.com

Если корпоративный DNS отдаёт один адрес, а публичный DNS другой, это важный факт. Также помни про /etc/hosts: локальная запись может переопределить DNS и направить запрос не туда, куда ты ожидаешь.

Сохранение сетевой диагностики

В инцидентах полезно сохранять короткие снимки:

ip addr > /root/ip_addr.txt
ip route > /root/routes.txt
ss -tlnp > /root/listen_ports.txt
curl -s -o /dev/null -w "%{http_code} %{time_total}\n" http://localhost:8080/ > /root/http_check.txt

Так можно сравнить состояние до и после изменения, а не спорить по памяти.

Материал изучен?

Войдите, чтобы сохранить прогресс и перейти к практической лаборатории.

Войти и продолжить