34. Наблюдаемость сети. tcpdump, Wireshark, netstat, ss

Введение в сетевую наблюдаемость

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

В высоконагруженных Go-системах узким местом редко становится чистая вычислительная мощность процессора. Ваше приложение в утилите top может выглядеть абсолютно безмятежно — потреблять скромные 5% CPU и 200 МБ памяти, — но внешние клиенты при этом будут захлебываться от ошибок 504 Gateway Timeout. Чаще всего деградация вызвана сетевыми задержками, утечками файловых дескрипторов, неоптимальным использованием пулов соединений или блокировками на уровне ядра. Инспекция трафика и состояния сокетов позволяет отделить проблемы рантайма Go от проблем инфраструктуры и сетевого стека.

Инструменты сетевой наблюдаемости (tcpdump, Wireshark, ss) — это и есть стетоскоп, рентген и кардиограф бэкенд-инженера.


tcpdump: Сырой трафик и BPF-фильтры

tcpdump — классический инструмент для захвата пакетов. В современных Linux он использует AF_PACKET сокеты и библиотеку libpcap.

Когда сетевая карта принимает кадр Ethernet, драйвер помещает его в кольцевой буфер приема (Rx ring). Специальный хук ядра dev_add_pack перехватывает пакет и дублирует его для сокета AF_PACKET, не дожидаясь, пока стек TCP/IP разберет заголовки.

flowchart TD
    classDef process  fill:#1e293b,stroke:#38bdf8,stroke-width:1px,color:#f8fafc
    classDef success  fill:#064e3b,stroke:#34d399,stroke-width:1px,color:#f8fafc
    classDef error    fill:#7f1d1d,stroke:#f87171,stroke-width:1px,color:#f8fafc
    classDef warning  fill:#78350f,stroke:#fbbf24,stroke-width:1px,color:#f8fafc
    classDef entry    fill:#1e3a5f,stroke:#38bdf8,stroke-width:2px,color:#f8fafc
    classDef aux      fill:#334155,stroke:#64748b,stroke-width:1px,color:#f8fafc
    classDef system   fill:#312e81,stroke:#a78bfa,stroke-width:1px,color:#f8fafc
    classDef data     fill:#132d29,stroke:#2dd4bf,stroke-width:1px,color:#f8fafc
    classDef network  fill:#881337,stroke:#fb7185,stroke-width:1px,color:#f8fafc
    classDef accent   fill:#1e1b4b,stroke:#818cf8,stroke-width:1px,color:#f8fafc
    NIC["Сетевая карта (NIC / DMA)"]:::system --> RxRing["Кольцевой буфер Rx ядра Linux"]
    RxRing --> Tap{"AF_PACKET Hook"}

    Tap -->|"Классический путь"| Netfilter["Netfilter / TCP/IP Stack"]
    Netfilter --> SocketBuf["Socket Receive Buffer"]
    SocketBuf --> GoApp["Go Runtime (netpoller / epoll)"]

    Tap -->|"Дублирование пакета"| BPF["BPF Filter (In-Kernel)"]
    BPF -- "Совпало" --> PcapBuf["libpcap Ring Buffer"]
    BPF -- "Не совпало" --> Drop["Мгновенный сброс без оверхеда"]
    PcapBuf --> Tcpdump["tcpdump (Userspace)"]
  

Ключевая особенность — BPF (Berkeley Packet Filter). Фильтры компилируются в байткод, который выполняется внутри ядра до передачи пакета в userspace. Это радикально снижает нагрузку на CPU и количество контекстных переключений, так как отбрасываются нецелевые пакеты на раннем этапе.

Для Go-разработчика критично понимать, как фильтровать трафик без деградации продакшена:

Bash
# Захват только TCP-пакетов для конкретного сервиса (порт 8080), без DNS-развертки
sudo tcpdump -i eth0 -n -s 0 'tcp port 8080' -w capture.pcap

