Статическая маршрутизация в Kubernetes и Docker: настройка маршрутов контейнерных сетей | AdminWiki

Статическая маршрутизация в Kubernetes и Docker: настройка маршрутов контейнерных сетей

20 сентября 2026 12 мин. чтения
Содержание статьи

Статические маршруты в контейнерных сетях нужны реже, чем принято думать. Штатный кластер Kubernetes получает маршруты к pod-сетям от CNI-плагина, а Docker сам создаёт bridge docker0 и правила NAT в iptables. Руками таблицу маршрутов правят в четырёх ситуациях: кластер собран без overlay, два Docker-хоста нужно связать напрямую, появилась внешняя подсеть (NFS, Ceph, сеть резервного копирования), сторонний клиент переписал default route и контейнеры перестали видеть внешний мир.

Короткий ответ: маршрут к pod-сети добавляют на узле командой ip route add 10.244.1.0/24 via 192.168.1.11 dev eth0. Маршруты к service-сетям (ClusterIP) на хостах не прописывают: виртуальные IP обслуживает kube-proxy через iptables или IPVS, и статический маршрут в тот же диапазон создаёт петлю. Любая правка живёт до перезагрузки, пока не записана в systemd-networkd, netplan или /etc/network/interfaces, а ошибка в шлюзе или default route легко отрезает доступ к узлу по SSH.

Зачем нужны статические маршруты в контейнерных средах

Автоматика закрывает большинство задач. Демон Docker при старте создаёт bridge docker0, пару veth на каждый контейнер и правила MASQUERADE, поэтому контейнер выходит в интернет, но не видит контейнеры на соседнем хосте. Kubernetes идёт дальше: kube-controller-manager выдаёт каждому узлу собственный pod CIDR, CNI-плагин объявляет этот диапазон остальным узлам, а kube-proxy отвечает за виртуальные адреса сервисов.

Ручной маршрут нужен там, где автоматика не дотягивается: между Docker-хостами без CNI, к внешним подсетям хранилища, при отладке и при конфликте с программами, которые меняют default route.

ПараметрDockerKubernetes
Кто создаёт маршрутыдемон Docker: bridge, veth, iptablesCNI-плагин
Типовая подсеть172.17.0.0/16 (docker0)pod CIDR на узел, например 10.244.1.0/24
Связь между хостаминастраивают вручнуюBGP, VXLAN, host-gw, native routing
Service-сетинет такого понятияClusterIP через kube-proxy
Типичный ручной маршрутip route add 172.18.0.0/16 via 192.168.1.20 dev eth0ip route add 10.244.1.0/24 via 192.168.1.11 dev eth0

Чем отличается маршрутизация в Docker и Kubernetes

В Docker таблица маршрутов строится вокруг одного bridge docker0 с подсетью 172.17.0.0/16. Контейнер получает адрес из этой подсети, наружу трафик уходит через NAT хоста, обратный доступ организуют пробросом портов. Маршруты к контейнерным сетям других хостов Docker не создаёт: связность между двумя демонами настраивают руками либо переходят на overlay и macvlan. Механику veth-пар, MASQUERADE и DNAT разбирает статья Маршрутизация в Docker: bridge-сети, NAT и настройка маршрутов на практике.

В Kubernetes узел держит не один bridge, а набор маршрутов к pod-сетям. Узел получает pod CIDR, например 10.244.1.0/24, и CNI-плагин сообщает остальным узлам, что этот диапазон доступен через IP узла. Адреса сервисов (обычно 10.96.0.0/12) в таблице маршрутов хоста не появляются: ClusterIP живёт в правилах kube-proxy, которые подменяют адрес назначения на конкретный pod. Как это выглядит в таблицах, подробно показано в статье Маршрутизация в Kubernetes: таблицы маршрутов между подами и нодами.

Когда ручные маршруты оправданы, а когда опасны

Оправданы:

  • Кластер без overlay: Calico в режиме BGP или Flannel в режиме host-gw, узлы в одной L2/L3-сети.
  • Прямая связка Docker-хостов без overlay, когда нужна предсказуемая задержка.
  • Маршрут к внешней подсети: NFS, Ceph, сеть резервного копирования, соседняя стойка.
  • Временный маршрут для отладки, пока не найден корень проблемы.

Опасны:

  • Дублирование маршрута, который уже добавил CNI: получаете петлю и «мигающую» связность.
  • Пересекающиеся CIDR у двух узлов или двух bridge-сетей: пакеты уходят не туда.
  • Правка default route: ошибка в шлюзе отрезает узел целиком.
  • Маршрут в service CIDR: он конфликтует с правилами kube-proxy.

