136 lines
5.7 KiB
Markdown
136 lines
5.7 KiB
Markdown
# Проблема загрузки файлов: диагностика и решение
|
||
|
||
**Дата:** 2026-07-16
|
||
**Версия приложения:** v0.0.31
|
||
**Кластер:** `iot-naeel` (4 ноды, Cilium Geneve, kube-vip, Штурвал)
|
||
|
||
---
|
||
|
||
## Проблема 1: 51-секундная задержка (MTU)
|
||
|
||
### Суть
|
||
При передаче POST из внешней сети — пауза 51с на любом объёме данных. Внутри кластера — мгновенно.
|
||
|
||
### Причина
|
||
Underlay MTU = 1450. Cilium Geneve добавляет 50 байт. Дефолтный MTU пода = 1500.
|
||
Cross-node пакет = 1500+50 = 1550 → фрагментация → дроп → ядро ждёт повторной передачи 51с.
|
||
|
||
### Решение
|
||
`cilium-config` → `mtu: 1400`. 1400+50 = 1450 = underlay.
|
||
Задокументировано 2026-07-13, применено 2026-07-14.
|
||
|
||
---
|
||
|
||
## Проблема 2: ERR_CONNECTION_RESET после фикса MTU
|
||
|
||
### Суть
|
||
curl с ВМ работает. Браузер — `ERR_CONNECTION_RESET` или `⏳ 100%`.
|
||
После `rollout restart` ingress — работает 5-10 минут, потом снова RST.
|
||
|
||
### Ложные следы (опровергнуты)
|
||
|
||
| Гипотеза | Почему неверна |
|
||
|----------|---------------|
|
||
| kube-vip ARP flapping | `kube-vip.io/vipHost: control-plane` — VIP жёстко на одной ноде |
|
||
| hostPort на 2 из 4 нод | Трафик только на control-plane, не на все 4 |
|
||
| upstream keepalive stale | `upstream-keepalive-connections: 0` не помог |
|
||
| conntrack | ETPolicy Cluster не при чём — трафик на одну ноду |
|
||
| MTU | Решено в проблеме 1 |
|
||
| HTTP/2 | `ERR_HTTP2_PROTOCOL_ERROR` → отключили → `ERR_CONNECTION_RESET` — симптом тот же |
|
||
|
||
### Истинная причина
|
||
**`keep-alive: 10`** — Штурвал ставил nginx `keep-alive: 10` (вместо дефолта 75).
|
||
Сценарий:
|
||
1. Браузер грузит страницу — TCP открыт
|
||
2. Пользователь выбирает файлы (>10с) — nginx закрывает idle соединение
|
||
3. Браузер не замечает FIN (буферизация ОС)
|
||
4. POST 63KB летит в закрытый сокет → TCP RST
|
||
|
||
### Решение (применено в платформе Штурвала 2026-07-15)
|
||
|
||
Через `ShturvalServiceConfig` (не kubectl — ArgoCD откатывает):
|
||
|
||
```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
|
||
```
|
||
|
||
Также в коде — `fetch('/health')` при загрузке страницы (прогрев upstream).
|
||
|
||
### v0.0.30 — .doc в диалоге выбора файлов
|
||
Добавлен `accept=".doc,.docx,.pdf,.txt,.zip"` в `<input type="file">`.
|
||
`doc_to_markdown()` переписан: вместо `soffice` (LibreOffice на ВМ) — HTTP-вызов `liberta.containerk8s.dev.nubes.ru/convert` через `httpx`.
|
||
|
||
### v0.0.31 — .doc обрабатывается обфускацией
|
||
`.doc` теперь не просто конвертируется, а проходит полную обфускацию (замена сущностей, mapping.csv). В архиве — `.md` с заменённым текстом, не исходный `.doc`.
|
||
|
||
### Результаты тестов (v0.0.29, 2026-07-15)
|
||
|
||
| Тест | Результат |
|
||
|------|-----------|
|
||
| 10× POST 63KB (свежие страницы) | **10/10 OK:200** |
|
||
| UI: 5 файлов + SSE + ZIP + CSV | ✅ полный цикл |
|
||
| 5× POST через 75с (keep-alive окно) | **5/5 OK:200** |
|
||
| 5MB PDF через curl | ✅ 200 OK, 0.62с |
|
||
| **ИТОГО** | **25/25 успешно, 0 RST** |
|
||
|
||
---
|
||
|
||
## Итоговая конфигурация кластера
|
||
|
||
### Ingress (через платформу Штурвала, несбрасываемо ArgoCD)
|
||
|
||
| Параметр | Дефолт nginx | Было (Штурвал) | Сейчас |
|
||
|----------|-------------|----------------|--------|
|
||
| `keep-alive` | 75 | **10** | **75** |
|
||
| `proxy-body-size` | 8m | 8m | **1024m** |
|
||
| `client-body-timeout` | 60 | **10** | **60** |
|
||
| `client-header-timeout` | 60 | **10** | **30** |
|
||
| `use-http2` | true | true | **false** |
|
||
| `upstream-keepalive-connections` | 0 | 50 | 0 |
|
||
| `error-log-level` | info | info | **debug** (вернуть info) |
|
||
|
||
### Cilium (через kubectl, не сбрасывается)
|
||
|
||
| Параметр | Дефолт | Сейчас |
|
||
|----------|--------|--------|
|
||
| `mtu` | 1500 | **1400** |
|
||
|
||
### Приложение (drhider v0.0.29)
|
||
|
||
```python
|
||
# site/app.py
|
||
VERSION = "0.0.29"
|
||
app.config["MAX_CONTENT_LENGTH"] = 200 * 1024 * 1024 # 200 MB
|
||
```
|
||
|
||
```javascript
|
||
// site/templates/index.html — warmup при загрузке
|
||
window.addEventListener('load', () => {
|
||
sf = []; fi.value = ''; rr();
|
||
fetch('/health').catch(() => {}); // прогрев upstream
|
||
});
|
||
```
|
||
|
||
---
|
||
|
||
## Документация
|
||
|
||
| Файл | Что |
|
||
|------|-----|
|
||
| `docs/ARCHITECTURE.md` | Архитектура + требования к Flask |
|
||
| `docs/MIGRATION-GUIDE.md` | Инструкция переноса UI на Flask |
|
||
| `History/2026-07-14-browser-rst-debug.md` | Полная история диагностики RST |
|
||
| `History/2026-07-15-rst-root-cause.md` | Финальный диагноз |
|
||
| `History/2026-07-15-cluster-dump.md` | Дамп кластера |
|
||
| `STATE-2026-07-15.md` | Состояние проекта на 2026-07-15 |
|