Точне налаштування маршрутизації для MetalLB у режимі L2

Точне налаштування маршрутизації для MetalLB у режимі L2
Нещодавно я зіткнувся з дуже нестандартним завданням налаштування маршрутищації для MetalLB. Усе нічого, т.к. зазвичай для MetalLB не потрібно ніяких додаткових дій, але в нашому випадку є досить великий кластер з досить нехитрою конфігурацією мережі.

У цій статті я розповім як налаштувати source-based та policy-based routing для зовнішньої мережі вашого кластера.

Я не буду докладно зупинятися на установці та налаштуванні MetalLB, тому що припускаю ви вже маєте деякий досвід. Пропоную одразу перейти до справи, а саме до налаштування маршрутизації. Тож ми маємо чотири кейси:

Випадок 1: Коли налаштування не потрібне

Розберемо простий кейс.

Точне налаштування маршрутизації для MetalLB у режимі L2

Додаткове налаштування маршрутизації не потрібно, коли адреси, що видаються MetalLB, знаходяться в тій же підмережі, що й адреси ваших нод.

Наприклад, ви маєте підсіти 192.168.1.0/24, в ній є маршрутизатор 192.168.1.1, і ваші ноди отримують адреси: 192.168.1.10-30тоді для MetalLB ви можете налаштувати рейндж 192.168.1.100-120 і бути впевненим, що вони будуть працювати без будь-якої додаткової настройки.

Чому так? Тому що ваші ноди вже мають налаштовані маршрути:

# ip route
default via 192.168.1.1 dev eth0 onlink 
192.168.1.0/24 dev eth0 proto kernel scope link src 192.168.1.10

І адреси з того ж рейджу будуть використовувати їх без будь-яких додаткових рухів тіла.

Випадок 2: Коли потрібно додаткове налаштування

Точне налаштування маршрутизації для MetalLB у режимі L2

Вам слід налаштувати додаткові маршрути щоразу тоді, коли ваші ноди не мають налаштованої IP-адреси або маршруту до підмережі для якої MetalLB видає адреси.

Поясню трохи докладніше. Щоразу коли MetalLB вийде адресу, це можна порівняти з простим призначенням виду:

ip addr add 10.9.8.7/32 dev lo

Зверніть увагу на:

  • a) Адреса призначається з префіксом /32 тобто маршрут у підмережу для нього автоматично не додасться (це просто адреса)
  • b) Адреса вішається будь-який інтерфейс ноди (наприклад loopback). Тут варто згадати про особливості мережевого стека Linux. Не важливо, на який інтерфейс ви додасте адресу, ядро ​​завжди буде обробляти arp-запити і відправляти arp-відповіді на будь-яку з них, ця поведінка вважається корректною і, крім того, досить широко використовується в такому динамічному середовищі як Kubernetes.

Цю поведінку можна налаштовувати, наприклад увімкнувши strict arp:

echo 1 > /proc/sys/net/ipv4/conf/all/arp_ignore
echo 2 > /proc/sys/net/ipv4/conf/all/arp_announce

У цьому випадку arp-відповіді надсилатимуться лише в тому випадку, якщо інтерфейс явно містить конкретну IP-адресу. Дане налаштування є обов'язковим, якщо ви плануєте використовувати MetalLB і ваш kube-proxy працює в режимі IPVS.

Тим не менш, MetalLB не використовує ядро ​​для обробки arp-запитів, а робить це самостійно в user-space, таким чином дана опція не вплине на роботу MetalLB.

Повернемося до нашого завдання. Якщо маршруту для адрес, що видаються, на ваших нодах не існує, додайте його заздалегідь на всі ноди:

ip route add 10.9.8.0/24 dev eth1

Випадок 3: Коли знадобиться source-based routing

Source-based routing вам потрібно налаштувати тоді, коли ви отримуєте пакети через окремий gateway, не той що налаштований у вас за замовчуванням, відповідно пакети у відповідь також повинні йти через цей же gateway.

Наприклад, у вас є все також підсіти 192.168.1.0/24 виділена для ваших нод, але ви хочете видавати зовнішні адреси за допомогою MetalLB. Припустимо, у вас є кілька адрес з підмережі 1.2.3.0/24 що знаходяться у VLAN 100, і ви хочете використовувати їх для доступу Kubernetes служб ззовні.

Точне налаштування маршрутизації для MetalLB у режимі L2

При зверненні на 1.2.3.4 ви будете здійснювати запити з іншої підмережі ніж 1.2.3.0/24 і чекати на відповідь. Нода, яка на даний момент є майстром для виданої MetalLB адреси 1.2.3.4отримає пакет від маршрутизатора 1.2.3.1, але відповідь для нього обов'язково має піти тим же маршрутом, через 1.2.3.1.