Перед первой правкой убедитесь, что есть консольный доступ (IPMI, iDRAC, KVM) или второй сетевой интерфейс.

Как добавить статический маршрут в Linux через ip route

Базовый инструмент это iproute2. Общий синтаксис: ip route add <сеть>/<маска> via <шлюз> dev <интерфейс> metric <число>. Параметр via задаёт шлюз, dev фиксирует интерфейс, metric управляет приоритетом: чем меньше число, тем раньше маршрут попадёт в выбор.

Временные маршруты: ip route add и ip route del

ip route add 10.244.1.0/24 via 192.168.1.11 dev eth0
ip route show
ip route show table all
ip route get 10.244.1.5
ip route del 10.244.1.0/24

Вывод ip route get 10.244.1.5 показывает, какой маршрут реально сработает: например, «10.244.1.5 via 192.168.1.11 dev eth0 src 192.168.1.10». Если ядро отвечает ошибкой Network is unreachable, шлюз недоступен напрямую и маршрут не будет установлен. После добавления проверьте связность: ping 10.244.1.1.

Для IPv6 применяют тот же синтаксис с флагом: ip -6 route add fd00:10:244:1::/64 via fd00:192:168:1::11 dev eth0. Чтобы откатить временную правку, удалите маршрут той же сети: ip route del 10.244.1.0/24.

Постоянные маршруты: systemd-networkd, netplan, ifupdown

Команды ip route add действуют до перезагрузки. Для постоянного маршрута создайте файл /etc/systemd/network/10-static-route.network:

[Match]
Name=eth0

[Network]
DHCP=yes

[Route]
Destination=10.244.1.0/24
Gateway=192.168.1.11

Примените конфигурацию: systemctl restart systemd-networkd, затем проверьте networkctl status eth0 и ip route show.

В netplan маршрут описывают в /etc/netplan/01-netcfg.yaml:

network:
  version: 2
  ethernets:
    eth0:
      dhcp4: true
      routes:
        - to: 10.244.1.0/24
          via: 192.168.1.11

После правки выполните netplan apply. Ошибка в отступах YAML ломает конфигурацию целиком, поэтому применяйте изменения в момент, когда у вас есть консольный доступ.

В системах с ifupdown маршрут добавляют строкой в /etc/network/interfaces: up ip route add 10.244.1.0/24 via 192.168.1.11. Правка заработает после ifdown eth0 && ifup eth0.

Настройка маршрутов к pod-сетям и service-сетям в Kubernetes

В работающем кластере маршруты к pod-сетям уже есть. Полезно понять, кто их создал, до того как добавлять свои.

Как узнать pod CIDR и service CIDR

kubectl get nodes -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.podCIDR}{"\n"}{end}'
kubectl cluster-info dump | grep -i service-cluster-ip-range
kubectl cluster-info dump | grep -i -- --cluster-cidr

Диапазоны pod-сетей раздаёт kube-controller-manager флагами --allocate-node-cidrs и --cluster-cidr: первый указывает, следует ли выделять CIDR для pod'ов, второй задаёт сам диапазон и используется только при --allocate-node-cidrs=true. Cilium в своей документации отдельно подчёркивает, что kube-controller-manager важно запускать с --allocate-node-cidrs, чтобы Kubernetes выделял диапазоны PodCIDR (Kubernetes Host Scope — Cilium). Описание флагов приведено в справочнике kube-controller-manager | Kubernetes. Диапазон сервисов задаётся на стороне kube-apiserver флагом --service-cluster-ip-range; в использованных материалах его точное описание не подтверждено, поэтому сверяйтесь с reference для вашей версии Kubernetes. Типовая картина: pod CIDR 10.244.0.0/16, по 10.244.N.0/24 на узел, service CIDR 10.96.0.0/12. Менять эти диапазоны после установки кластера нельзя без пересоздания или миграции, поэтому проверяйте их до первых правок.

Взаимодействие с CNI-плагинами: Calico, Flannel, Cilium

ПлагинКак строит маршрутыЧто важно при ручной правке
CalicoBGP между узлами; IPIP или VXLAN как туннелив режиме BGP маршруты объявляются автоматически, лишний статический даст конфликт
FlannelVXLAN по умолчанию, host-gw как альтернативав host-gw маршруты добавляются напрямую, пересечение с ними ломает связность
Ciliumnative routing или туннель, обработка через eBPFправки в ip route могут не влиять на пути, которые считает eBPF

Если CNI работает штатно, статические маршруты не нужны. Пример для кластера из двух узлов, pod CIDR 10.244.0.0/16, узлы 192.168.1.10 и 192.168.1.11:

# узел 192.168.1.10
ip route add 10.244.1.0/24 via 192.168.1.11 dev eth0

