Модуль реализует механизм LoadBalancer для сервисов в кластерах bare metal.

Основан на решении MetalLB.

Режим layer 2

В режиме layer 2 один из узлов берет на себя ответственность за предоставление сервиса в локальной сети. С точки зрения сети это выглядит так, словно у узла несколько IP-адресов, назначенных его сетевому интерфейсу. Технически это реализовано следующим образом: MetalLB отвечает на ARP-запросы для IPv4-сервисов и NDP-запросы — для IPv6. Основным преимуществом режима layer 2 является его универсальность: он работает в любой сети Ethernet, не требуя специального оборудования.

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

В режиме layer 2 весь трафик поступает на один узел, с которого kube-proxy распределяет его на поды сервиса. В этом смысле режим layer 2 не реализует балансировку нагрузки. Скорее он реализует механизм failover, когда один из узлов берет на себя управление, если ведущий узел по какой-то причине выходит из строя. В нашем случае failover происходит автоматически. Отказавший узел определяется с помощью списка узлов (memberlist), после чего другие узлы принимают его IP-адреса.

Ограничения

Необходимо учитывать два ключевых ограничения режима layer 2:

  • Единственный узел — узкое место.

    В режиме layer 2 узел, выбранный лидером, получает весь трафик. То есть пропускная способность Ingress’а определенного сервиса ограничена пропускной способностью этого узла. Это фундаментальное ограничение, связанное с использованием ARP и NDP для перенаправления трафика.

  • Потенциально медленный failover.

    В текущей реализации failover между узлами зависит от настроек вышестоящих роутеров. Когда происходит failover, MetalLB посылает ряд «незапрошенных» пакетов уровня 2, уведомляя клиентов об изменении MAC-адреса, связанного с IP сервиса. Большинство операционных систем правильно обрабатывают «незапрошенные» пакеты и быстро обновляют кэш с информацией о соседях. В этом случае failover происходит в течение нескольких секунд. Однако некоторые системы либо вообще не реализуют такую обработку, либо имеют ошибку во встроенной в них реализации, что задерживает обновление кэша.

    Все современные версии популярных ОС (Windows, Mac, Linux) правильно реализуют layer 2 failover, поэтому единственная ситуация, когда могут возникнуть проблемы — старые или экзотические ОС. Чтобы минимизировать влияние failover на таких клиентов (с ошибочной реализацией), следует оставить старый ведущий узел в рабочем состоянии на несколько минут после смены лидера. Так он сможет продолжать пересылать трафик старым клиентам (пока их кэш не обновится). Во время failover IP-адреса сервиса будут недоступны до тех пор, пока такие клиенты не обновят записи в своих кэшах.

Сравнение с Keepalived

У режима layer 2 в MetalLB много общего с Keepalived, поэтому пользователям Keepalived все это должно быть знакомо. Однако есть и несколько отличий, о которых следует упомянуть.

Keepalived использует протокол Virtual Router Redundancy Protocol (VRRP). Инстансы Keepalived постоянно обмениваются VRRP-сообщениями друг с другом для выбора лидера. Кроме того, данный механизм позволяет установить, что лидер более не доступен. MetalLB, в свою очередь, полагается на memberlist при отслеживании состояния узла. Если узел недоступен, запускается процесс переноса IP-адресов сервиса в другое место.

С точки зрения клиента, Keepalived и MetalLB выглядят одинаково: в процессе failover’а IP-адрес сервиса как бы мигрирует с одной машины на другую. В остальное время все выглядит так, словно у машин несколько IP-адресов. Поскольку MetalLB не использует VRRP, на него не распространяются некоторые ограничения этого протокола. Например, в MetalLB нет свойственного VRRP ограничения в 255 балансировщиков нагрузки на сеть. Можно создать любое количество балансировщиков, если в сети достаточно свободных IP-адресов. Кроме того, в конфигурации MetalLB меньше параметров, чем в конфигурации VRRP: например, здесь нет идентификаторов виртуальных маршрутизаторов.

С другой стороны, поскольку MetalLB полагается на memberlist при получении информации о членстве в кластере, он не может взаимодействовать с VRRP-совместимыми маршрутизаторами сторонних производителей и инфраструктурой. Так и задумано: MetalLB специально разработан для балансировки нагрузки и failover в кластере Kubernetes. В этом сценарии взаимодействие со сторонним программным обеспечением для балансировки нагрузки не предполагается.

Режим BGP

В режиме BGP каждый узел кластера устанавливает пиринговую сессию по протоколу BGP с сетевыми маршрутизаторами и использует ее для публикации IP-адресов внешних сервисов кластера. Поддержка multipath маршрутизаторами позволяет добиться настоящей балансировки нагрузки: маршруты, публикуемые MetalLB, эквивалентны друг другу (за исключением их nexthop). То есть маршрутизаторы будут использовать все nexthop вместе и балансировать нагрузку между ними. kube-proxy отвечает за финальный hop: как только пакеты поступают на узел, он переправляет их на определенный под сервиса.

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

