Files
SQS-service/doc/thinking/nubes-large-body-workaround-findings-2026-10-08.md
T
Repinoid a1d72b61d4 doc: находки — как та же сетевая болезнь платформы обойдена в drhider (буфер на ВМ, pull вместо push)
Собраны и проверены по источникам все факты из ~/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;
- вывод: тикет в поддержку — не решение, нужен архитектурный обход.
2026-10-08 16:50:36 +03:00

177 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Находки: как та же сетевая болезнь платформы уже решена в 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`, последовательные вызовы |