# узел 192.168.1.11
ip route add 10.244.0.0/24 via 192.168.1.10 dev eth0

Дополнительный маршрут к тому же pod CIDR через неверный узел превращается в чёрную дыру, поэтому сверяйте результат с выводом ip route show и с диапазонами, которые публикует плагин. Туннельные режимы уменьшают MTU: в примере конфигурации Flannel в Kubernetes задан FLANNEL_MTU=1450, а в руководстве по Flannel VXLAN все интерфейсы Flannel VXLAN устанавливаются на MTU 1450, и это значение считается типовым для большинства систем Flannel (Как pod в Kubernetes получает IP-адрес). Точную величину накладных расходов VXLAN в байтах в использованных материалах подтвердить не удалось, поэтому при нестандартном внешнем MTU проверяйте значение по документации вашего плагина, иначе крупные пакеты молча теряются.

Статические маршруты на хостах Docker

Задачи две: дать контейнерам доступ во внешние подсети и связать контейнеры, живущие на разных хостах. Общая схема сетей Docker и типовых сбоев разобрана в статье Сетевые модели в Docker и Kubernetes.

Маршрут к внешней подсети для контейнеров Docker

В bridge-сети контейнер выходит наружу через NAT хоста. Маршрут к внешней подсети нужен самому хосту, а контейнер получит доступ после подмены адреса источника на IP хоста:

ip route add 10.0.0.0/8 via 192.168.1.1 dev eth0
docker run --rm alpine ping -c 2 10.0.0.1

Если нужен прямой доступ без NAT, маршрут к подсети контейнеров прописывают на внешнем маршрутизаторе и исключают эту подсеть из masquerade. Для фильтрации трафика контейнеров Docker предоставляет цепочку DOCKER-USER: она обрабатывается раньше цепочки DOCKER, и правила в неё добавляют так, чтобы они выполнялись до правил Docker, а завершающий RETURN возвращал обработку к стандартному порядку (Docker with nftables | Docker Docs). Готовые конфигурации адресации и прямой маршрутизации собраны в статье Продвинутая маршрутизация в Docker и Compose.

Связность контейнеров между хостами без overlay

Возьмём два хоста: 192.168.1.10 с сетью контейнеров 172.17.0.0/16 и 192.168.1.20 с сетью 172.18.0.0/16.

# на обоих хостах
sysctl -w net.ipv4.ip_forward=1

# хост 192.168.1.10
ip route add 172.18.0.0/16 via 192.168.1.20 dev eth0

# хост 192.168.1.20
ip route add 172.17.0.0/16 via 192.168.1.10 dev eth0

Сохраните параметр ядра в /etc/sysctl.d/99-docker-routes.conf строкой net.ipv4.ip_forward=1, иначе после перезагрузки маршруты останутся, а пересылка выключится. Проверьте фильтрацию: iptables -L FORWARD -n -v. Docker добавляет свои правила, но при политике DROP в цепочке FORWARD трафик между bridge-сетями не пройдёт. Проверка связности: docker exec -it container1 ping -c 3 172.18.0.2.

Подсети bridge-сетей не должны пересекаться между хостами: задавайте их явно через docker network create --subnet. В сетях с rp_filter асимметричные пути между хостами иногда блокируются, тогда проверяют net.ipv4.conf.all.rp_filter. Отдельный вариант прямой связности это macvlan или ipvlan, но они требуют поддержки со стороны коммутатора и конфликтуют с DHCP, поэтому адреса назначают статически.

Диагностика маршрутов и проверка связности подов

ip route show и ip route get: чтение таблицы маршрутов

ip route show
ip route get 10.244.1.5
traceroute -n 10.244.1.5
tcpdump -ni eth0 host 10.244.1.5

В выводе ip route show строки вида 10.244.1.0/24 via 192.168.1.11 dev eth0 означают маршрут через соседний узел, а 10.244.0.0/24 dev cni0 это локальная pod-сеть. Если нужного маршрута нет, пакет уйдёт через default gateway и, скорее всего, потеряется. Команда ip route get отвечает на вопрос «куда уйдёт конкретный пакет» и показывает выбранный исходный адрес.

Проверка связности подов через kubectl exec и ping

kubectl run test --image=alpine --restart=Never -- sleep 3600
kubectl get pods -o wide
kubectl exec -it test -- ip route show
kubectl exec -it test -- ping -c 3 10.244.1.5
kubectl exec -it test -- nc -zv 10.244.1.5 80

