# Проблема загрузки файлов: диагностика и решение **Дата:** 2026-07-13 --- ## В чём проблема При загрузке файлов через веб-интерфейс drhider периодически возникает пауза 51 секунда. Размер файла не важен — даже 100 KB могут «залипнуть». ### Причина (технически) Кластер использует Cilium CNI с Geneve-туннелем между нодами. Geneve добавляет 50 байт к каждому пакету. При underlay MTU = 1450 (подтверждено замером) пакет с Geneve-заголовком не влазит → роутер не может фрагментировать (ICMP заблокирован) → TCP ждёт таймаут 51 секунду. ### Почему не всегда Проблема только при **cross-node**-форвардинге (kube-vip направляет трафик на ingress-под на другой ноде). При попадании на ту же ноду — всё мгновенно. --- ## Что сделано сейчас (временный обход) Для обхода проблемы были применены два изменения: **1. `externalTrafficPolicy: Local`** — запрещает cross-node-форвардинг. Трафик обслуживается только на той ноде куда пришёл, без туннеля → stall отсутствует. **2. 4 реплики ingress вместо 2** — у Штурвала по умолчанию 2 пода, а нод 4. С `Local` трафик на ноду без пода дропается. Выставлено 4 — по одной на каждую ноду. **Проблема:** Штурвал управляет ingress через Helm с `replicaCount: 2` и при каждом реконсиле (~каждый час) возвращает 4 обратно на 2. Половина запросов начинает падать с `ERR_TIMED_OUT`. Требуется постоянный ручной контроль. --- ## Корневое решение Выставить корректный MTU в Cilium: ```bash kubectl edit configmap -n kube-system cilium-config # mtu: 1450 kubectl rollout restart ds/cilium -n kube-system kubectl rollout restart deploy -n ingress shturval-ingress-controller-controller # Вернуть Cluster kubectl patch svc -n ingress shturval-ingress-controller-controller \ -p '{"spec":{"externalTrafficPolicy":"Cluster"}}' ``` После этого: - Cross-node-форвардинг работает без stall - Ingress остаётся на 2 репликах (стоковое значение Штурвала) - `Local` не нужен - Ручной scale не нужен ### Почему 1450 Замер выполнен 2026-07-13: ``` ping -M do -s 1472 → Message too large (1500 не проходит) ping -M do -s 1422 → ok (1450 проходит) ``` Underlay MTU = 1450 → Geneve-туннель = 1400 → поды = 1400. ### Почему это безопасно - Понижение MTU консервативно: меньше пакет → меньше проблем - Влияет на весь кластер одинаково (не ломает отдельные сервисы) - Стандартная практика для туннельных CNI (Geneve, VXLAN, GRE) - Не трогает логику Штурвала --- ## Полный анализ Опуса 4.8 ### Главное уточнение `mtu` в cilium-config — это MTU **underlay-сети** (физической сети между нодами), а НЕ туннеля. Cilium сам вычитает 50 байт на Geneve: ``` mtu: 1450 → tunnel/pod MTU = 1450 − 50 = 1400 ``` По умолчанию Cilium автодетектит underlay 1500 → туннель 1450. Если stall всё равно происходит — значит реальный underlay < 1500. Поэтому нужно измерять, не гадать. ### Подводные камни - **ConfigMap сам не применяется.** Нужен `kubectl rollout restart ds/cilium -n kube-system` — без этого агенты продолжат работать со старым MTU. - **Существующие TCP-соединения не поменяют MSS** — эффект только на новые соединения после рестарта. - **Нужен рестарт подов приложения** (ingress, drhider) — veth-интерфейс пода получает MTU при создании. Для транзитного (ingress) трафика обновления маршрутов ноды обычно достаточно на новых соединениях, но для чистоты — пересоздать. - Пересоздавать туннели вручную не нужно — рестарт агента переинициализирует `cilium_geneve` device. - **Установленный вручную `externalTrafficPolicy: Local`** надо вернуть на `Cluster` через `kubectl patch` (ставился не через Helm, сам не откатится). ### Риски для других сервисов `mtu` — параметр всего кластера, влияет на ВСЕ поды и ВСЕ ноды. **Понижение MTU безопасно** (меньше = консервативнее). Максимум — небольшой рост числа пакетов / микроскопическая потеря throughput. Функционально ничего не ломает. Риск в момент раскатки: `rollout restart ds/cilium` перезапускает агенты по нодам. eBPF-датаплейн переживает рестарт (трафик идёт), но возможны короткие блипы. Лучше делать в окно. Настоящий риск — только если поставить **слишком высокий** MTU (проблема останется) или **слишком низкий без нужды** (лишний оверхед). Поэтому критически важен замер. ### iptables как альтернатива **Не равноценен и с Cilium может не сработать.** Cilium с eBPF-датаплейном / kube-proxy replacement обходит netfilter `FORWARD` для трафика подов. Правило в `mangle/FORWARD` может просто не видеть нужные пакеты. `--clamp-mss-to-pmtu` берёт MSS из PMTU исходящего маршрута. Если MTU маршрута неверен — клампинг будет неправильным. Надёжнее явный `--set-mss 1400`. На какую ноду: kube-vip раздаёт VIP на **все 4 ноды** → правило нужно на всех (DaemonSet/node-level). Вывод: iptables — костыль-fallback, который с Cilium может оказаться нерабочим. **Правильный путь — `cilium-config`**, потому что Cilium ставит `advmss` на маршрутах (тот же MSS clamping, но нативно в eBPF-датаплейне). ### Верификация после изменения ```bash # Проверить что Cilium применил MTU kubectl -n kube-system exec ds/cilium -- cilium-dbg status | grep -i mtu ip link show | grep -E 'cilium|geneve' # MTU tunnel-девайса ip route show | grep -E 'advmss|mtu' # advmss на cilium-маршрутах # Проверить MSS в SYN-пакетах tcpdump -ni any 'tcp[tcpflags] & tcp-syn != 0' -vv | grep mss ``` Функционально: файл >5 MB через drhider — без паузы 51с. F5 без ERR_TIMED_OUT. ### Более грамотная альтернатива на будущее Cilium **DSR + `kubeProxyReplacement`** убирает cross-node SNAT на обратном пути целиком — двойной инкапсуляции return-трафика (главная причина stall) просто не возникает. Но это меняет всю сетевую конфигурацию, для быстрого фикса — избыточно. ### Итоговый вердикт Путь правильный (фикс корня, а не симптома). Конечное состояние `Cluster + корректный MTU + replicaCount: 2` — чистое и согласованное: убирает разом и ручной scale, и Local-костыль.