Маршрутизация в Kubernetes: как устроены таблицы маршрутов между подами и нодами | AdminWiki

Маршрутизация в Kubernetes: как устроены таблицы маршрутов между подами и нодами

18 сентября 2026 11 мин. чтения

Как трафик ходит между подами и нодами: базовая модель

В Kubernetes нет одной общей таблицы маршрутов. Маршрутизация разделена на два уровня: таблица внутри network namespace каждого пода и таблица в сетевом namespace каждой ноды. В поде обычно один интерфейс eth0 и один default-маршрут, который прописал CNI-плагин. На ноде десятки записей: маршруты к podCIDR других нод через туннель или напрямую, маршрут к локальному podCIDR через bridge и служебные записи самого CNI.

Pod-to-pod трафик идёт по этим маршрутам, и за них отвечает только CNI. Трафик к ClusterIP и NodePort устроен иначе: виртуальный IP не назначен ни на один интерфейс, а балансировкой и DNAT занимается kube-proxy через iptables или IPVS либо eBPF-программы Cilium при включённом kube-proxy replacement. Отсюда первый шаг диагностики: понять, ломается прямой обмен по podIP или только обращение к ClusterIP при рабочем podIP.

Путь пакета целиком: pod A → veth-пара → bridge или eBPF-хук на ноде A → таблица маршрутов ноды A → туннель (VXLAN, IPIP) или нативный маршрут → нода B → veth → pod B. PodCIDR для каждой ноды выделяет kube-controller-manager и передаёт CNI-плагину. Если CIDR при установке Calico через Tigera Operator не совпал с CIDR, заданным при kubeadm init, маршруты с самого начала ведут в недостижимые адреса: у оператора по умолчанию другой блок. Проверить эту связку на этапе развёртывания помогает материал про установку кластера Kubernetes через kubeadm на Ubuntu 22.04.

Общую картину сетей кластера и разницу между pod network и service network удобно держать в одном справочнике: практический гайд по сетям Kubernetes.

Pod-to-pod и service-трафик: два разных механизма

Pod-to-pod это L3-маршрутизация по адресам podCIDR. Пакет адресован конкретному podIP, решение о следующем хопе принимает ядро Linux по таблице маршрутов ноды. CNI-плагин заботится о том, чтобы в этой таблице были корректные записи для всех podCIDR кластера, и обновляет их при появлении новых нод.

Service-трафик начинается с виртуального адреса. Пакет к ClusterIP 10.96.0.10 проходит цепочку KUBE-SERVICES в таблице nat, затем KUBE-SVC-<hash>, где backend выбирается правилом statistic mode random, и в KUBE-SEP-<hash> адрес назначения подменяется через DNAT на конкретный podIP и порт. Только после DNAT ядро делает повторный поиск маршрута, и в работу вступает маршрутизация CNI. Два механизма работают последовательно, поэтому и проверяются по отдельности.

Практическое следствие: правила KUBE-* не влияют на прямой обмен между подами, поэтому ошибки в маршрутах CNI нельзя вылечить правкой iptables, и наоборот.

Что происходит с пакетом от пода-источника до пода-получателя

  1. Pod A формирует пакет на podIP пода B. Адрес назначения не входит в локальную подсеть, поэтому пакет уходит по default-маршруту через шлюз (обычно .1 в podCIDR) на интерфейс eth0 внутри netns пода.
  2. Пакет выходит через veth-пару и попадает в root netns ноды A. Там его принимает bridge (cni0, docker0) или eBPF-программа, привязанная к интерфейсу.
  3. Нода A ищет маршрут до podIP B. Если podCIDR ноды B анонсирован по BGP (Calico в режиме без инкапсуляции), запись ведёт напрямую на IP ноды B с протоколом bird. Если используется оверлей, запись ведёт на туннельный интерфейс flannel.1 или vxlan.calico, и пакет инкапсулируется.
  4. Пакет приходит на ноду B, распаковывается (для туннеля) либо принимается напрямую, после чего маршрут к локальному podCIDR направляет его в veth нужного пода.
  5. Для прохождения пакетов между интерфейсами ноды нужны включённый ip_forward и загруженный модуль br_netfilter. Их состояние проверяют через sysctl и lsmod, а модули overlay и br_netfilter загружаются при подготовке ноды.

Таблица маршрутов внутри пода: что там на самом деле

Внутри пода маршрутов почти нет. Типичный вывод выглядит так:

kubectl exec -it app-7d9f8b6c5d-xk2lp -- ip route
default via 10.244.1.1 dev eth0
10.244.1.1 dev eth0 scope link

