Files
drhider/PROBLEM-AND-SOLUTION.md
T

9.5 KiB
Raw Blame History

Проблема загрузки файлов: диагностика и решение

Дата: 2026-07-13


В чём проблема

При передаче данных через HTTP POST из внешней сети на managed-сервисы, развёрнутые в кластере (Flask, любые другие), периодически возникает пауза длительностью 51 секунда. Проблема затрагивает любой сервис, чей трафик проходит через ingress-контроллер Штурвала при cross-node-форвардинге. Размер передаваемых данных не имеет значения — задержка константна, проявляется даже на 100 KB.

Внутри кластера (pod → ingress pod напрямую) передача происходит мгновенно. Проблема проявляется только при проходе трафика извне через VIP (kube-vip), который распределяет запросы на все ноды кластера.

Причина (технически)

Cilium — сетевая подсистема Kubernetes, отвечает за связь между подами на разных нодах. Geneve — протокол туннелирования, который Cilium использует чтобы «обернуть» обычный сетевой пакет для передачи через физическую сеть между серверами.

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:

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 <drhider-ns> --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 на этапе развёртывания, до деплоя приложений.