Отказоустойчивый Kubernetes на BGP. Часть 3: BGP на роутере (FRR)

В третьей части настраиваем сторону роутера. Кластера ещё нет, но роутер можно подготовить заранее: он заранее знает всех будущих соседей и будет ждать, пока они появятся.

Серия «Отказоустойчивый Kubernetes на BGP»

  1. Схема и план
  2. Готовим узлы Ubuntu 24.04
  3. BGP на роутере (FRR) (эта статья)
  4. kube-vip в режиме BGP и первый мастер
  5. Calico с eBPF и рабочие узлы
  6. Адреса LoadBalancer из Calico по BGP
  7. Traefik за BGP и externalTrafficPolicy Local
  8. Проверка отказоустойчивости и разбор полётов

В моём случае роутер — обычная машина с Ubuntu 24.04, на которой уже работают маршрутизация и файрвол (iptables). Сеть узлов подключена к его интерфейсу eth1 (адрес 192.168.10.1/24). Если у вас другой роутер — идеи те же, меняется только синтаксис.


Установка FRR

sudo apt-get install -y frr frr-pythontools
sudo sed -i 's/^bgpd=no/bgpd=yes/' /etc/frr/daemons
sudo systemctl enable --now frr

Из всех демонов FRR по умолчанию включён только zebra — он разговаривает с ядром о маршрутах. Строка bgpd=yes включает собственно BGP. Пакет frr-pythontools нужен ради vtysh -b: эта команда применяет frr.conf к работающим демонам без перезапуска службы.


Конфигурация

Вот весь /etc/frr/frr.conf:

frr version 8.4
frr defaults traditional
hostname router.example.com
log syslog informational
service integrated-vtysh-config
!
router bgp 65010
 bgp router-id 192.168.10.1
 no bgp default ipv4-unicast
 bgp graceful-restart
 !
 neighbor MASTERS peer-group
 neighbor MASTERS remote-as 65020
 neighbor MASTERS description Kubernetes control plane
 neighbor MASTERS timers 10 30
 !
 neighbor WORKERS peer-group
 neighbor WORKERS remote-as 65030
 neighbor WORKERS description Kubernetes workers
 neighbor WORKERS timers 10 30
 !
 neighbor 192.168.10.17 peer-group MASTERS
 neighbor 192.168.10.18 peer-group MASTERS
 neighbor 192.168.10.19 peer-group MASTERS
 !
 neighbor 192.168.10.20 peer-group WORKERS
 neighbor 192.168.10.21 peer-group WORKERS
 neighbor 192.168.10.22 peer-group WORKERS
 neighbor 192.168.10.23 peer-group WORKERS
 neighbor 192.168.10.24 peer-group WORKERS
 !
 address-family ipv4 unicast
  neighbor MASTERS activate
  neighbor MASTERS soft-reconfiguration inbound
  neighbor MASTERS route-map FROM-CLUSTER in
  neighbor MASTERS route-map NOTHING-OUT out
  neighbor WORKERS activate
  neighbor WORKERS soft-reconfiguration inbound
  neighbor WORKERS route-map FROM-CLUSTER in
  neighbor WORKERS route-map NOTHING-OUT out
  maximum-paths 8
 exit-address-family
!
ip prefix-list SERVICES seq 10 permit 192.168.20.0/24 le 32
!
route-map FROM-CLUSTER permit 10
 match ip address prefix-list SERVICES
!
route-map NOTHING-OUT deny 10
!
line vty

Применяем:

sudo install -o frr -g frr -m 640 frr.conf /etc/frr/frr.conf
sudo vtysh -b

Что здесь что значит

router bgp 65010 и bgp router-id — наша AS и идентификатор маршрутизатора.

no bgp default ipv4-unicast — соседи не получают обмен IPv4-маршрутами автоматически, каждый включается явно командой activate в блоке address-family. Защита от обмена маршрутами, которых вы не ждёте.

peer-group MASTERS и WORKERS — группы соседей. Мастера (AS 65020, kube-vip) и воркеры (AS 65030, Calico) играют разные роли, и политики к ним удобнее применять раздельно, а не повторять одни и те же строки восемь раз. Сами соседи — отдельными строками neighbor <адрес> peer-group .... Новый узел — одна строка.

timers 10 30 — keepalive 10 секунд, hold 30 секунд вместо стандартных 60/180. Hold-таймер — это максимальное время, за которое роутер обнаружит жёстко умершего соседа (выключили питание, кабель выдернули) и уберёт его маршруты. 180 секунд для API — слишком много.

maximum-paths 8 — включает ECMP. Один и тот же префикс приходит сразу от нескольких узлов: VIP — от трёх мастеров, а позже и адрес сервиса — от нескольких воркеров. Без этой строки роутер выберет один маршрут из равноценных, а остальные оставит про запас. Работает потому, что у нас eBGP: при совпадающих AS пришлось бы писать maximum-paths ibgp.