Wireshark: Глубокий анализ протоколов

Wireshark — GUI-фронтенд для libpcap. Его сила — в встроенных диссекторах (разборщиках протоколов). Для Go-бэкенда он незаменим при отладке:

  • TLS/HTTPS: Визуализация TLS Handshake, проверка сертификатов, анализ ClientHello/ServerHello.
  • HTTP/2 & gRPC: Разбор фреймов, приоритетов потоков, HEADERS, DATA, GOAWAY. Позволяет увидеть HEAD-of-Line блокировки или асимметрию в PushPromise.
  • Результаты: Экспорт в tshark для CI/CD или скриптов автоматизации.

netstat vs ss: Архитектура и производительность

Оба инструмента показывают состояние сокетов, но их архитектура кардинально различается.

flowchart TD
    classDef process  fill:#1e293b,stroke:#38bdf8,stroke-width:1px,color:#f8fafc
    classDef success  fill:#064e3b,stroke:#34d399,stroke-width:1px,color:#f8fafc
    classDef error    fill:#7f1d1d,stroke:#f87171,stroke-width:1px,color:#f8fafc
    classDef warning  fill:#78350f,stroke:#fbbf24,stroke-width:1px,color:#f8fafc
    classDef entry    fill:#1e3a5f,stroke:#38bdf8,stroke-width:2px,color:#f8fafc
    classDef aux      fill:#334155,stroke:#64748b,stroke-width:1px,color:#f8fafc
    classDef system   fill:#312e81,stroke:#a78bfa,stroke-width:1px,color:#f8fafc
    classDef data     fill:#132d29,stroke:#2dd4bf,stroke-width:1px,color:#f8fafc
    classDef network  fill:#881337,stroke:#fb7185,stroke-width:1px,color:#f8fafc
    classDef accent   fill:#1e1b4b,stroke:#818cf8,stroke-width:1px,color:#f8fafc
    subgraph NetstatPath ["Устаревший netstat"]
        NProc["Чтение файлов procfs:<br/>/proc/net/tcp, /proc/net/sockstat"]:::network
        NProc --> NParse["Форматирование и парсинг текста в userspace<br/>(Блокировка таблиц сокетов ядра!)"]
        NParse --> NOut["netstat: Высокий iowait / O(N) латентность"]
    end

    subgraph SSPath ["Современный ss"]
        SKernel["Ядро Linux (TCP/IP стек)"]
        SKernel -->|"Syscall netlink (NETLINK_INET_DIAG)"| NL["Прямой бинарный трансфер без парсинга текста"]
        NL --> SOut["ss: Мгновенный O(1) дамп миллионов сокетов"]
    end
  
Характеристика netstat ss
Источник данных /proc/net/tcp, /proc/net/sockstat (procfs) netlink сокеты (прямой запрос к ядру)
Производительность Очереди, парсинг текстовых файлов getsockopt/netlink запросы, мгновенно
Поддержка Старые TCP-состояния QUIC, BBR, send/recv очереди, PID процессов

Go-специфика: Связь инструментов с netpoller и рантаймом

Когда вы отлаживаете Go-сервис через ss или tcpdump, важно помнить о модели G-M-P.

