From a1d72b61d4472285f3dc2b3b4c5edd453e722434 Mon Sep 17 00:00:00 2001 From: Repinoid Date: Thu, 8 Oct 2026 16:50:36 +0300 Subject: [PATCH] =?UTF-8?q?doc:=20=D0=BD=D0=B0=D1=85=D0=BE=D0=B4=D0=BA?= =?UTF-8?q?=D0=B8=20=E2=80=94=20=D0=BA=D0=B0=D0=BA=20=D1=82=D0=B0=20=D0=B6?= =?UTF-8?q?=D0=B5=20=D1=81=D0=B5=D1=82=D0=B5=D0=B2=D0=B0=D1=8F=20=D0=B1?= =?UTF-8?q?=D0=BE=D0=BB=D0=B5=D0=B7=D0=BD=D1=8C=20=D0=BF=D0=BB=D0=B0=D1=82?= =?UTF-8?q?=D1=84=D0=BE=D1=80=D0=BC=D1=8B=20=D0=BE=D0=B1=D0=BE=D0=B9=D0=B4?= =?UTF-8?q?=D0=B5=D0=BD=D0=B0=20=D0=B2=20drhider=20(=D0=B1=D1=83=D1=84?= =?UTF-8?q?=D0=B5=D1=80=20=D0=BD=D0=B0=20=D0=92=D0=9C,=20pull=20=D0=B2?= =?UTF-8?q?=D0=BC=D0=B5=D1=81=D1=82=D0=BE=20push)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Собраны и проверены по источникам все факты из ~/nubes/drhider: - диагноз: ingress рвёт входящие тела >64КБ, egress не ограничен; - схема пофайлового транзита через ВМ-буфер + egress-GET (pull); - применённые фиксы платформы (cilium mtu:1400, keep-alive 10->75, proxy-body-size 1024m, HTTP/2 off) через ShturvalServiceConfig; - ложные следы, лимиты, SSRF/path-traversal защиты, тесты 25/25; - вывод: тикет в поддержку — не решение, нужен архитектурный обход. --- ...rge-body-workaround-findings-2026-10-08.md | 176 ++++++++++++++++++ 1 file changed, 176 insertions(+) create mode 100644 doc/thinking/nubes-large-body-workaround-findings-2026-10-08.md diff --git a/doc/thinking/nubes-large-body-workaround-findings-2026-10-08.md b/doc/thinking/nubes-large-body-workaround-findings-2026-10-08.md new file mode 100644 index 0000000..1770f24 --- /dev/null +++ b/doc/thinking/nubes-large-body-workaround-findings-2026-10-08.md @@ -0,0 +1,176 @@ +# Находки: как та же сетевая болезнь платформы уже решена в 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`, последовательные вызовы |