From b5b36e08cc25765a724295af1f911e918c4406fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CNaeel=E2=80=9D?= Date: Thu, 16 Jul 2026 09:03:01 +0400 Subject: [PATCH] =?UTF-8?q?v2.0.5:=20=D1=87=D0=B5=D1=81=D1=82=D0=BD=D1=8B?= =?UTF-8?q?=D0=B9=20=D1=81=D1=87=D1=91=D1=82=D1=87=D0=B8=D0=BA=20=D0=B7?= =?UTF-8?q?=D0=B0=D0=B3=D1=80=D1=83=D0=B7=D0=BA=D0=B8=20+=20=D0=B2=D0=BE?= =?UTF-8?q?=D0=BF=D1=80=D0=BE=D1=81=20Sonnet=20=D0=BF=D1=80=D0=BE=20TCP=20?= =?UTF-8?q?stall?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sonnet-question-tcp-stall-2026-07-16.md | 116 ++++++++++++++++++ contracts-flask | 2 +- 2 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 History/sonnet-question-tcp-stall-2026-07-16.md diff --git a/History/sonnet-question-tcp-stall-2026-07-16.md b/History/sonnet-question-tcp-stall-2026-07-16.md new file mode 100644 index 0000000..08fbd9e --- /dev/null +++ b/History/sonnet-question-tcp-stall-2026-07-16.md @@ -0,0 +1,116 @@ +# Вопрос для Sonnet — TCP stall 51s на первом upload (2026-07-16) + +## Ситуация + +Два сервиса на одном кластере (contractor + drhider), оба Flask на Штурвале. +После миграции contractor на Flask: **первый upload файла из браузера зависает на ~51 секунду**, потом проходит. Второй и последующие файлы — мгновенно. + +## Симптомы + +- **Браузер**: первый POST /upload (~63KB) висит 51 секунду на ~95% прогресса, потом ок +- **Логи ingress (nginx)**: ВСЕ запросы (включая успешные) проходят за 0.06-0.16 сек +- **Зависание происходит ДО того как запрос попадает в nginx** — на уровне TCP +- **51 секунда** — стабильно, воспроизводится периодически +- **Затрагивает оба сервиса**: и contractor и drhider + +## Топология кластера + +``` +Клиент (браузер) + ↓ +185.247.187.151 (kube-vip, LoadBalancer, externalTrafficPolicy: Cluster) + ↓ на ЛЮБУЮ из 4 нод +shturval-ingress-controller (4 реплики, по одной на каждой ноде) + ↓ upstream к поду сервиса +pythonk8s (1 под, нода workers-6f74n, IP 172.16.1.221:5000) +``` + +4 ноды (k8s 1.34.1): +- control-plane-xb699 (10.10.102.2) — поды: 172.16.0.0/24 +- workers-6f74n (10.10.102.3) — поды: 172.16.1.0/24 ← **contractor здесь** +- workers-bhbvs (10.10.102.4) — поды: 172.16.2.0/24 +- workers-v8zq4 (10.10.102.5) — поды: 172.16.3.0/24 + +Ingress-поды (4 шт): +- 172.16.0.73 на control-plane (.2) +- 172.16.1.65 на workers-6f74n (.3) ← **локально с contractor** +- 172.16.2.144 на workers-bhbvs (.4) +- 172.16.3.149 на workers-v8zq4 (.5) + +Cilium CNI: Geneve-энкапсуляция, MTU 1400, encryption Disabled. + +kube-vip: DaemonSet (по одному на ноду .2 .3 .4 .5) + provider. + +## КЛЮЧЕВАЯ НАХОДКА + +```yaml +# Ingress ConfigMap: +upstream-keepalive-connections: "0" +upstream-keepalive-time: 60s +``` + +**Каждый HTTP-запрос от ингресса к бэкенду — новый TCP-рукопожатие.** + +75% запросов проходят через Geneve-туннель (ingress на .2/.4/.5 → backend на .3). + +51 секунда ≈ `tcp_syn_retries=5`: 1+2+4+8+16 = 31с или 3+6+12+24+48 = 93с. +Точнее — зависит от начального RTO ядра. + +## Вторая подозрительная находка + +Cilium на ноде .3 (`cilium bpf tunnel list`) показывает Geneve-туннели: + +``` +172.16.3.0 → 10.10.102.5 (нода .5) +172.16.1.0 → 10.10.102.3 (себя) +172.16.0.0 → 10.10.102.2 (нода .2) +``` + +**Туннель к ноде .4 (172.16.2.0 → 10.10.102.4) ОТСУТСТВУЕТ в списке!** + +Всего 3 туннеля при 4 нодах. + +## Что проверено + +- `cilium status --verbose`: все healthy, 0 ошибок +- `cilium monitor -t drop`: дропов нет +- `cilium bpf ct list global`: 4682 записи, conntrack не переполнен +- `cilium encrypt status`: Disabled +- `cilium bpf lb list`: сервис pythonk8s (10.98.50.130:80 → 172.16.1.221:5000) — OK +- Cilium endpoint 1010 (pythonk8s): Disabled/Disabled, ready +- Логи ingress (последние 100 запросов): все 200 OK, 0.007-0.162 сек +- Ошибок `upstream timed out`, `connect failed`, `502/504` в логах ingress НЕТ +- kube-vip поды: все Running +- kube-vip provider: Running (рестарт 46ч назад) + +## Гипотезы + +1. **Первый TCP-рукопожатие ingress→backend через Geneve теряет SYN** + - Причина: eBPF-программа Cilium для нового соединения не готова мгновенно + - Причина: neighbour discovery для Geneve endpoint + - Вопрос: почему именно 51 секунда, а не мгновенный RST или 1-2 секунды? + +2. **Отсутствующий Geneve-туннель к ноде .4** + - Если запрос попадает на ingress на .4 → трафик к backend на .3 должен идти через туннель + - Если туннеля нет в bpf map — куда идёт трафик? Через хост-сеть? Через другую ноду? + +3. **externalTrafficPolicy: Cluster + 4 ingress реплики** + - kube-vip может отправить трафик на ЛЮБУЮ ноду + - Если на ноду без ingress-пода → kube-proxy/Cilium форвардит на другую ноду → двойной Geneve + - Может ли это добавлять задержку? + +4. **client-body-timeout: 60 + первый upload = гонка** + - Если TCP-рукопожатие занимает 51 секунду, а body upload начинается после + - Может ли общее время превысить 60 секунд и вызвать 408/413? + +## Вопросы + +1. Почему `upstream-keepalive-connections: "0"` + Geneve вызывает 51-секундный TCP-stall именно на ПЕРВОМ запросе, а последующие идут мгновенно? + +2. Отсутствие Geneve-туннеля к ноде .4 (172.16.2.0) в `cilium bpf tunnel list` на ноде .3 — это норма или баг Cilium? Может ли это быть причиной потери SYN-пакетов? + +3. Как диагностировать ГДЕ именно теряется SYN: между kube-vip и ingress, или между ingress и backend? Какие инструменты Cilium/ядренные использовать? + +4. Рекомендация: менять `upstream-keepalive-connections` на 2-4 вместо 0 — решит ли это проблему первого запроса? + +5. Есть ли ещё что посмотреть в кластере чего мы не проверили? diff --git a/contracts-flask b/contracts-flask index 1bd62a5..f4bae5a 160000 --- a/contracts-flask +++ b/contracts-flask @@ -1 +1 @@ -Subproject commit 1bd62a51efd11b2aa5f0a4853453404d575ba488 +Subproject commit f4bae5a6b957cc6ec7345dbd7d2d23a2b0133c9c