# Расследование: загрузка файлов через 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. Решение ```bash 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, будет иметь ту же проблему.