Сначала получите адрес и узел целевого пода через kubectl get pods -o wide, затем проверьте маршрут внутри тестового пода и только после этого смотрите таблицу маршрутов на узле. Если ICMP не проходит, проверьте тот же адрес утилитой nc или curl: некоторые CNI и NetworkPolicy блокируют ICMP, а TCP при этом работает. Сетевые политики и команды диагностики для межузлового трафика разбирает материал таблицы маршрутизации контейнеров и диагностика сетевых проблем.

Влияние режимов перехвата трафика на маршрутизацию контейнеров

Клиенты Xray и Sing-box работают в одном из трёх режимов: System Proxy, TUN или Mixed. Выбор режима меняет таблицу маршрутов хоста, и контейнеры чувствуют это первыми. Разбор режимов с примерами приведён в материале Режимы работы Xray и Sing-box: System Proxy, TUN или Mixed?

Как TUN меняет таблицу маршрутизации

В режиме TUN клиент создаёт виртуальный адаптер и меняет таблицу маршрутизации, добавляя запись вида default dev happ-xray metric 1. Metric 1 делает маршрут приоритетным, поэтому весь исходящий IP-трафик системы уходит в туннель. Перехватывается 100% трафика: браузеры, фоновые демоны, консоль, Docker, DNS-запросы и системные службы. Контейнер отправляет пакеты на default gateway хоста, попадает в тот же туннель, и если правила разделения трафика не покрывают подсеть 172.17.0.0/16, связность рвётся. Симптом описан прямо: при неверном выборе режима консольные утилиты и Docker перестают видеть внешнюю сеть.

Что делать: добавить исключающий маршрут для контейнерных подсетей, например ip route add 172.17.0.0/16 dev docker0, либо развести трафик через policy routing (ip rule и отдельная таблица), либо вернуться в режим, который не трогает default route, если прокси нужен только браузеру.

System Proxy и Docker: почему контейнеры не используют прокси

В режиме System Proxy настройки читают только приложения, которые ориентируются на настройки ОС: браузеры, Telegram, часть десктопных клиентов. Контейнеры Docker, базы данных, IDE и консольные утилиты в этот список не входят, поэтому прокси для них задают явно: docker run -e HTTP_PROXY=http://proxy:8080 -e HTTPS_PROXY=http://proxy:8080 -e NO_PROXY=localhost,127.0.0.1. В Kubernetes те же переменные передают через env в спецификации пода. Не все приложения уважают эти переменные, так что после настройки проверьте фактический выход в сеть.

Предупреждения и безопасное применение изменений

Порядок действий перед правкой: сохраните текущее состояние командой ip route show > /root/routes-backup.txt, убедитесь в наличии консольного доступа к узлу и добавьте страховку в виде отложенного отката, например at now + 5 minutes с командой удаления нового маршрута. Для постоянных маршрутов правьте конфигурационные файлы, а не init-скрипты.

Как откатить изменения маршрутов

ip route del 10.244.1.0/24
ip route show | diff - /root/routes-backup.txt

Если маршрут добавлен командой, его снимают той же сетью. Если правка внесена в файл systemd-networkd или netplan, верните предыдущую версию файла и перезапустите службу: systemctl restart systemd-networkd или netplan apply. Маршруты, которые создал CNI-плагин или демон Docker, удалять вручную не стоит: демон вернёт их при следующем запуске, а в промежутке вы получите разрыв связности.

Проверка после перезагрузки

ip route show
sysctl net.ipv4.ip_forward
kubectl get nodes
kubectl get pods -o wide --all-namespaces

Дальше повторите ping между подами на разных узлах. Если маршрут исчез, проверьте, применилась ли конфигурация сети и не перезаписал ли её CNI при старте: некоторые плагины при старте агента на узле полностью пересобирают таблицу маршрутов.

Актуальность команд для разных версий Kubernetes и Docker

Синтаксис iproute2 стабилен годами, а окружение вокруг него меняется: набор флагов kube-apiserver и kube-controller-manager, режимы работы CNI, версия спецификации CNI. Перед правкой сверяйтесь с release notes вашей версии Kubernetes и документацией плагина. В Docker поведение iptables и параметры демона задают в daemon.json: установка ключей iptables или ip6tables в false предотвращает создание Docker'ом большинства его правил iptables или nftables (Packet filtering and firewalls | Docker Docs), а флаг --ip6tables включает правила IPv6 iptables (только если включён experimental). Docker Engine 20.10 содержит обновлённые версии Docker Compose, Docker Scan, containerd и ряд исправлений (Docker Engine 20.10 release notes | Docker Docs); какие именно ветки считать актуальными сегодня, в использованных материалах не подтверждено, поэтому ориентируйтесь на актуальные release notes Docker. Проверять версии удобно командами kubectl version, docker version и ip -V: расхождение версий клиента и сервера часто объясняет, почему команда из старой инструкции не срабатывает.

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