# Проблема загрузки файлов: диагностика и решение **Дата:** 2026-07-13 --- ## В чём проблема При передаче данных через HTTP POST из внешней сети на managed-сервисы, развёрнутые в кластере (Flask, любые другие), периодически возникает пауза длительностью 51 секунда. Проблема затрагивает любой сервис, чей трафик проходит через ingress-контроллер Штурвала при cross-node-форвардинге. Размер передаваемых данных не имеет значения — задержка константна, проявляется даже на 100 KB. Внутри кластера (pod → ingress pod напрямую) передача происходит мгновенно. Проблема проявляется только при проходе трафика извне через VIP (kube-vip), который распределяет запросы на все ноды кластера. ### Причина (технически) *Cilium — сетевая подсистема Kubernetes, отвечает за связь между подами на разных нодах. Geneve — протокол туннелирования, который Cilium использует чтобы «обернуть» обычный сетевой пакет для передачи через физическую сеть между серверами.* ```mermaid flowchart LR A["Geneve 50 B"]:::orange --- B["Данные 1450 B"]:::green B -.-> C["Итого 1500 B"]:::gray C --> D["Underlay-канал 1450 B"]:::red D --> E["TCP stall 51с"]:::dark classDef orange fill:#ff9800,color:#fff,stroke:#e65100 classDef green fill:#4caf50,color:#fff,stroke:#2e7d32 classDef gray fill:#9e9e9e,color:#fff,stroke:#616161 classDef red fill:#f44336,color:#fff,stroke:#c62828 classDef dark fill:#b71c1c,color:#fff ``` > **1450** (данные) + **50** (Geneve) = **1500** → underlay **1450** → не проходит → TCP stall **51 секунда**. - Cilium соединяет ноды через **Geneve-туннель** - Geneve добавляет к каждому пакету **+50 байт** заголовков (внешний IP 20 + UDP 8 + Geneve 8 + инкапсулируемый L2-кадр 14) - Cilium ожидает что underlay (физический канал между нодами) = **1500 байт** > *Стандартный Ethernet MTU — 1500. Все сетевые интерфейсы, драйверы и протоколы по умолчанию исходят из этого. Cilium опрашивает интерфейс ноды, видит 1500 и принимает за истину. Почему реальный underlay оказался 1450 — может быть связано с инфраструктурой хостинг-провайдера, промежуточным сетевым оборудованием или инкапсуляцией на уровне виртуализации. Без диагностики на стороне провайдера точная причина неизвестна. Факт установлен замером: 1500 не проходит, 1450 проходит.* - Cilium считает: **1500 − 50 = 1450** — такой MTU назначает туннелю и подам - Реальный underlay (измерен `ping -M do`): **1450 байт**, а не 1500 - Пакет из пода = **1450 байт** - Пакет + Geneve-заголовок = **1450 + 50 = 1500 байт** - Реальный канал = **1450 байт** → пакет **1500 не влазит** - Роутер пытается отправить ICMP «fragmentation needed», но ICMP заблокирован - Отправитель не знает что пакет не дошёл, ждёт подтверждения - TCP включает exponential backoff от начального RTO 200ms: **0.2с → 0.4с → 0.8с → 1.6с → 3.2с → 6.4с → 12.8с → 25.6с = 51 секунда** - Через 51 секунду прикладной таймаут (браузер/curl) разрывает ожидание → `ERR_TIMED_OUT` ### Почему не всегда Проблема только при **cross-node**-форвардинге (kube-vip направляет трафик на ingress-под на другой ноде). При попадании на ту же ноду — всё мгновенно. --- ## Что сделано сейчас (временный обход) Для обхода проблемы были применены два изменения: **1. `externalTrafficPolicy: Local`** — запрещает cross-node-форвардинг. Трафик обслуживается только на той ноде куда пришёл, без туннеля → stall отсутствует. **2. 4 реплики ingress вместо 2** — у Штурвала по умолчанию 2 пода, а нод 4. С `Local` трафик на ноду без пода дропается. Выставлено 4 — по одной на каждую ноду. **Проблема:** Штурвал управляет ingress через Helm с `replicaCount: 2`. При обнаружении расхождения (фактическое состояние не соответствует Helm-значениям) реконсилер возвращает реплики с 4 обратно на 2. Интервал не фиксирован — срабатывает по событию (деплой, изменение конфигурации). Половина запросов начинает падать с `ERR_TIMED_OUT`. --- ## Корневое решение Выставить корректный MTU в Cilium: ```bash kubectl patch configmap -n kube-system cilium-config --type merge -p '{"data":{"mtu":"1400"}}' kubectl rollout restart ds/cilium -n kube-system kubectl delete pods -n ingress --all --force --grace-period=0 kubectl delete pods -n --all --force --grace-period=0 ``` После этого: - Cross-node-форвардинг работает без stall - Ingress остаётся на 2 репликах (стоковое значение Штурвала) - `Local` не нужен - Ручной scale не нужен ### Почему 1400 Underlay между нодами — **1450** (измерен `ping -M do`). В этой версии Cilium (`routing-mode: tunnel`, `tunnel-protocol: geneve`) значение `mtu` задаёт pod MTU напрямую. Geneve-заголовок (+50 байт) добавляется поверх. ``` Pod MTU 1400 → пакет 1400 + Geneve 50 = 1450 = underlay 1450 → проходит Pod MTU 1450 → пакет 1450 + Geneve 50 = 1500 > underlay 1450 → дроп ``` | Параметр | Значение | |---------|----------| | Underlay между нодами | 1450 | | `cilium-config mtu:` | **1400** | | Pod MTU | 1400 | | Пакет через Geneve | 1400 + 50 = 1450 | > В некоторых версиях Cilium `mtu` означает underlay (Cilium сам вычитает 50). В нашей — задаёт pod MTU напрямую. Проверено 2026-07-14: `mtu: 1450` → pod 1450 (не работает через Geneve), `mtu: 1400` → pod 1400 (работает). --- ## Результаты (кластер `iot-naeel`, 2026-07-14) | Метрика | До (MTU 1450) | После (MTU 1400 + Connection: close) | |---------|---------------|--------------------------------------| | Upload из браузера | ERR_CONNECTION_RESET | <0.2s | | 5 файлов подряд | первый сбой | все <0.1s | | F5 при обработке | мёртвые соединения | чисто | | Edge 7× подряд | на 5-й сбой | стабильно | --- ## Рекомендации для production-кластеров На действующих кластерах с активными сервисами **НЕ рекомендуется** применять `kubectl edit configmap` + `rollout restart ds/cilium` вручную. Причины: - `rollout restart ds/cilium` перезапускает сетевой слой на **всех** нодах одновременно (rolling update, но всё же) - eBPF-датаплейн переживает рестарт, но возможны кратковременные блипы - Ошибка в значении MTU затронет **все** сервисы кластера - На production требуется окно обслуживания и мониторинг **Рекомендуемый порядок для production:** 1. Замерить underlay MTU между нодами через `ping -M do` 2. Передать значение провайдеру/девопсу для включения в конфигурацию кластера 3. Применить при плановом обслуживании или при создании новых кластеров 4. Для существующих кластеров — согласовать окно и порядок отката **Для новых кластеров:** включить `mtu: <измеренное значение>` в конфиг Cilium на этапе развёртывания, до деплоя приложений.