soft-reconfiguration inbound — роутер хранит принятые маршруты до фильтра. Это важно для диагностики (received-routes и filtered-routes, см. ниже), без неё эти команды выводят пустоту, и легко решить, что сосед ничего не присылает.

ip prefix-list SERVICES + route-map FROM-CLUSTER in — принимаем только 192.168.20.0/24 и подсети внутри неё. Это предохранитель: если Calico однажды начнёт рассказывать роутеру про сеть подов 172.20.0.0/16 (а в какой-то момент это у меня случилось), роутер отбросит эти маршруты, и они не попадут в ядро.

route-map NOTHING-OUT out — роутер кластеру не отдаёт ничего. У узлов есть маршрут по умолчанию через роутер, большего им не нужно.

Ловушка bgp ebgp-requires-policy

По умолчанию (при frr defaults traditional) включена bgp ebgp-requires-policy: eBGP-сосед без входящей и исходящей политики не обменивается маршрутами вообще. Мы это условие выполняем (route-map ... in/out у обеих групп), но обратная сторона такая: опечатка в имени route-map приведёт к тому, что все маршруты будут молча отброшены, без единой ошибки в логе. Если show bgp пуст, смотрим сюда первым делом.


Файрвол: BGP-порт

BGP работает по TCP-порту 179. Если у вас, как и у меня, политика INPUT — DROP, соседям надо его открыть:

sudo iptables -A INPUT -i eth1 -s 192.168.10.0/24 -p tcp --dport 179 -j ACCEPT

Правило нужно держать в сохраняемых правилах (iptables-persistent, netfilter-persistent save) — иначе после перезагрузки роутера сессии не поднимутся.


Самая неочевидная часть: разворот трафика внутри подсети узлов

Теперь о том, без чего вся схема просто не работает. Если вы пропустите этот раздел, с первого взгляда всё будет работать, но ни одно соединение с полезной нагрузкой не будет доходить.

Узлы живут в 192.168.10.0/24, а VIP — в 192.168.20.0/24, которой на канальном уровне не существует. Когда рабочий узел обращается к VIP, он, как и положено, отправляет пакет через шлюз — роутер. Роутер находит в таблице маршрутов 192.168.20.1/32 через мастера и отправляет пакет обратно в тот же интерфейс eth1.

Мастер получает пакет, формирует ответ и видит, что адресат (воркер) находится в его собственной подсети. Поэтому он отвечает напрямую, минуя роутер.

Получается, что роутер видит только половину разговора. Его conntrack создаёт запись по SYN, никогда не видит SYN-ACK и остаётся в состоянии SYN_SENT. Следующий же пакет, уже с данными, попадает под правило, которое есть почти в любом файрволе:

-A FORWARD -m conntrack --ctstate INVALID -j DROP

Выглядит это издевательски:

  • ping проходит;
  • TCP-рукопожатие проходит (ответ идёт мимо роутера и всё равно доходит);
  • а первый же пакет с данными пропадает и бесконечно ретранслируется;
  • в tcpdump на роутере SYN виден дважды (вход и выход), а пакет с данными — один раз.

Лечится правилом, которое разрешает разворот и стоит раньше проверки на INVALID:

sudo iptables -I FORWARD 1 -i eth1 -o eth1 -d 192.168.20.0/24 -j ACCEPT

Правило намеренно узкое: только разворот внутри подсети узлов в сторону подсети сервисов. Всем остальным клиентам оно не нужно: у них обратный путь идёт через роутер, трафик симметричен, и conntrack работает как обычно.

Обратите внимание, что я написал правило на всю подсеть 192.168.20.0/24, а не на один адрес VIP. Адреса LoadBalancer, которые появятся позже, оказываются в точно такой же ситуации, и тогда правило расширять не придётся.

Если файрволом на роутере управляет какой-нибудь агент или генератор правил (у меня так и есть), добавляйте правило через его механизм «пользовательских» правил в начале цепочки FORWARD: иначе при следующей генерации оно пропадёт.


Проверка

Пока ни одного кластерного узла нет, соседи будут в состоянии Active (роутер пытается подключиться, но на другой стороне никто не слушает). Для BGP это нормально.

sudo vtysh -c 'show bgp summary'

Здесь важна колонка State/PfxRcd: число — сессия жива, и столько префиксов принято; слово (Active, Connect, Idle) — сессии нет.

Когда поднимется кластер, для проверки пригодятся ещё несколько команд, и я их оставлю на потом:

sudo vtysh -c 'show bgp ipv4 unicast'                # что приняли
sudo vtysh -c 'show bgp ipv4 unicast 192.168.20.1'     # кто анонсирует конкретный адрес
ip route show 192.168.20.1                             # что попало в ядро
sudo vtysh -c 'show bgp ipv4 unicast neighbors 192.168.10.20 filtered-routes'   # что отбросили

В части 8 будет полная шпаргалка, а пока роутер готов: в части 4 поднимем VIP для API-сервера с помощью kube-vip и инициализируем первый мастер.

Оставить комментарий могут только зарегистрированные пользователи.

Войдите на сайт или зарегистрируйтесь, чтобы оставить комментарий.