Свой runbook — чек-листы для типовых сетевых проблем
В любой инженерной команде существует один документ, который ценнее тысячи строк кода — runbook. Это набор пошаговых инструкций: «когда видишь симптом X, делай Y, потом Z, если не помогло — эскалируй». Хороший runbook позволяет дежурному инженеру в три часа ночи быстро восстановить сервис, даже если он впервые столкнулся с проблемой. Плохой runbook — это либо отсутствие документа, либо «откройте Wireshark и разберитесь».
Цель этого финального урока — собрать собственный runbook для типичных сетевых проблем, который вы будете использовать всю карьеру. Не теория, не «вообще про сети» — конкретные decision trees: «получил Connection timeout — три команды, по которым понимаешь причину за 60 секунд». Этот runbook — ваш личный конспект из 14 модулей курса, превращённый в инструмент.
После этого урока вы напишете свой runbook в LAB-03 и в любой текстовый файл, который вы будете носить с собой. Никаких заклинаний — только последовательные команды с понятными исходами.
Структура хорошего runbook
Runbook — это не учебник. Это decision tree. На каждом шаге — один симптом, одна команда, две-три ветки в зависимости от результата.
Каждый шаг сужает причину
Каждая запись в runbook содержит четыре части:
- Симптом — что наблюдает пользователь / monitoring.
- Первый шаг — одна команда, которая разделяет основные причины.
- Дерево — 2-4 ветки в зависимости от исхода.
- Действие — что сделать в каждой ветке.
Категория 1 — DNS не работает
Симптом: curl: (6) Could not resolve host: api.partner.com или getaddrinfo failed в Python.
Step 1: dig +short api.partner.com
├─ Ответ есть, IP корректный
│ -> DNS ОК, проблема дальше. Переходи к категории 2 (Connection).
│
├─ Ответ пустой
│ -> Step 2: dig +trace api.partner.com
│ ├─ Доходит до authoritative, но нет ответа
│ │ -> Запись удалили или зона повреждена. Эскалация в DNS-команду.
│ │
│ └─ Не доходит до authoritative
│ -> Проблема с upstream resolver. Попробуй @8.8.8.8.
│
├─ Ошибка "SERVFAIL"
│ -> DNSSEC validation fail или resolver не отвечает.
│ -> Попробуй с другого resolver: dig api.partner.com @1.1.1.1
│
└─ Ошибка "NXDOMAIN"
-> Домена не существует. Проверь typo, проверь /etc/hosts override.
Дополнительные команды:
# Что в моём resolver.conf?
cat /etc/resolv.conf # Linux
scutil --dns | head -20 # macOS
# /etc/hosts перекрывает?
getent hosts api.partner.com # Linux
# Кэш OS возвращает старое?
sudo killall -HUP mDNSResponder # macOS
sudo resolvectl flush-caches # Linux systemd-resolved
# Альтернатива через другой resolver
dig api.partner.com @8.8.8.8 +noall +answer
dig api.partner.com @1.1.1.1 +noall +answer
# В контейнере?
docker exec -it <container> cat /etc/resolv.conf
docker exec -it <container> nslookup api.partner.com
В Kubernetes 90% «DNS не работает» — это либо coredns с старыми записями (TTL), либо ndots:5 в /etc/resolv.conf, который форсит DNS прокручивать search domains. Проверь /etc/resolv.conf внутри пода — там может быть search default.svc.cluster.local svc.cluster.local cluster.local, что заставляет curl делать 4 DNS-запроса перед попыткой внешнего домена.
Категория 2 — TCP не подключается
Симптом: curl: (7) Failed to connect to api.partner.com port 443: Connection refused или Connection timed out.
Два совершенно разных сценария, и их нужно различать:
Connection refused— получили TCP RST в ответ на SYN. Сервер отвечает, но порт закрыт (или firewall настроен на REJECT, не DROP).Connection timeout— SYN ушёл, ответа нет (или ICMP unreachable). Сервер не отвечает, или firewall DROPит пакеты.
Step 1: nc -zv api.partner.com 443
├─ "connection refused"
│ -> Сервер слушает, но не на этом порту, или backend упал.
│ -> Проверь со стороны сервера: ss -lnt | grep :443
│ -> Проверь логи backend (systemd, docker logs)
│
├─ "connection timeout" (после 20-60 сек)
│ -> Firewall между нами и сервером DROPит пакеты.
│ -> Step 2: traceroute api.partner.com
│ -> Где обрывается путь -- там firewall/маршрутизация.
│ -> mtr api.partner.com -- для длительного наблюдения за path
│
└─ "succeeded"
-> TCP-port открыт. Проблема выше -- TLS или HTTP. Переходи к категории 3.
Дополнительные команды:
# Что слушает на этом порту локально?
sudo ss -lntp | grep :443 # Linux
sudo lsof -i :443 # macOS
# Со стороны сервера: установленные соединения
ss -ant | grep :443 | wc -l # сколько соединений
ss -ant state established | head # топ-10
# Сетевая connectivity на разных уровнях
ping api.partner.com # ICMP -- но многие фаерволы блокируют
nc -zv api.partner.com 443 # TCP -- самый надёжный тест
traceroute api.partner.com # путь к хосту
mtr -rwc 10 api.partner.com # объединение ping + traceroute
# Кто отвечает за этот IP -- может быть NAT/CDN?
dig +short api.partner.com
whois 93.184.216.34 | head -20
ping (ICMP) ничего не говорит о TCP. Многие AWS/GCP security groups блокируют ICMP, но разрешают TCP:443. Никогда не делай вывод «сеть упала» только по ping. Используй nc -zv или curl -v для проверки конкретного TCP-порта.
Категория 3 — TLS handshake падает
Симптом: curl: (60) SSL certificate problem: ... или SSL_ERROR_BAD_CERT_DOMAIN в браузере.
Step 1: openssl s_client -connect api.partner.com:443 -servername api.partner.com </dev/null 2>&1 | head -20
├─ "verify return:1"
│ -> На стороне openssl всё ок. Проблема в нашем клиенте (CA bundle, certifi).
│ -> Step 2: python -c "import certifi; print(certifi.where())"
│ -> Проверь, что bundle актуален: pip install --upgrade certifi
│
├─ "certificate has expired"
│ -> Cert просрочен. Эскалация в команду, которая владеет сертификатом.
│ -> openssl x509 -in <(openssl s_client ... 2>/dev/null) -noout -dates
│
├─ "self signed certificate in certificate chain"
│ -> Корпоративный прокси/MITM или dev-сервер с self-signed.
│ -> Проверь, что не за корпоративным TLS-inspecting proxy.
│
├─ "Hostname mismatch"
│ -> SAN сертификата не содержит хоста, к которому ходим.
│ -> openssl s_client ... | openssl x509 -noout -text | grep -A1 "Subject Alternative"
│
└─ "unable to get local issuer certificate"
-> Не хватает intermediate cert в цепочке (server misconfig).
-> openssl s_client ... -showcerts -- увидишь, сколько cert'ов сервер прислал.
Дополнительные команды:
# Полный детальный handshake
openssl s_client -connect api.partner.com:443 -servername api.partner.com -showcerts -debug
# Дата истечения cert (без подключения, если есть файл)
openssl x509 -in cert.pem -noout -dates
openssl x509 -in cert.pem -noout -text | grep -A2 "Subject Alternative"
# Какой TLS-версия и cipher выбран
openssl s_client -connect api.partner.com:443 -tls1_3 </dev/null
openssl s_client -connect api.partner.com:443 -tls1_2 </dev/null
# Online: ssllabs
# https://www.ssllabs.com/ssltest/analyze.html?d=api.partner.com
# Python: какой CA bundle используется?
python3 -c "import ssl; print(ssl.get_default_verify_paths())"
python3 -c "import certifi; print(certifi.where())"
Категория 4 — HTTP-уровень: 4xx / 5xx
Симптом: TCP ОК, TLS ОК, но 400 Bad Request, 403 Forbidden, 404 Not Found, 500 Internal Server Error, 502 Bad Gateway, 503 Service Unavailable, 504 Gateway Timeout.
Эти ошибки уже про HTTP, не сеть. Но логика похожая.
Step 1: curl -v https://api.partner.com/endpoint
├─ 400 Bad Request
│ -> Malformed request: missing required header, неправильный JSON.
│ -> curl -v --data '{"key":"val"}' -H "Content-Type: application/json"
│
├─ 401 / 403
│ -> Auth-проблема. 401 -- не залогинен; 403 -- залогинен, но нет прав.
│ -> Проверь token: curl -H "Authorization: Bearer ..." -v
│
├─ 404
│ -> URL не существует. Typo, неправильный API version, маршрут не подключён.
│
├─ 429
│ -> Rate limit hit. Проверь заголовки X-RateLimit-* или Retry-After.
│
├─ 500
│ -> Сервер упал на нашем запросе. Эскалация владельцу с requestId.
│
├─ 502 Bad Gateway
│ -> Reverse proxy (nginx/CDN) получил битый ответ от upstream.
│ -> Step 2: curl -v на upstream напрямую (минуя nginx).
│
├─ 503 Service Unavailable
│ -> Сервер целенаправленно говорит «занят» (rate limit, maintenance).
│ -> Проверь Retry-After header.
│
└─ 504 Gateway Timeout
-> nginx ждал ответа от upstream и не дождался.
-> Upstream долго думает: проверь нагрузку, увеличь timeout, профилируй.
Дополнительные команды:
# Полный verbose, видим все request/response headers
curl -v https://api.partner.com/endpoint 2>&1 | tee curl.log
# Чисто headers, без body
curl -I https://api.partner.com/endpoint
# С таймингами по фазам
curl -w "@-" -o /dev/null -s https://api.partner.com/endpoint <<'EOF'
dns_resolution: %{time_namelookup}s
tcp_connect: %{time_connect}s
tls_handshake: %{time_appconnect}s
time_to_first_byte: %{time_starttransfer}s
total: %{time_total}s
EOF
# Перехват трафика, если непонятно что отправляет клиент
mitmproxy --listen-port 8080
# Затем: export HTTPS_PROXY=http://localhost:8080; curl --cacert mitm.pem ...
Категория 5 — Latency-проблемы
Симптом: «API стал медленный», «p99 вырос с 200 мс до 2 с».
Это самая коварная категория — ничего не падает, просто всё медленнее.
Step 1: curl -w "%{time_namelookup} %{time_connect} %{time_appconnect} %{time_starttransfer} %{time_total}\n" -o /dev/null -s https://api.partner.com/
├─ time_namelookup > 50 мс
│ -> DNS медленный. Может быть кэш протух, resolver далеко.
│ -> Step 2: time dig api.partner.com -- если медленно, проблема в DNS-инфре.
│
├─ time_connect - time_namelookup > 100 мс
│ -> TCP handshake долгий. Сервер далеко (физически) или перегружен.
│ -> Сравни с другого региона. Проверь mtr.
│
├─ time_appconnect - time_connect > 200 мс
│ -> TLS-handshake долгий. Большой cert chain или CPU-bound на сервере.
│ -> Step 2: openssl speed rsa2048 -- если CPU не успевает с RSA, проблема в железе.
│
├─ time_starttransfer - time_appconnect > 500 мс
│ -> TTFB (Time To First Byte) большой. Backend медленно генерирует ответ.
│ -> Это уже не сеть, это application-level. Дерево profiling.
│
└─ time_total - time_starttransfer > 500 мс
-> Длинный body, медленная сеть. Проверь throughput: iperf3.
Дополнительные команды:
# Структурированный curl-timing report
curl -w "@curl-format.txt" -o /dev/null -s https://api.partner.com/
# Где curl-format.txt:
# time_namelookup: %{time_namelookup}\n
# time_connect: %{time_connect}\n
# time_appconnect: %{time_appconnect}\n
# time_pretransfer: %{time_pretransfer}\n
# time_starttransfer: %{time_starttransfer}\n
# time_total: %{time_total}\n
# Throughput-тест между двумя серверами
iperf3 -s # на сервере
iperf3 -c api.partner.com -t 30 # на клиенте
# Packet loss detection
mtr -rwc 100 api.partner.com # 100 пингов, отчёт
# DNS-кэш: на macOS time показывает разницу холодный/тёплый
sudo killall -HUP mDNSResponder
time dig api.partner.com
time dig api.partner.com # второй раз -- из кэша
# Если есть подозрение на out-of-order/retransmissions
sudo tcpdump -i en0 -w slow.pcap host api.partner.com
# Потом: tshark -r slow.pcap -Y 'tcp.analysis.flags'
Категория 6 — «Работает локально, не работает в k8s»
Симптом: скрипт работает с моего ноута, в Kubernetes-поде падает.
Это часто встречающаяся проблема. Причин обычно три:
Step 1: kubectl exec -it <pod> -- /bin/sh
# внутри пода:
cat /etc/resolv.conf # ndots:5, search domains -- может ломать DNS
nslookup api.partner.com # резолвится ли вообще?
nc -zv api.partner.com 443 # доступен ли порт?
curl -v https://api.partner.com/
├─ DNS не резолвит
│ -> CoreDNS в кластере, проверь kubectl logs -n kube-system deploy/coredns
│ -> NetworkPolicy блокирует трафик к kube-dns?
│
├─ TCP timeout
│ -> NetworkPolicy в namespace блокирует egress
│ -> kubectl get netpol -n <namespace>
│ -> Egress firewall на cloud провайдере (AWS SG, GCP firewall)
│
├─ TLS handshake fails
│ -> Внутри образа нет ca-certificates пакета
│ -> docker run -it <image> -- ls /etc/ssl/certs/ | wc -l
│ -> В Alpine надо явно: apk add ca-certificates
│
└─ HTTP 403/401
-> Сервис требует source IP whitelisting, под идёт через NAT
-> Узнай egress IP кластера и добавь в allowlist
Категория 7 — Хосты доступны частично
Симптом: «curl работает на одной машине, на соседней нет, конфиги идентичны».
Step 1: diff /etc/resolv.conf на обеих
Step 2: diff /etc/hosts на обеих
Step 3: ip route show на обеих -- таблицы маршрутизации
Step 4: dig api.partner.com на обеих -- что получают
Step 5: curl -v на обеих -- что видят
Step 6: traceroute api.partner.com -- где расходятся пути
Чаще всего разница в одной из четырёх:
- DNS-резолвер (разные
/etc/resolv.conf). - Сетевые маршруты (
ip route show— другая default gateway). - /etc/hosts override.
- Firewall (iptables/nftables/security group).
Универсальные helpers
В любом runbook нужны несколько быстрых команд для общей картины:
# IP-конфиг
ip addr show # Linux
ifconfig # macOS, Linux (deprecated)
# Маршрутизация
ip route show # Linux
netstat -nr # macOS, Linux
# Активные соединения
ss -ant # Linux
netstat -an # macOS, Linux
# DNS-резолвер
cat /etc/resolv.conf # Linux
scutil --dns # macOS
# Что слушает порты
sudo ss -lntp # Linux
sudo lsof -i -P -n # macOS
# Захват трафика
sudo tcpdump -i any -w trace.pcap
# Тест connectivity
nc -zv host port # TCP
curl -I https://... # HTTP
ping host # ICMP
mtr host # длительный monitoring path
Эти десять команд покрывают 80% диагностики.
Попробуй сам
Создай свой RUNBOOK.md в ~/notes/ или в личном git-репозитории. Структура:
# My Networking Runbook
## DNS
### Симптом: "Could not resolve host"
- [ ] dig +short <host>
- [ ] /etc/resolv.conf
- [ ] DNS cache flush
- [ ] dig @8.8.8.8 <host>
## TCP
### Симптом: "Connection timeout"
- [ ] nc -zv <host> <port>
- [ ] traceroute <host>
- [ ] ICMP блокируется? --> mtr <host>
## TLS
### Симптом: "SSL certificate problem"
- [ ] openssl s_client -connect <host>:443 -servername <host>
- [ ] openssl x509 -dates (проверить срок)
- [ ] curl -v --cacert ...
## HTTP
### Симптом: 502 Bad Gateway
- [ ] curl -v напрямую на upstream
- [ ] nginx error.log
- [ ] backend logs
## Latency
- [ ] curl -w "timing format"
- [ ] mtr -rwc 100
- [ ] iperf3
## Дополнения после первого инцидента
(Сюда добавляешь свои находки по мере их появления.)
Этот документ — ваш самый ценный артефакт после курса. Он будет жить с вами годами, расти, специализироваться под вашу инфраструктуру. Каждый инцидент, который вы разберёте сам — новая строчка в runbook. Через год у вас будет личная экспертная база, которая покрывает 95% сетевых проблем вашей команды.
Что отличает хороший runbook
Если runbook не отвечает на «что делать сейчас» -- он не работает
Хорошие признаки:
- Конкретность —
ss -lntp | grep :443, а не «проверьте порты». - Ожидаемый output — «увидишь LISTEN 0.0.0.0:443, если процесс есть».
- Branching — «если результат А, то Б; если В, то Г».
- Версионирование — помечай дату изменений, чтобы потом понять, актуально ли.
- Owner для эскалации — «если не помогло, пиши @platform-team».
Плохие признаки:
- «Сделайте магию» / «проверьте всё».
- Только описание без команд.
- Не обновлялся 3 года, ссылается на удалённые сервисы.
Финал курса
Это последний урок. Вы прошли путь от первого ping 8.8.8.8 до собственного HTTPS-сервера, от ARP до consistent hashing, от TLS 1.2 до QUIC. У вас в руках набор знаний и инструментов, который покрывает 95% сетевых задач Junior DE.
Что дальше:
- Пройти
LAB-01(захват трафика),LAB-02(DNS resolver),LAB-03(capstone стек) — три hands-on упражнения, которые закрепят знания. - Записать свой первый runbook и дополнять его при каждом инциденте.
- Углубить знания:
Computer Networking: A Top-Down Approach(Kurose, Ross),TCP/IP Illustrated(Stevens). - Перейти к следующему курсу трека —
rest-api-fundamentalsдля глубокого погружения в HTTP API.
Итог курса
В этом capstone мы:
- Урок 1 — спроектировали мини-инфраструктуру с nginx, TLS, двумя backend.
- Урок 2 — захватили pcap своего трафика и разобрали по фазам.
- Урок 3 — собрали HTTPS-сервер на Python с self-signed cert.
- Урок 4 — собрали runbook decision trees для DNS / TCP / TLS / HTTP / latency / k8s.
Теперь у вас есть и теория, и практика. Теория — из 14 модулей курса. Практика — из labs, которые вы запустите следующим шагом. И главное — runbook, который растёт вместе с вашим опытом.
Networking — это навык, который не теряется. Через 10 лет HTTP/3 будет нормой, IPv6 будет везде, TLS 1.3 будет старым. Но базовые принципы — 3-way handshake, DNS hierarchy, TLS handshake — останутся. Вы их теперь знаете.
Welcome to networking.