Оскільки наша нода вже має налаштований default gateway 192.168.1.1, то за умовчанням відповідь піде до нього, а не до 1.2.3.1через який ми отримали пакет.

Як же впоратися з цією ситуацією?

У цьому випадку вам необхідно підготувати всі ноди таким чином, щоб вони були готові обслуговувати зовнішні адреси без додаткового налаштування. Тобто для наведеного вище прикладу вам потрібно заздалегідь створити VLAN-інтерфейс на ноді:

ip link add link eth0 name eth0.100 type vlan id 100
ip link set eth0.100 up

А потім додати маршрути:

ip route add 1.2.3.0/24 dev eth0.100 table 100
ip route add default via 1.2.3.1 table 100

Зверніть увагу маршрути ми додаємо до окремої табиці маршрутизації 100 вона міститиме лише два маршрути необхідні для відправки пакету у відповідь через гейтвей 1.2.3.1, що знаходиться за інтерфейсом eth0.100.

Тепер нам потрібно додати просте правило:

ip rule add from 1.2.3.0/24 lookup 100

яке явно говорить: якщо адреса джерела пакета знаходиться в 1.2.3.0/24, то потрібно використовувати табіцю маршрутизації 100. У ній у нас вже описаний маршрут, який відправить його через 1.2.3.1

Випадок 4: Коли знадобиться policy-based routing

Топологія мережі як і в попередньому прикладі, але припустимо ви хочете також мати можливість звертатися до зовнішніх адрес пула 1.2.3.0/24 з ваших подів:

Точне налаштування маршрутизації для MetalLB у режимі L2

Особливість полягає в тому, що при зверненні до будь-якої адреси в 1.2.3.0/24, пакет у відповідь потрапляючи на ноду і маючи адресу джерела в діапазоні 1.2.3.0/24 буде слухняно відправлений у eth0.100Але ми хочемо, щоб Kubernetes перенаправив його в наш перший під, який і згенерував початковий запит.

Вирішити цю проблему виявилося непросто, але це стало можливим завдяки policy-based routing:

Для більшого розуміння процесу наведу блок схеми netfilter:
Точне налаштування маршрутизації для MetalLB у режимі L2

Для початку, як і в попередньому прикладі, створимо додаткову таблицю маршрутизації:

ip route add 1.2.3.0/24 dev eth0.100 table 100
ip route add default via 1.2.3.1 table 100

Тепер додамо кілька правил у iptables:

iptables -t mangle -A PREROUTING -i eth0.100 -j CONNMARK --set-mark 0x100
iptables -t mangle -A PREROUTING  -j CONNMARK --restore-mark
iptables -t mangle -A PREROUTING -m mark ! --mark 0 -j RETURN
iptables -t mangle -A POSTROUTING -j CONNMARK --save-mark

Ці правила маркуватимуть вхідні підключення на інтерфейс eth0.100, позначаючи всі пакети тегом 0x100, цим же тегом будуть позначені і відповіді в рамках одного підключення.

Тепер ми можемо додати правило роутингу:

ip rule add from 1.2.3.0/24 fwmark 0x100 lookup 100

Тобто всі пакети з адресою джерела 1.2.3.0/24 та тегом 0x100 повинні бути змаршрутизовані, використовуючи таблицю 100.

Таким чином, інші пакети, отримані на інший інтерфейс, під це правило не потрапляють, що дозволить їм бути змаршрутизованими стандартними засобами Kubernetes.

Є ще одне але, в Linux існує так званий reverse path filter, який псує всю малину здійснює просту перевірку: для всіх вхідних пакетів він змінює адресу джерела пакета з адресою відправника і перевіряє чи може пакет піти через той же інтерфейс на який був отриманий якщо ні, то відфільтровує його.

Проблема в тому, що в нашому випадку він працюватиме некоректно, але ми можемо відключити його:

echo 0 > /proc/sys/net/ipv4/conf/all/rp_filter
echo 0 > /proc/sys/net/ipv4/conf/eth0.100/rp_filter

Зверніть увагу, що перша команда котролює глобальну поведінку rp_filter, якщо її не відключити, то друга команда не матиме жодного ефекту. Проте інші інтерфейси залишаться із включеним rp_filter.

Щоб не обмежувати роботу фільтра повністю, ми можемо скористатися реалізацією rp_filter для netfilter. Використовуючи rpfilter як модуль iptables можна налаштувати досить гнучкі правила, наприклад:

iptables -t raw -A PREROUTING -i eth0.100 -d 1.2.3.0/24 -j RETURN
iptables -t raw -A PREROUTING -i eth0.100 -m rpfilter --invert -j DROP

включають rp_filter на інтерфейсі eth0.100 для всіх адрес крім 1.2.3.0/24.

Джерело: habr.com

Додати коментар або відгук