Files
loadtest/History/2026-07-11-sonnet-review.md
T
2026-07-11 15:16:41 +04:00

5.8 KiB
Raw Blame History

Расследование: загрузка файлов через Managed Flask на Shturval

1. Исходная проблема

Приложение: Flask 3.1.3 + flask-sock, загрузка файлов через WebSocket (64KB чанки). Платформа: Shturval 2.12.1 (managed Kubernetes), kube-vip ARP mode, Cilium Geneve.

Симптом: файл 3.7 MB — первые 90% загружаются мгновенно, затем пауза ровно 51 секунда. В ~80% соединений. Оставшиеся ~20% проходят мгновенно.

2. Что проверили (и исключили)

Код приложения

  • Пробовали flow control на клиенте (bufferedAmount) — не помогло
  • Пробовали server-side ACK (чанк → подтверждение → следующий) — сломалось полностью
  • Создали минимальный Flask (13 строк кода, без логики) — та же пауза
  • HTTP POST вместо WebSocket — та же пауза для тел >50KB
  • Gunicorn с gevent вместо Werkzeug — не влияет

Платформа

  • nodeSelector (под на одной ноде с ingress) — не влияет
  • Scale ingress до 4 реплик (на всех нодах) — не влияет
  • hostNetwork на python-поде (исключает Cilium Geneve) — не влияет
  • keepalive=0 к upstream (исключает nginx connection pool) — не влияет
  • vip_leaderelection=true на kube-vip (пробовали ранее) — не влияет
  • MSS clamping (пробовали ранее) — не влияет

nginx ingress

  • proxy_request_buffering on/off — не влияет
  • proxy_buffering on/off — не влияет
  • ModSecurity в DetectionOnly — не блокирует
  • Таймауты: proxy_send_timeout 600s, proxy_read_timeout 600s

3. Ключевой эксперимент: изоляция точки сбоя

Тестировали WebSocket 2MB с разных точек:

Источник Цель Результат
Pod внутри кластера ingress pod IP (та же нода, 172.16.1.51) 5/5 FAST
Pod внутри кластера ingress pod IP (другая нода, 172.16.0.9) 5/5 FAST
Pod внутри кластера ingress ClusterIP (10.105.13.158) 5/5 FAST
Pod внутри кластера свой же ClusterIP (без ingress) 3/3 FAST
SSH-хост (5.172.178.213) VIP (185.247.187.151) 2/5 FAST, 3/5 SLOW
Внешняя машина VIP ~15% FAST, ~85% SLOW

Вывод: внутри кластера всё мгновенно всегда. Пауза только при проходе через VIP извне.

4. Корень проблемы

Kube-vip в ARP-режиме с vip_leaderelection=false (отдельно от svc_election=true):

  • Все 4 ноды анонсируют VIP через ARP
  • Маршрутизатор выбирает ноду (вероятно, ECMP или последний ARP-ответ)
  • Kube-vip проводит leader election для сервиса и назначает VIP на интерфейс ноды-лидера
  • Лидером стала нода iot-naeel-workers-vqphm-bhbvs (10.10.102.4)
  • На этой ноде не было ingress-пода (ingress был только на 2 из 4 нод)
  • Трафик шёл: клиент → VIP → bhbvs → cross-node forward → ingress-нода → python-под

Cross-node форвардинг (через Geneve или IPIP) вызывает TCP stall:

  • Первые ~1MB данных проходят в TCP-буфер
  • Буфер заполняется → TCP объявляет zero window
  • Начинается stop-and-go oscillation: сервер читает порцию → окно приоткрывается → пара пакетов проходит → окно закрывается
  • Эффективная скорость падает до ~8 KB/s
  • Оставшиеся данные дрейнуются за ~51 секунду

Сервис имел externalTrafficPolicy: Cluster (дефолт), что разрешает kube-vip форвардить трафик на любую ноду, даже без локальных endpoints.

5. Решение

kubectl patch svc -n ingress shturval-ingress-controller-controller \
  -p '{"spec":{"externalTrafficPolicy":"Local"}}'

Плюс kubectl scale deploy -n ingress shturval-ingress-controller-controller --replicas=4 — чтобы на каждой ноде был локальный ingress-под.

С Local трафик идёт только на ноды с локальными ingress-подами. Cross-node форвардинг исключён.

Результат: 5/5 FAST (2MB WebSocket через VIP с внешней машины).

6. Что должно быть сделано на стороне платформы

Kube-vip с externalTrafficPolicy: Cluster должен форвардить трафик только на ноды с живыми endpoints сервиса, а не на все ноды подряд. Либо Shturval должен по умолчанию ставить externalTrafficPolicy: Local для LoadBalancer-сервисов, либо kube-vip должен корректно обрабатывать Cluster-режим без TCP stall.

7. Вердикт

Баг платформы. Код приложения корректен. Любое приложение на этом кластере, принимающее >50KB данных через VIP, будет иметь ту же проблему.