# Находки: как та же сетевая болезнь платформы уже решена в drhider (буфер на ВМ) **Дата:** 2026-10-08 **Источник находок:** репозиторий `~/nubes/drhider` (+ `~/nubes/drhider/upload-platform`) **Зачем:** у shared-sqs та же проблема (таймауты внешнего пути, большие тела), и наивный путь «отправить тикет в Nubes» оказался тупиком — явление признано болезнью платформы. В drhider ту же болезнь **обошли архитектурно**, а не починили. > ## ⚠️ ГЛАВНЫЙ ВЫВОД > **Ingress managed-кластера обрывает ВХОДЯЩИЕ тела больше порога, а ИСХОДЯЩИЙ > трафик (egress) из кластера не ограничен.** Отсюда рабочий приём: не «толкать» > большие данные в кластер (push), а **«вытягивать» их извне (pull)** — данные > кладутся на внешний хост-буфер, а приложение в кластере забирает их исходящим > запросом. Тикет в поддержку проблему не решает. --- ## 1. Диагноз проблемы (цитаты из drhider) `upload-platform/HISTORY/2026-09-06-layer2-architecture-and-api-boundaries.md:18`: > «**Причина существования**: Ingress-контроллер k8s-кластера (`pythonk8s`) > обрывает входящие запросы с телом более 64 КБ. Прямой `POST` больших файлов в > кластер невозможен; **исходящий трафик (egress) из кластера не ограничен**.» `PLAN-upload-layers.md:27` (контекст, «что уже проверено»): > «Паттерн загрузки (**шлюз managed-кластера рвёт тела >64КБ, egress не > ограничен**): браузер → `PUT` на ВМ-буфер → Flask `POST /api/upload_refs` → > egress `GET` (pull) → сессия.» Это ровно тот же класс, что явление A у shared-sqs (большие POST), только у drhider порог «тело >64 КБ» на ingress, а у SQS — MSS/MTU-порог ~1.4 КБ на внешнем шлюзе (`doc/thinking/nubes-ticket.md`, явление A). Причина на стороне платформы в обоих случаях. ## 2. Схема обхода, применённая в drhider (пошагово) `upload-platform/HISTORY/2026-09-06-layer2-architecture-and-api-boundaries.md:19-27` — «Пофайловый транзит (Per-File Transit)»: 1. Файл №k браузер отправляет методом `PUT` **в буфер на ВМ** (вне кластера). 2. Браузер сразу шлёт в приложение (Flask в кластере) `POST /api/upload_refs` — тело запроса маленькое (только ссылка на файл), поэтому ingress его пропускает. 3. Приложение **само забирает** файл исходящим потоковым `GET` прямо в память сессии — egress не режется. 4. Приложение немедленно отправляет `DELETE` на ВМ — буфер очищен. 5. Браузер переходит к файлу №k+1. **В буфере на ВМ в любой момент максимум один файл.** Дополнительные принципы оттуда же: - **RAM-only**: без постоянной записи на диск ни в кластере, ни на ВМ. - **Zero hardcode**: адрес буфера и префикс — параметры конфигурации (`vmUploadUrl` на фронте, `vmUploadPrefix` на бэке для обязательной SSRF-проверки). - **Автономный mock-буфер** (in-memory WebDAV) для тестов без внешней сети. `upload/README.md:13` подтверждает ту же механику: > «Пофайловый стриминг: для каждого файла выполняется `PUT` в буфер на ВМ, > немедленный `POST /api/upload_refs` в бэкенд, и после подтверждения приёма > буфер на ВМ очищается.» ## 3. Что нельзя потерять при переносе (накоплено в drhider) `PLAN-upload-layers.md` (раздел «Требования/что НЕЛЬЗЯ потерять»): - дедупликация «имя+размер» (иначе суффикс `_2`); - раскрытие ZIP с путями и фильтр расширений (`.pdf .doc .docx .txt .md`); - рекурсивный обход папок (`webkitdirectory`); - **SSRF-валидация URL** (`url.startswith(VM_UPLOAD_PREFIX)`); - `_safe_name` — защита от path traversal с сохранением подпапок; - **повторы pull** (3 попытки, пауза 2 с); - лимиты **50 МБ на файл / 500 МБ на сессию**; - различение «сессия не найдена» и «превышен лимит»; - статусы «пропущен (лимит)» / «не извлечён»; - экранирование имён для защиты от self-XSS. ## 4. Фиксы платформы, которые уже применили (и их границы) `PROBLEM-AND-SOLUTION.md` (drhider, v0.0.31, 2026-07-16): **Проблема 1 — пауза 51 с (MTU).** Underlay MTU 1450, Cilium Geneve добавляет 50 байт, дефолт пода 1500 → пакет 1550 → фрагментация → дроп → ядро ждёт повторной передачи 51 с. **Решение:** `cilium-config` → `mtu: 1400` (1400+50 = 1450). Задокументировано 2026-07-13, применено 2026-07-14. **Проблема 2 — `ERR_CONNECTION_RESET` в браузере.** Истинная причина — `keep-alive: 10` вместо дефолта 75: пока пользователь выбирал файлы (>10 с), nginx закрывал idle-соединение, браузер не замечал FIN и слал POST в закрытый сокет → RST. **Решение** (в платформе Штурвала, 2026-07-15) — через `ShturvalServiceConfig`, а не через kubectl: ```yaml controller: config: keep-alive: "75" proxy-body-size: "1024m" client-body-timeout: "60" client-header-timeout: "30" hostPort: enabled: true replicaCount: 2 service: type: LoadBalancer ``` > Важно: правки ingress на платформе делаются через `ShturvalServiceConfig` — > **через kubectl нельзя, ArgoCD откатывает**. **Ложные следы, которые опровергли** (та же таблица): kube-vip ARP flapping, hostPort на 2 из 4 нод, upstream keepalive, conntrack, MTU (для проблемы 2), HTTP/2. **Результаты после фиксов (v0.0.29, 2026-07-15):** 10× POST 63 КБ — 10/10 OK; полный UI-цикл (5 файлов + SSE + ZIP + CSV) — OK; 5× POST с интервалом 75 с — 5/5 OK; PDF 5 МБ через curl — 200 OK за 0.62 с. **Итого 25/25, 0 RST.** ⚠️ Но это вылечило ingress-таймер и MTU-фрагментацию, **а не порог размера тела**: обходной слой через ВМ-буфер остался нужен и является рабочим решением. ## 5. Обходы «не по сети» — тот же принцип pull/вынос `PROBLEM-AND-SOLUTION.md:71`: `doc_to_markdown()` переписали — вместо локального `soffice` (LibreOffice на ВМ, тяжёлый и не тянул) **HTTP-вызов внешнего сервиса** `liberta.containerk8s.dev.nubes.ru/convert` через `httpx`. То есть тяжёлую/медленную работу тоже вынесли наружу и ходят к ней по HTTP. Смежное (из кода drhider): конвертация `.doc` через `liberta` выполняется **последовательно** (`_LLM_CONCURRENCY = 1`) — сервис не выдерживает конкурентность (`drhider/extractor.py:160-194`, `drhider/scanner.py:122`). ## 6. Готовый переиспользуемый модуль (для справки) `PLAN-upload-layers.md` — слои выделены так, чтобы их можно было перенести в любой проект копированием (без общей репы): | Слой | Где | Ответственность | |---|---|---| | 0. Конфиг | общий | адрес ВМ-буфера, расширения, лимиты | | 1. Выбор файлов | фронт | таблица, дедуп, ZIP, дерево папок, статусы | | 2. Закачка через ВМ | фронт + бэк | `PUT` на ВМ → `upload_refs` pull → сессия | | 3. Логика приложения | бэк | обработка (у каждого своя) — **не выносится** | Реализация-эталон: `~/nubes/drhider/upload-platform/` (Слой 1 v0.1.13 готов, Слой 2 в работе на 2026-09-06), а также рабочий образец `~/nubes/drhider/upload/`. ## 7. Инфраструктурные факты - **ВМ-буфер:** `5.172.178.213` (вне кластера). Nginx WebDAV, каталоги `/var/www/drhider-upload/`, `/var/www/contracts-upload/`, cron-очистка каждые 5 минут (`find … -mmin +30 -delete`). Для целевого прода — перевод в RAM/in-memory буфер. - **SSH к ВМ:** `ssh -i ~/.ssh/naeel_vm_id_ed25519 naeel@5.172.178.213` (`README.md:141`). - **Кластеры/неймспейсы:** `pythonk8s` (ingress режет >64 КБ); сервисы drhider, contractor, uploader-dev. ## 8. Выводы (что берём из чужого опыта) 1. **Тикет в Nubes — не решение.** Это болезнь платформы; в drhider её не «починили», а обошли. Тикет (`doc/thinking/nubes-ticket.md`) имеет смысл только как фиксация для истории, не как способ получить рабочий сервис. 2. **Рабочий приём — pull вместо push:** внешний буфер + исходящий `GET` из кластера. Egress свободен, ingress режется. 3. **Правки ingress на платформе** — только через `ShturvalServiceConfig` (kubectl откатывается ArgoCD). 4. **MTU-фикс (`cilium mtu: 1400`)** нужен как гигиена, но порога больших тел не снимает. 5. **Тяжёлые/медленные операции** тоже выносятся наружу и вызываются по HTTP (пример: `liberta`). ## Источники (файл:строка) | Источник | Что | |---|---| | `~/nubes/drhider/PROBLEM-AND-SOLUTION.md` | MTU-фикс, keep-alive 10→75, ложные следы, тесты 25/25 | | `~/nubes/drhider/PLAN-upload-layers.md:27` | паттерн «шлюз рвёт >64 КБ, egress свободен» | | `~/nubes/drhider/upload/README.md:13` | пофайловый стриминг через ВМ-буфер | | `~/nubes/drhider/upload-platform/HISTORY/2026-09-06-layer2-architecture-and-api-boundaries.md:18-27` | диагноз + пошаговый пофайловый транзит, RAM-only, mock-буфер | | `~/nubes/drhider/docs/MSS-CLAMPING-PLAN.md`, `docs/about1500.md`, `docs/INGRESS-SOLUTIONS.md` | сопутствующие материалы по MTU/MSS/ingress | | `~/nubes/drhider/drhider/extractor.py:160-194`, `drhider/scanner.py:122` | вынос конвертации в сервис `liberta`, последовательные вызовы |