diff --git a/README.md b/README.md new file mode 100644 index 0000000..f66a16d --- /dev/null +++ b/README.md @@ -0,0 +1,77 @@ +# loadtest — загрузка файлов через Managed Flask (Shturval) + +Тестовый проект для проверки Managed Flask на платформе Shturval 2.12.1. +Задача: принять загрузку файлов 3-5 MB через веб-интерфейс стабильно и быстро. + +## Как работает + +- **Фронтенд:** HTML + vanilla JS, выбор файлов, прогресс-бар, drag-and-drop +- **Бэкенд:** Flask 3.1 + flask-sock (WebSocket) + Gunicorn (gevent worker) +- **Загрузка:** WebSocket, 64KB чанки, один connection на все файлы +- **Деплой:** git push → Shturval auto-deploy + +## Проблема: 51-секундный TCP stall + +### Симптом +Файл 3.7 MB — 90% мгновенно, затем пауза ровно 51 секунда. В ~80% соединений. + +### Корень +`externalTrafficPolicy: Cluster` (дефолт) на LoadBalancer-сервисе `shturval-ingress-controller-controller`. Kube-vip в ARP-режиме форвардил трафик на ноды без локального ingress-пода → cross-node форвардинг через Geneve → TCP zero window oscillation → 51 секунда. + +### Исправление (платформа) +```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 +``` + +С `Local` трафик идёт только на ноды с локальными ingress-подами. Cross-node форвардинг исключён. + +### Что НЕ помогло +- Flow control на клиенте (`bufferedAmount`, ACK) +- Gunicorn вместо Werkzeug +- nginx keepalive, buffering, таймауты +- nodeSelector, hostNetwork +- HTTP POST вместо WebSocket + +Внутри кластера всё работало мгновенно — проблема была только при проходе через VIP извне. + +## Как юзать загрузку + +1. Открыть https://loadtest.pythonk8s.dev.nubes.ru/ +2. Нажать «Выбрать файлы» или перетащить файлы в зону загрузки +3. Нажать «Загрузить» +4. Прогресс-бар показывает реальный прогресс +5. После загрузки — ✓ с временем и скоростью + +Поддерживается множественная загрузка (несколько файлов подряд через один WebSocket). + +## Структура проекта + +``` +. +├── Dockerfile # Gunicorn CMD (не используется Shturval) +├── requirements.txt # flask, gunicorn, gevent, flask-sock +├── site/ +│ ├── app.py # Flask: /, /health, /upload (HTTP), /ws-upload (WS) +│ ├── static/ +│ │ └── app.js # Клиент: выбор файлов, WebSocket загрузка +│ └── templates/ +│ └── index.html # UI +└── History/ # Полная история расследования + ├── 2026-07-11-fix-externalTrafficPolicy.md # Финальный фикс + ├── 2026-07-11-sonnet-review.md # Отчёт для ревью + └── 2026-07-11-review-response.md # Рецензия и выводы +``` + +## Выводы для других проектов на Shturval + +1. **Managed Flask может принимать большие файлы** — после фикса платформы +2. **Всегда проверять «работает ли изнутри кластера»** — это изолирует платформу от кода +3. **WebSocket через managed ingress работает** — с правильным `externalTrafficPolicy` +4. **Если платформа не чинится** — вынести загрузку на ВМ с родным nginx (как в проекте `contracts`) + +## Версия + +Текущая: **1.0.25** (baseline WebSocket + Gunicorn gevent)