Шлюз и подсеть задаёт CNI-плагин. Маршрутов к другим подам внутри netns нет: выбор следующего хопа делает нода. Проверить адрес, MTU и DNS можно так:

kubectl exec -it app-7d9f8b6c5d-xk2lp -- ip addr show eth0
kubectl exec -it app-7d9f8b6c5d-xk2lp -- cat /etc/resolv.conf

MTU в поде часто меньше MTU физического интерфейса: при VXLAN разница составляет 50 байт. Если значение выставлено неверно, соединение устанавливается, а крупные пакеты пропадают, и симптом выглядит как проблема приложения.

В конфигурациях Cilium внутри пода иногда появляются дополнительные интерфейсы и маршруты, а часть трафика обрабатывается eBPF до сетевого стека. Если в поде нет утилит ip и route (distroless, scratch-образы), применяйте эфемерный контейнер или nsenter с ноды.

Как зайти в network namespace пода с ноды

Порядок такой: получить container ID, вытащить PID, войти в netns.

crictl ps
crictl inspect <container-id> | grep -i pid
nsenter -t <pid> -n ip route
nsenter -t <pid> -n ip neigh

Альтернатива без доступа к crictl и root на ноде это отладочный контейнер с набором сетевых утилит:

kubectl debug -it app-7d9f8b6c5d-xk2lp --image=nicolaka/netshoot --target=app -- ip route

netshoot содержит ip, tcpdump, traceroute, dig и conntrack. nsenter требует root на ноде и доступа к PID namespace хоста, поэтому в managed-кластерах (EKS, GKE, AKS) он может быть недоступен: там остаются kubectl debug и логи сетевого плагина.

Таблица маршрутов на ноде: как CNI формирует маршруты к podCIDR

Базовый набор команд для ноды:

ip route show
ip route show table all
ip neigh show

В типичном кластере видны три группы записей: маршруты к podCIDR других нод, маршрут к локальному podCIDR через bridge или eBPF и маршруты к внешним сетям. Дальше различия определяет плагин.

Calico: BGP-маршруты и режим без инкапсуляции

Calico по умолчанию распространяет podCIDR между нодами по BGP: нода анонсирует свой блок, Felix программирует ядро, а записи приходят с протоколом bird. Пример строки в таблице маршрутов:

10.244.1.0/26 via 192.168.1.11 dev eth0 proto bird

При включённой инкапсуляции IPIP или VXLAN появляется туннельный интерфейс tunl0 либо vxlan.calico, и маршруты указывают на него. Команды для проверки:

ip route | grep bird
calicoctl node status
kubectl get ippools -o yaml

Сверьте CIDR в IPPool с pod-network-cidr, заданным при kubeadm init. Дефолт Tigera Operator с ним не совпадает, и это одна из частых причин ситуации, когда маршруты есть, а трафик не доходит. Установка Calico через Tigera Operator и правка CIDR в custom-resources.yaml разобраны в материале про развёртывание кластера kubeadm на Ubuntu 22.04.

Flannel: VXLAN-туннели и интерфейс flannel.1

Flannel в режиме VXLAN создаёт на каждой ноде интерфейс flannel.1 и записи FDB для MAC-адресов удалённых нод. Маршруты к чужим podCIDR ведут через flannel.1 с флагом onlink:

10.244.1.0/24 via 10.244.1.0 dev flannel.1 onlink
ip -d link show flannel.1
bridge fdb show dev flannel.1
ip route | grep flannel

VXLAN добавляет к каждому пакету 50 байт. Если MTU туннеля не согласован с MTU физической сети, мелкие пакеты проходят, а крупные теряются: типичный симптом, когда ping работает, а curl зависает. Проверка размера без фрагментации выполняется командой ping -M do -s 1400 с адресом пода.

Cilium: eBPF вместо iptables и таблиц маршрутов

Cilium переносит балансировку и политики в eBPF-программы, привязанные к сетевым интерфейсам, и может полностью заменить kube-proxy. В режиме tunneling пакеты инкапсулируются на уровне eBPF, и таблица маршрутов ноды остаётся почти пустой. В режиме native routing маршруты к podCIDR появляются в обычной таблице. Команды для проверки:

cilium status
cilium service list
cilium bpf lb list
bpftool prog show

Отсутствие правил KUBE-* в iptables при включённом kube-proxy replacement ожидаемо. Для трассировки в этом случае используется cilium monitor, потому что часть пакетов обрабатывается до сетевого стека.

