Не так давно я столкнулся с весьма нестандартной задачей настройки маршрутищации для MetalLB. Всё бы ничего, т.к. обычно для MetalLB не требуется никаких дополнительных действий, но в нашем случае имеется достаточно большой кластер с весьма нехитрой конфигурацией сети. В данной статье я расскажу как настроить source-based и policy-based routing для внешней сети вашего кластера. Я не буду подробно останавливаться на установке и настройке MetalLB, так как предполагаю вы уже имеете некоторый опыт. Предлагаю сразу перейти к делу, а именно к настройке маршрутизации. Итак мы имеем четыре кейса: Разберём простой кейс.  Дополнительная настройка маршрутизации не требуется, когда выдаваемые MetalLB адреса находятся в той же подсети что и адреса ваших нод. Например вы имеете подсеть 192.168.1.0/24, в ней имеется маршрутизатор 192.168.1.1, и ваши ноды получают адреса: 192.168.1.10-30, тогда для MetalLB вы можете настроить рейндж 192.168.1.100-120 и быть уверенным что они будут работать без какой-либо дополнительной настройки. Почему так? Потому что ваши ноды уже имеют настроенные маршруты: И адреса из того же рейджа будут переиспользовать их без каких-либо дополнительных телодвижений.  Вам следует настроить дополнительные маршруты всякий раз тогда, когда ваши ноды не имеют настроенного IP-адреса или маршрута в подсеть для которой MetalLB выдаёт адреса. Объясню чуть подробней. Всякий раз когда MetalLB выдёт адрес, это можно сравнить с простым назначением вида: Обратите внимание, на: Данное поведение можно настраивать, например включив strict arp: В этом случае arp-ответы будут отправляться только в том случае, если интерфейс явно содержит конкретный IP-адрес. Данная настройка является обязательной в случае если вы планируете использовать MetalLB и ваш kube-proxy работает в режиме IPVS. Тем не менее MetalLB не использует ядро для обработки arp-запросов, а делает это самостоятельно в user-space, таким образом данная опция не повлияет на работу MetalLB. Вернёмся к нашей задаче. Если маршрута для выдаваемых адресов на ваших нодах не существует, добавьте его заранее на все ноды: Source-based routing вам потребуется настроить тогда, когда вы получаете пакеты через отдельный gateway, не тот что настроен у вас по умолчанию, соответственно ответные пакеты также должны уходить через этот же gateway. Например, у вас есть всё таже подсеть 192.168.1.0/24 выделенная для ваших нод, но вы хотите выдавать внешние адреса с помощью MetalLB. Предположим, у вас есть несколько адресов из подсети 1.2.3.0/24 находящихся во VLAN 100, и вы хотите использовать их для доступа Kubernetes-служб извне.  При обращении на 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-интерфейс на ноде: А затем добавить маршруты: Обратите внимание маршруты мы добавляем в отдельную табицу маршрутизации 100 она будет содержать только два маршрута необходимых для отправки ответного пакета через гейтвей 1.2.3.1, находящийся за интерфейсом eth0.100. Теперь нам нужно добавить простое правило: которое явно говорит: если адрес источника пакета находится в 1.2.3.0/24, то нужно использовать табицу маршрутизации 100. В ней у нас уже описан маршрут который отправит его через 1.2.3.1 Топология сети как и в предыдущем примере, но допустим вы хотите также иметь возиможность обращаться к внешним адресам пула 1.2.3.0/24 из ваших подов:  Особенность заключается в том что при обращении к любому адресу в 1.2.3.0/24, ответный пакет попадая на ноду и имея адрес источника в диапазоне 1.2.3.0/24 будет послушно отправлен в eth0.100, но мы-то хотим чтобы Kubernetes перенаправил его в наш первый под, который и сгенерировал изначальный запрос. Решить данную проблему оказалось непросто, но это стало возможным благодаря policy-based routing. Для начала, как и в предыдущем примере, создадим дополнительную таблицу маршрутизации: Уже после публикации этой статьи мне предложили более простой и изящный метод решения данной проблемы, для этого нам всего лишь нужно добавить два правила: где: Второе правило обязательно должно быть объявленно после первого, чтобы получило наивысший приоритет. Для большего понимания процесса приведу блок схему netfilter:
 Теперь добавим несколько правил в iptables: Эти правила будут маркировать входящие подключения на интерфейс eth0.100, помечая все пакеты тэгом 0x100, этим же тэгом будут помечены и ответы в рамках одного подключения. Теперь мы можем добавить правило роутинга: То есть все пакеты с адресом источника 1.2.3.0/24 и тэгом 0x100 должны быть смаршрутизированны используя таблицу 100. Таким образом другие пакеты, полученные на другой интерфейс, под это правило не попадают, что позволит им быть смаршрутизированными стандартными средствами Kubernetes. Есть ещё одно но, в Linux существует так называемый reverse path filter, который портит всю малину осуществляет простую проверку: для всех входящих пакетов он меняет адрес источника пакета с адресом отправителя и проверяет может ли пакет уйти через тот-же интерфейс на который был получен, если нет, то отфильтровывет его. Проблема в том что в нашем случае он будет работать некорректно, но мы можем отключить его: Обратите внимание, первая команда котролирует глобальное поведение rp_filter, если его не отключить, то вторая команда не будет иметь никакого эфекта. Тем не менее остальные интерфейсы останутся со включённым rp_filter. Чтобы не ограничивать работу фильтра полностью мы можем воспользоваться реализацией rp_filter для netfilter. Используя rpfilter в качестве модуля iptables можно настроить достаточно гибкие правила, например: включат rp_filter на интерфейсе eth0.100 для всех адресов кроме 1.2.3.0/24. Суперпользователь