8.8 KiB
Проблема загрузки файлов: диагностика и решение
Дата: 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. kubectl scale replicas=4 — у Штурвала по умолчанию 2 ingress-пода, а нод 4. С Local трафик на ноду без пода дропается. Поставили 4 реплики — по одной на каждую ноду.
Проблема: Штурвал управляет ingress через Helm с replicaCount: 2 и при каждом реконсиле (~каждый час) сбрасывает 4 обратно на 2. Половина запросов начинает падать с ERR_TIMED_OUT. Приходится постоянно подмасштабировать вручную.
Корневое решение
Выставить корректный MTU в Cilium:
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_genevedevice. - Установленный вручную
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-датаплейне).
Верификация после изменения
# Проверить что 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-костыль.