kube-proxy, iptables и eBPF: как обрабатывается сервисный трафик

kube-proxy в режиме iptables создаёт цепочки KUBE-SERVICES, KUBE-SVC-<hash> и KUBE-SEP-<hash>. Первая отвечает за выбор сервиса, вторая распределяет запросы между endpoints, третья делает DNAT. Посмотреть цепочки можно так:

iptables-save | grep KUBE-SERVICES
iptables -t nat -L KUBE-SERVICES -n -v
iptables -t nat -L KUBE-SVC-XXXX -n -v

В режиме IPVS правила хранятся не в iptables, а в таблицах балансировщика ядра:

ipvsadm -Ln
ipvsadm -Ln --stats

С версии Kubernetes 1.29 у kube-proxy появился режим nftables, и в поздних версиях он активно развивается. В этом режиме цепочек KUBE-* в iptables нет, правила смотрятся через nft list ruleset. Перед диагностикой уточните режим и состояние подов kube-proxy:

kubectl -n kube-system get pods -l k8s-app=kube-proxy -o wide
kubectl -n kube-system logs <kube-proxy-pod> | grep -i -E 'proxy mode|Using'

Результат DNAT хранится в conntrack, поэтому состояние соединения с сервисом видно в таблице отслеживания:

conntrack -L | grep 10.96.0.10

Очистка conntrack -F разрывает активные соединения, на проде её выполняют только при полном понимании последствий. Если сервисы публикуются наружу через Ingress или Gateway API, добавляется ещё один уровень маршрутизации; практические сценарии с манифестами собраны в материале про маршрутизацию между сервисами в Kubernetes.

Как отличить проблему маршрутизации от проблемы kube-proxy

  1. Проверьте podIP напрямую (ping, curl или nc по нужному порту). Не работает - смотрите CNI, маршруты и туннель.
  2. podIP работает, ClusterIP нет - смотрите endpoints: kubectl get endpoints <svc>. Пустой список указывает на селектор или готовность подов, а не на сеть.
  3. Убедитесь, что kube-proxy запущен на нужной ноде: kubectl -n kube-system get pods -l k8s-app=kube-proxy -o wide.
  4. Сравните iptables-save (или nft list ruleset) на ноде-источнике и на ноде, где живёт backend. В цепочках KUBE-SVC-* должны быть актуальные podIP.
  5. Проверьте сетевые политики: NetworkPolicy отбрасывает пакеты до балансировки, и симптом совпадает с поломкой маршрутов.

Практическая диагностика: команды для локализации сбоя связности

Последовательность от самого дешёвого шага к самому глубокому:

  1. Получить podIP и ноду: kubectl get pods -o wide -n <namespace>.
  2. Проверить маршруты и адрес внутри пода: kubectl exec -it <pod> -- ip route и kubectl exec -it <pod> -- ip addr.
  3. На ноде посмотреть маршруты, соседей и конкретный путь до podIP: ip route show, ip neigh show, ip route get <podIP>.
  4. Снять tcpdump одновременно на двух нодах: tcpdump -i any -n host <podIP> -c 20. Так видно, доходит ли пакет до ноды-получателя.
  5. Пройти маршрут из пода, если есть утилита: traceroute -n -T -p <port> <podIP>.
  6. Проверить состояние соединения: conntrack -L | grep <podIP>.
  7. Для подов без утилит поднять netshoot через kubectl debug.
  8. Для CNI-специфичной диагностики использовать cilium monitor, calicoctl node status и логи flannel.
kubectl get pods -o wide
kubectl exec -it app-7d9f8b6c5d-xk2lp -- ip route
ip route get 10.244.2.7
tcpdump -i any -n host 10.244.2.7 -c 20
conntrack -L | grep 10.244.2.7

tcpdump на veth может не показать пакет, если его обработала eBPF-программа до сетевого стека. В Cilium в такой ситуации используйте cilium monitor, в Calico - calicoctl node status и логи Felix. Асимметричный маршрут, когда ответ уходит через другой интерфейс, разбирается отдельно в материале про профиль маршрутизации в Kubernetes. Расширенные сценарии с несколькими интерфейсами, Ingress и Service Mesh собраны в руководстве по управлению и маршрутизации сетевого трафика в Kubernetes.

Типовые симптомы и что они означают