stateDiagram-v2
    classDef process  fill:#1e293b,stroke:#38bdf8,stroke-width:1px,color:#f8fafc
    classDef success  fill:#064e3b,stroke:#34d399,stroke-width:1px,color:#f8fafc
    classDef error    fill:#7f1d1d,stroke:#f87171,stroke-width:1px,color:#f8fafc
    classDef warning  fill:#78350f,stroke:#fbbf24,stroke-width:1px,color:#f8fafc
    classDef entry    fill:#1e3a5f,stroke:#38bdf8,stroke-width:2px,color:#f8fafc
    classDef aux      fill:#334155,stroke:#64748b,stroke-width:1px,color:#f8fafc
    classDef system   fill:#312e81,stroke:#a78bfa,stroke-width:1px,color:#f8fafc
    classDef data     fill:#132d29,stroke:#2dd4bf,stroke-width:1px,color:#f8fafc
    classDef network  fill:#881337,stroke:#fb7185,stroke-width:1px,color:#f8fafc
    classDef accent   fill:#1e1b4b,stroke:#818cf8,stroke-width:1px,color:#f8fafc
    [*] --> SYN_RECEIVED: Входящий SYN
    SYN_RECEIVED --> ESTABLISHED: Тройное рукопожатие завершено

    state ESTABLISHED {
        [*] --> ActiveReadWrite: Обработка в Go goroutine
        ActiveReadWrite --> RemoteFIN: Клиент прислал FIN
    }

    RemoteFIN --> CLOSE_WAIT: Ядро ответило ACK
    note right of CLOSE_WAIT
        ЛОВУШКА В GO:
        Если в коде забыли resp.Body.Close()
        или conn.Close(), сокет навсегда
        застревает в CLOSE_WAIT!
    end note

    CLOSE_WAIT --> LAST_ACK: Go вызвал conn.Close()
    LAST_ACK --> [*]: Получен финальный ACK
  
  1. Утечки соединений (CLOSE_WAIT): Если ss -t state close-wait показывает множество соединений от вашего PID, это почти всегда означает, что в Go коде не вызван resp.Body.Close() или conn.Close(). Удаленная сторона отправила FIN, ядро перевело сокет в CLOSE_WAIT, но прикладной поток не закрыл дескриптор.
  2. TIME_WAIT и истощение портов: При высокой скорости создания/завершения соединений (например, в гравитационных тестах или при плохой настройке http.Transport) возникает 39. TIME_WAIT, Port Exhaustion и другие проблемы TCP-сервисов. ss -t state time-wait \| wc -l покажет нагрузку. Go не управляет tcp_tw_reuse напрямую, но может минимизировать проблему через пул соединений (MaxIdleConns, IdleConnTimeout).
  3. Блокировки и epoll: ss -p показывает PID процесса, владеющего сокетом. Если вы видите, что сокет принадлежит PID Go-рантайма, но трафик не идет (tcpdump показывает пустоту), проверьте, не заблокирован ли netpoller или не исчерпан ли epoll лимит (fs.epoll.max_user_watches).

Практический чек-лист отладки для Go-бэкенда

  • Сервис не отвечает: ss -t state established \| grep <pid> -> tcpdump -i any port <port> -n -> проверка HEADERS/DATA в Wireshark.
  • Высокая latency: ss -t -i (показывает ssthresh, cwnd, rtt, bbr параметры) -> анализ tcpdump на предмет retransmission.
  • Утечка памяти/файловых дескрипторов: ss -p \| grep <pid> \| wc -l vs lsof -p <pid> \| grep sock. Если ss показывает меньше сокетов, чем lsof, возможно, дескрипторы утекли в syscall или не закрыты в defer.
  • Проблемы с TLS/gRPC: Захват трафика Wireshark -> фильтр tls.handshake.type == 1 (Client Hello) -> проверка сертификатов и cipher suites.

Итог

Сетевая наблюдаемость в Go требует понимания границы между рантаймом и ядром. tcpdump и libpcap дают доступ к сырым пакетам с минимальными издержками благодаря BPF. ss вытеснил netstat благодаря netlink и производительности. Wireshark незаменим для протокольного уровня (TLS, HTTP/2, gRPC). Понимание того, как netpoller взаимодействует с epoll, и как состояния сокетов отражаются в Go-коде, позволяет быстро находить утечки, блокировки и узкие места в высоконагруженных системах.

В следующей статье мы разберем инструменты диагностики и устранения проблем: 35. Диагностика сети. ping, traceroute, dig, curl, mtr, чтобы научиться быстро находить разрывы в цепочке доставки пакетов.