Отказоустойчивый Kubernetes на BGP. Часть 1: схема и план

Это первая статья серии о том, как с нуля собрать отказоустойчивый кластер Kubernetes, в котором и API-сервер, и сервисы типа LoadBalancer публикуются по BGP. Такой кластер у меня работает на виртуальных машинах Hyper-V, и на нём живёт, в том числе, этот блог.

Серия получилась практической: в каждой части — команды, конфигурация и то, на что я успел наступить.

Серия «Отказоустойчивый 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. Проверка отказоустойчивости и разбор полётов

Что получится в итоге

  • три мастера (control plane) и пять рабочих узлов;
  • адрес API-сервера (VIP) висит одновременно на всех трёх мастерах, и каждый из них анонсирует его роутеру по BGP — это делает kube-vip;
  • адреса сервисов LoadBalancer выдаёт и анонсирует Calico — отдельный MetalLB не нужен;
  • роутер (Linux + FRR) принимает маршруты и раскладывает трафик между узлами по ECMP;
  • CNI — Calico с датаплейном eBPF, kube-proxy в кластере нет;
  • входящий трафик принимает Traefik, который стоит за адресом из BGP.

Никаких виртуальных адресов «на интерфейсе одного из узлов» и никакого ARP: адрес — это просто маршрут, который есть у роутера, пока есть хотя бы один живой узел, способный обслужить запрос.


Зачем BGP, если есть ARP

У обоих «классических» способов опубликовать адрес — kube-vip в ARP-режиме и MetalLB в L2-режиме — одна и та же особенность: в каждый момент адрес принадлежит одному узлу.

  • В ARP-режиме kube-vip узлы выбирают лидера через lease в Kubernetes API, и лидер вешает VIP на свой интерфейс и рассылает gratuitous ARP. Пока лидер жив — весь трафик идёт на него. Если он умер, надо дождаться окончания lease, выбрать нового и обновить ARP-таблицы соседей.
  • MetalLB в L2-режиме работает так же: один узел отвечает на ARP за адрес сервиса и принимает на себя весь трафик к нему.

Оба способа требуют, чтобы клиенты и узлы находились в одном L2-сегменте.

При BGP картина другая:

  • анонсируют все узлы сразу, роутер видит несколько равноценных маршрутов к одному адресу и раскладывает по ним трафик (ECMP);
  • отказ узла — это исчезновение его маршрута: либо сразу (сессия оборвалась), либо по hold-таймеру BGP, либо потому что узел сам снял анонс;
  • адресу не нужен L2-сегмент: он может жить в подсети, которой физически не существует.

Цена вопроса

Честно о минусах:

  1. Нужен роутер, который умеет BGP. У меня это обычный Linux с FRR, но подойдут и MikroTik, и pfSense/OPNsense с плагином FRR, и «взрослое» железо.
  2. Подсеть для адресов должна быть чисто маршрутизируемой. В BGP-режиме kube-vip не отвечает на ARP, поэтому, если VIP лежит в той же подсети, что и узлы, соседи будут искать его широковещанием и не найдут. Я завёл отдельную 192.168.20.0/24, у которой нет L2-сегмента.
  3. Трафик внутри подсети узлов получается несимметричным (узел идёт к VIP через роутер, а ответ возвращается напрямую). Об этом отдельная часть: без правки файрвола на роутере схема просто не заработает.
  4. Роутер стал частью control plane. Рабочие узлы ходят к API через него, поэтому перезагрузка роутера роняет их в NotReady. Раньше, когда VIP был в той же подсети, роутер в этом пути не участвовал.

Схема

                     интернет / клиенты
                              │
                ┌─────────────┴──────────────┐
                │ router.example.com         │
                │ Linux + FRR, AS 65010      │
                │ LAN: 192.168.10.1/24       │
                └─────────────┬──────────────┘
                              │ сеть узлов 192.168.10.0/24
       ┌──────────────────────┼──────────────────────┐
       │                      │                      │
 k8s-master-1..3        k8s-node-1..5          (маршрутизируемая
 192.168.10.17-19       192.168.10.20-24       подсеть без L2)
 kube-vip, AS 65020     Calico (BIRD), AS 65030  192.168.20.0/24
 анонсирует             анонсирует /32 адресов   ├─ .1       VIP API
 192.168.20.1/32        LoadBalancer             └─ .64/26   пул LoadBalancer

Сессий BGP восемь: по одной между роутером и каждым узлом. Мастера и рабочие узлы между собой по BGP не общаются.

Адресный план

Что Значение
Подсеть узлов 192.168.10.0/24, роутер 192.168.10.1
Мастера k8s-master-1…3 192.168.10.17–19
Рабочие узлы k8s-node-1…5 192.168.10.20–24
VIP API-сервера 192.168.20.1 = control.k8s.example.com
Пул адресов LoadBalancer 192.168.20.64/26
Сеть подов 172.20.0.0/16, блоки по /26
Сеть сервисов 10.96.0.0/12

Пул LoadBalancer начинается с .64, чтобы не пересекаться с VIP, который лежит в той же подсети.

Номера AS

Кто AS
Роутер 65010
Мастера (kube-vip) 65020
Рабочие узлы (Calico) 65030

Все номера из приватного диапазона 64512–65534, и они разные — это сознательное решение:

  • роутер и узлы в разных AS — значит, сессии eBGP; при одинаковых AS это был бы iBGP, где ECMP включается отдельной командой (maximum-paths ibgp), о которой легко забыть и остаться без балансировки;
  • у мастеров и воркеров разные AS, потому что у них разные роли и политику к ним удобнее применять раздельно; к тому же, одинаковая AS у узла и роутера делала бы невозможным передачу маршрутов обратно в кластер: узел отбросил бы маршрут, увидев в AS_PATH собственный номер.

Пример в документации kube-vip использует одинаковые значения только потому, что там оставлены умолчания.


Версии

Компонент Версия
ОС Ubuntu 24.04
Kubernetes (kubeadm) 1.36
containerd 2.x из репозитория Docker
kube-vip 1.2.4
Calico 3.32 (через tigera-operator)
FRR на роутере 8.4
Traefik 3.7

Версии — на момент написания; принцип от них не зависит, но имена полей и флагов могут отличаться.


План серии

  1. Схема и план — эта статья.
  2. Готовим узлы Ubuntu 24.04 — сеть, containerd, kubeadm, время, мелочи, о которых лучше знать заранее.
  3. BGP на роутере (FRR) — конфигурация, фильтры, ECMP и правило файрвола для «разворота» трафика.
  4. kube-vip в режиме BGP и первый мастер — статический под, kubeadm init, присоединение остальных мастеров.
  5. Calico с eBPF, рабочие узлы — CNI без kube-proxy.
  6. Адреса LoadBalancer из Calico по BGP — пул, пиры, аннотация и грабли.
  7. Traefik за BGP — externalTrafficPolicy: Local и почему это важно.
  8. Проверка отказоустойчивости и разбор полётов — ломаем узлы, смотрим на роутер, шпаргалка по диагностике.

Что понадобится

  • восемь (или меньше — три мастера и хотя бы один воркер) машин с Ubuntu 24.04: у меня виртуальные, по 4 vCPU, 6 ГБ памяти и 60 ГБ диска;
  • роутер с Linux (или иным BGP-маршрутизатором), стоящий на пути между узлами и остальными сетями;
  • свободная подсеть, которой нет ни у кого в сети: в ней будут жить VIP и адреса сервисов;
  • DNS-имя для API (control.k8s.example.com в моём случае).

Идём дальше: в части 2 подготовим узлы.

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

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