СимптомВероятная причинаЧто проверить
ping до podIP не проходит, ping до IP ноды работаетМаршруты CNI к podCIDR или проблема в туннелеip route show на обеих нодах, ip neigh, статус агента CNI
ping проходит, TCP-соединение зависает на крупных ответахMTU и фрагментация, особенно при VXLAN и IPIPip link show, ping -M do -s 1400 до podIP
podIP доступен, ClusterIP нетkube-proxy, цепочки iptables, IPVS или eBPF-карты балансировкиkubectl get endpoints, iptables-save | grep KUBE, ipvsadm -Ln
Работает с одной ноды, не работает с другойАсимметричная маршрутизация, разные параметры CNI, закрытые портыip route show table all на обеих нодах, firewall, NetworkPolicy
Связность есть после старта и пропадает через времяПереполнение таблицы conntrack, устаревшие записи FDBconntrack -C, dmesg | grep conntrack, bridge fdb show

Таблица описывает частые сочетания, но поведение зависит от версии CNI и режима kube-proxy, поэтому вывод команд важнее самого симптома.

Безопасные практики при изменениях в рабочем кластере

  • Перед правкой правил или маршрутов снимите дамп: iptables-save > backup.rules, ip route show > routes.txt, ip -6 route show >> routes.txt. С этим набором состояние восстанавливается предсказуемо.
  • Не запускайте conntrack -F на проде без причины: команда разрывает активные соединения.
  • Не меняйте podCIDR на живом кластере. Смена блока требует пересоздания кластера или нод, потому что адреса уже закреплены за подами и маршрутами.
  • Перед работами на ноде выводите её из балансировки: kubectl drain <node> --ignore-daemonsets --delete-emptydir-data, после работ возвращайте командой kubectl uncordon.
  • Проверяйте изменения на staging с теми же версиями Kubernetes, CNI и режимом kube-proxy: различия в режиме балансировки меняют и команды, и вывод.
  • Сравнивайте ip route show и iptables-save до и после работ: расхождение между нодами видно быстрее, чем формулируется гипотеза о причине.

Ошибки конфигурации, которые ломают маршрутизацию

  1. Несовпадение podCIDR в kubeadm init и в custom-resources.yaml Calico. Дефолтный CIDR Tigera Operator отличается от заданного, и маршруты указывают в никуда. Проверка: kubectl get ippools -o yaml.
  2. Отключённый ip_forward или незагруженный br_netfilter. Пакеты не проходят между интерфейсами ноды. Проверка: sysctl net.ipv4.ip_forward и lsmod | egrep overlay|br_netfilter.
  3. Разные cgroup driver у kubelet и containerd. При установке containerd значение SystemdCgroup = false заменяется на SystemdCgroup = true, иначе кластер не инициализируется.
  4. Swap отключён не полностью. kubelet может не пройти health-check за отведённые 4 минуты и завершиться с ошибкой unable to load bootstrap kubeconfig.
  5. Несколько сетевых интерфейсов на ноде и отсутствие флага --apiserver-advertise-address при kubeadm init. kubeadm выберет не тот интерфейс, и часть маршрутов будет вести в недоступную сеть.
  6. Закрытые порты между нодами: kubelet API, etcd client и peer, scheduler и controller-manager на control-plane, NodePort на worker-нодах. Трафик молча не доходит, а причина видна только в логах агентов.
  7. Несогласованный MTU в оверлее. Мелкие пакеты доходят, крупные теряются, и симптом легко спутать с ошибкой приложения.

Пункты 1, 3, 4, 5 и 6 воспроизводятся на этапе подготовки нод и установки кластера, поэтому их проверяют до поиска причины внутри CNI. Пошаговый чек-лист с командами приведён в статье про развёртывание кластера kubeadm на Ubuntu 22.04.

Внутренние механизмы CNI, kube-proxy и eBPF заметно меняются от версии к версии: режим nftables у kube-proxy, kube-proxy replacement в Cilium, параметры MTU и инкапсуляции. Сверяйте команды с документацией своей версии Kubernetes и версии CNI-плагина, а выводы проверяйте на тестовом кластере с той же конфигурацией.

Начните с двух команд на проблемной ноде: ip route get <podIP> показывает, каким интерфейсом ядро отправит пакет, а kubectl get pods -o wide даёт пару podIP и ноды для сверки. Маршрут ведёт в туннель - проверяйте FDB и MTU. Маршрут ведёт в bridge - проверяйте ip_forward и правила CNI. podIP работает, а ClusterIP нет - переходите к endpoints и цепочкам kube-proxy. Такой порядок сужает область поиска за несколько минут.

Поделиться:
Сохранить гайд? В закладки браузера