Конкретное поведение при балансировке нагрузки зависит от модели и конфигурации маршрутизатора. Обычно балансировка осуществляется по каждому соединению с использованием хэшей пакетов.

Балансировка по каждому соединению означает, что все пакеты определенной TCP- или UDP-сессии направляются на одну и ту же машину в кластере. Перераспределяется трафик, относящийся к разным соединениям, а не пакеты в рамках одного соединения. Перераспределение пакетов между несколькими узлами кластера привело бы к ряду проблем:

  • Разбивка одного соединения на несколько путей влечет переупорядочивание пакетов на конечном узле, оказывая значительное негативное влияние на его производительность.
  • Маршрутизация трафика на одном узле в Kubernetes не гарантирует согласованности между узлами. Разные узлы могут направлять пакеты, относящиеся к одному и тому же соединению, в разные поды, что приведет к проблемам.

Хэширование пакетов позволяет высокопроизводительным маршрутизаторам распределять соединения между несколькими бэкендами без хранения состояния (statelessly). Для каждого пакета извлекаются некоторые поля, которые используются в качестве исходных данных для детерминированного выбора одного из возможных бэкендов. Для идентичных полей всегда будет выбираться один и тот же бэкенд. Конкретные методы подсчета хэшей зависят от аппаратного и программного обеспечения маршрутизатора. Обычно используется хэширование по трем и по пяти элементам (3-tuple и 5-tuple hashing). При хэшировании по трем элементам в качестве ключей используются протокол, IP-адрес источника и IP-адрес получателя. Таким образом все пакеты с двумя уникальными IP-адресами будут направляться на один и тот же бэкенд. При хэшировании по пяти элементам также используются порты источника и назначения, что позволяет распределять по кластеру различные соединения от одних и тех же клиентов.

Рекомендуется закладывать как можно больше энтропии в хэш пакета, то есть предпочтительно большее количество полей. Увеличение энтропии приближает балансировку нагрузки к «идеальному» состоянию, когда каждый узел получает одинаковое количество пакетов. Увы, из-за перечисленных выше проблем достичь такого идеального состояния невозможно. Все, что можно сделать, — попытаться распределить соединения максимально равномерно, предотвратив образование узких мест.

Ограничения

Важное преимущество использования протокола BGP для балансировки нагрузки заключается в возможности использовать обычные маршрутизаторы вместо их экзотичных реализаций. Однако у этого подхода есть и недостатки.

Главный — балансировка нагрузки на основе BGP плохо реагирует на изменения в бэкендах. При выходе из строя узла кластера последует разрыв всех активных соединений с сервисом (пользователи увидят сообщение «Connection reset by peer»). Маршрутизаторы на базе BGP реализуют stateless-распределение нагрузки. Они вычисляют хэш пакета на основе полей его заголовка и используют вычисленный хэш в качестве индекса в массиве для определения адреса бэкенда.

Проблема в том, что используемые в маршрутизаторах хэши обычно нестабильны. Когда набор бэкендов меняется (например, обрывается BGP-сессия узла), существующие соединения перехэшируются фактически случайным образом. В этом случае большинство существующих соединений могут быть внезапно перенаправлены на другие бэкенды, не имеющие о них никакого представления. Таким образом, любое изменение связки «IP-адрес → узел сервиса» влечет разовый отказ, при котором большинство активных соединений с сервисом прервется. Выражается это в полном однократном разрыве без потери пакетов или blackhol’инга.

Существуют различные стратегии, позволяющие снизить риск (в зависимости от предназначения сервиса):

  • Настроить BGP-маршрутизатор на использование более стабильного алгоритма хэширования ECMP (часто называется resilient ECMP или resilient LAG). Использование такого алгоритма значительно снижает количество затронутых соединений при изменении набора бэкендов.
  • Привязывать сервисы к определенным узлам, тем самым сократив количество узлов, за которыми нужно следить.
  • Планировать изменения на время минимального трафика, когда большинство пользователей неактивны и трафик невелик.
  • Разбить каждый логический сервис на два Service’а Kubernetes с разными IP-адресами и использовать DNS для миграции пользовательского трафика с одного Service’а на другой до того, как произойдет прерывание работы сервиса.
  • Добавить прозрачную логику повторных запросов на стороне клиента для восстановления после внезапных обрывов. Это особенно актуально для мобильных приложений или одностраничных веб-приложений.
  • Разместить сервисы за Ingress-контроллером. Ingress-контроллер может использовать MetalLB для получения трафика, при этом наличие промежуточного слоя между BGP и сервисами позволяет вносить изменения в сервисы, не опасаясь обрывов трафика. Осторожность потребуется только при изменении самого Ingress-контроллера (например, при его масштабировании).
  • Принять факт, что время от времени будут возникать обрывы соединений. Это может быть приемлемо для внутренних сервисов с низкой доступностью.