doc: план v3 от Соннета — разделение явлений A/B, port-forward первым; принят за основу

This commit is contained in:
“Naeel”
2026-08-15 14:56:13 +04:00
parent 71e774da77
commit db90a6392e
2 changed files with 165 additions and 0 deletions
+15
View File
@@ -989,3 +989,18 @@ cilium-operator, echo переоценён, нет PMTU/port-forward/второ
Pro — методологически полнее, грубых ошибок нет.
Рекомендованный синтез-порядок: nginx-логи → PMTU-зонд → сырые TCP →
port-forward → tcpdump клиента → второй ISP → cilium monitor → echo → тикет.
## 2026-08-15 — план v3 от СОННЕТА + сравнение трёх планов
Сохранён: `doc/thinking/nubes-network-bug-plan-v3-sonnet.md`.
Оценка (основной агент): план Соннета — лучший из трёх. Ключевые достоинства:
явное разделение явлений A (MTU/PMTUD, 51с = экспонента RTO 1-2-4-8-16) и
B (регулярность 1/37с = таймер gateway/kube-vip/conntrack), port-forward первым
kubectl-тестом, точный список размеров ping-зонда, корреляция по TCP seq.
Поправки: fallback если ICMP закрыт (mtr --tcp/TCP-порог), добавить второй
ISP и контрольный канал YMQ, port-forward идёт через API-сервер (исключает
приложение, но не доказывает шлюз), точный интервал таймаутов мерить новым
тестом с timestamp на каждый таймаут.
Сравнение: Flash — ошибки и недооценка; Pro — силён, но без явного разделения
явлений; Соннет — принять за основу с поправками.
@@ -0,0 +1,150 @@
# Plan: Доказательство сетевого бага Nubes — v3 (СОННЕТ)
> ✅ **ПОМЕТКА: это план от Соннета** (пользователь передал ответ Соннета).
> Сохранено 2026-08-15. Сравнение с v1 (DeepSeek Flash) и v2 (DeepSeek Pro) —
> в конце файла и в HISTORY.
## TL;DR
Два РАЗНЫХ явления с разными причинами — нельзя объяснять одной гипотезой и
нельзя ловить одним методом. Цепочка: nginx-логи → port-forward (бесплатное
доказательство невиновности приложения) → PMTUD-зонд → tcpdump → cilium
monitor → echo-сервис (только если предыдущих данных не хватает).
## Разделение двух явлений (критически важно)
**Явление A — большие POST (~51с).** Причина: MSS=1448, underlay MTU=1450,
сегменты 1488 → дроп. PMTUD сломан (ICMP Type 3 Code 4 заблокирован шлюзом) →
TCP ретрасмитит по экспоненте: 1с,2с,4с,8с,16с ≈ 31с, ещё попытка → ~51с.
Задача: подтвердить ping-зондом и зафиксировать в тикете.
**Явление B — малые POST 512 байт (~1/37с).** MTU-моделью НЕ объясняется.
Ключевая улика: РЕГУЛЯРНОСТЬ 1/37с — это таймер, не случайные потери.
Кандидаты: сессионный таймер gateway на VIP 185.247.187.151 (вероятнее всего),
kube-vip ARP keepalive цикл (флап при смене лидера), conntrack GC с агрессивным
idle-таймером.
## 1. Echo vs сырые TCP
Сырые TCP на 32391 — недостаточны (проверяют только фазу установления; проблемы
в фазе ДАННЫХ; nginx ответит RST/400 на мусор — неинформативно).
Правильный порядок:
1. kubectl port-forward (бесплатно, 5 минут) — чище и быстрее echo.
2. Echo NodePort без ingress — если port-forward и логи не дали ответа.
Echo: принимает POST, возвращает тело; `?bytes=N` для размера ответа
(обратный путь отдельно от прямого). Деплой БЕЗ ingress, прямой NodePort.
## 2. Как поймать таймаут: tcpdump
Клиент (запустить ДО теста, параллельно):
`tcpdump -i <iface> -s 0 -w /tmp/sqs.pcap 'host 185.247.187.151 and port 32391'`
Явление A (51с, большой POST): ICMP Type 3 Code 4 от 185.x → PMTUD работает;
нет ICMP, ретрасмиты 1/2/4/8/16с → PMTUD сломан (основная гипотеза); кто шлёт
RST в конце и через сколько — чей таймаут (клиент vs nginx/шлюз).
Явление B (30с, 512 байт): ACK на запрос получен → запрос дошёл, ответ потерян
на обратном пути; ACK нет → потеря до nginx (kube-vip/NodePort/gateway);
точный интервал между таймаутами (~37с ± 1с → таймер подтверждён); RST от
сервера — чей (nginx idle keepalive или gateway).
Ключевой приём: корреляция по TCP seq/ack, не по времени.
## 3. kube-vip / NodePort / nginx — только kubectl
nginx access log: `kubectl logs -n ingress <nginx-pod> --since=35m | grep -E " (499|5[0-9][0-9]) "`.
Нужны поля `$request_time $upstream_response_time`. Если нет — проверить
ConfigMap nginx.
| Запись в nginx log | Вывод |
|---|---|
| 499 + большой request_time | клиент ушёл до ответа (наш ReadTimeout) |
| 200 + upstream <100мс, клиент получил таймаут | ответ потерян между nginx и клиентом |
| записи нет | потеря ДО nginx: kube-vip/NodePort/gateway |
kube-vip: `kubectl get pods -A | grep -i vip`, логи (leader/ARP/flap/error).
ARP-флап во время теста → кратковременная потеря VIP → дропы.
Cilium (на ПОДЕ, не на operator): `kubectl get pods -n kube-system -l k8s-app=cilium -o wide`;
`kubectl exec -n kube-system <cilium-pod> -- cilium monitor --type drop`
параллельно с тестом.
NodePort/SNAT: `externalTrafficPolicy` — Cluster → любая нода + SNAT (лишний хоп,
conntrack); Local → только нода-держатель VIP (миграция VIP → brief outage).
port-forward: `kubectl port-forward -n <ns> svc/<sqs-svc> 14100:4100` — тот же
скрипт на localhost:14100. 0 таймаутов → приложение невиновно.
## 4. Пошаговый план: дешевле → дороже
**Фаза 0 — бесплатно, уже есть данные:**
1. nginx access log за период прошлого теста (499, request_time/upstream_response_time).
2. kube-vip логи: флапы в тот же промежуток.
3. Тикет Nubes: два явления отдельно — (A) MTU/PMTUD порог 1448, (B) таймер ~37с.
**Фаза 1 — клиентские тесты, 30 минут:**
4. PMTUD-зонд: `ping -M do -s N 185.247.187.151` для N=1300,1400,1420,1422,
1448,1450,1460,1472 — точный порог. Нет ответа ≥1448 → ICMP заблокирован →
тикет с числами.
5. tcpdump + тот же 30-мин тест. Wireshark: `tcp.analysis.retransmission || icmp`.
Точный интервал между таймаутами.
**Фаза 2 — kubectl, без деплоя:**
6. port-forward тест ПЕРВЫМ: 15-мин прогон через туннель. 0 таймаутов →
приложение доказанно невиновно.
7. cilium monitor параллельно со свежим тестом против внешнего IP.
8. Если нет upstream_response_time в формате — обновить ConfigMap, повторить.
**Фаза 3 — echo (только если 0–2 не дали ответа):**
9. Minimal Go echo (NodePort, без ingress), тот же тест. Воспроизвелось →
nginx и приложение исключены → kube-vip/NodePort/gateway. Нет → echo через
ingress → изоляция nginx.
10. Матрица размеров 512/1000/1400/1448/1460/1500 × 10 мин, порог бинарным поиском.
**Фаза 4 — финальная корреляция для тикета:**
11. Одновременно tcpdump + nginx access log + cilium monitor. Корреляция по
seq: «запрос ушёл, ACK получен, ответа нет» = потеря между nginx и клиентом.
Артефакты: pcap + логи + ping-зонд → тикет.
**Решения о приоритетах:**
- port-forward — самый дешёвый и чистый способ изолировать приложение. ДО echo.
- Сырые TCP на 32391 — пропустить.
- Echo — только если port-forward + nginx-логи не дали ответа.
- Явления A и B документировать в тикете РАЗДЕЛЬНО.
---
## Поправки основного агента (GitHub Copilot)
1. **ICMP может быть закрыт на шлюзе** — тогда ping-зонд не даст порога.
Нужен fallback: mtr --tcp / tcptraceroute или TCP-сегменты переменного
размера (то, что Соннет отбросил как «сырые TCP» — но для определения
ПОРОГА размера они валидны).
2. **Второй клиент с другого ISP** (был у Pro) — отсутствует. Дешёво исключает
локальную сеть пользователя. Добавить в фазу 1.
3. **Параллельный контрольный канал YMQ** — стоит сохранить во всех тестах:
отделяет «плохой интернет вообще» от «плох путь до Nubes».
4. **port-forward идёт через API-сервер** — не тот сетевой путь; чистый
результат исключает приложение, но НЕ доказывает шлюз. Формулировка Соннета
«доказательство невиновности приложения» — корректна, использовать её.
5. **Точный интервал таймаутов** надо измерять новым тестом с timestamp на
каждый таймаут (в прошлом тесте тайминги не логировались поштучно).
6. Смена nginx log format (ConfigMap) на managed может требовать релода/помощи
платформы — держать как запасной шаг.
## Сравнение трёх планов
| | Flash (v1) | Pro (v2) | Соннет (v3) |
|---|---|---|---|
| Разделение явлений A/B | нет | частично | **явно, с причиной B=таймер** |
| Ошибки команд | cilium через operator | нет | нет |
| PMTU-зонд | нет | есть | есть, точный список размеров |
| port-forward | нет | есть | **первым kubectl-тестом** |
| Сырые TCP | недооценены | да | отброшены (спорно для порога) |
| Второй ISP | нет | есть | нет |
| Критерии доказательства | слабые | таблица | таблицы + фазы |
| Объяснение 51с | нет | «прикладной таймер» | **экспонента RTO 1-2-4-8-16** |
Вердикт: план Соннета — лучший из трёх. Принять за основу с поправками 1–6.