Как опубликовать приложение из Kubernetes наружу — со стабильным IP, балансировкой и автоматическим failover? В облаке это решает провайдер: создал сервис type: LoadBalancer — и получил готовый балансировщик. На bare metal такой «магии» нет: ClusterIP и NodePort не дают ни единого внешнего IP, ни отказоустойчивости.

В этой статье разберём принципы работы внешней балансировки L4: от базовых сервисов ClusterIP/NodePort до MetalLB в режимах BGP и Layer 2. Покажем, как эти механизмы реализованы на практике, в Deckhouse Kubernetes Platform (DKP), где доступен в том числе улучшенный L2-режим. Вы поймёте, как спроектировать отказоустойчивую точку входа для трафика независимо от того, используете ли вы готовые платформы или настраиваете инфраструктуру самостоятельно.

Меня зовут Игорь Бужин, я инженер в Deckhouse Академии, веду и разрабатываю различные курсы. Мы учим строить стабильную и надёжную ИТ-инфраструктуру на базе продуктов экосистемы Deckhouse. Предлагаем онлайн-курсы с экспертами-преподавателями и бесплатные материалы для самостоятельного изучения. Обучение дистанционное: лекции, демонстрации в живом кластере и практика на персональном стенде. По итогам — уверенные навыки и возможность получить официальную сертификацию.

Почему стандартных сервисов Kubernetes не хватает для внешнего доступа

В Kubernetes есть несколько типов сервисов для балансировки локального трафика внутри кластера: 

  • ClusterIP — закрепляет за приложением стабильный IP и DNS-имя с балансировкой по эндпоинтам (round robin); 

  • Headless — отдаёт клиенту IP подов напрямую через DNS; 

  • NodePort — открывает порт из диапазона 30000–32767 на всех узлах кластера, позволяя обращаться к приложению извне. 

Все они решают задачу внутренней балансировки, но для внешних клиентов (Internet/Intranet) этого недостаточно:

  • ClusterIP и Headless доступны только изнутри кластера. Напрямую снаружи к ним не подключиться: чтобы внешний клиент попал в приложение, придётся дополнительно настраивать пробросы портов, ставить внешние прокси или выдумывать обходные решения.

  • NodePort требует нестандартного порта, а главное: у клиента нет механизма проверить, жив ли узел, на который он стучится. Если трафик попал на упавший узел, соединение просто разорвётся — автоматического переключения на другой узел не произойдёт.

Для полноценной внешней балансировки нужен выделенный балансировщик L4, который принимает трафик на стандартных портах, распределяет нагрузку и обеспечивает failover. В Kubernetes для таких целей есть сервис типа LoadBalancer.

Как работает LoadBalancer в облачной инфраструктуре

Сервис LoadBalancer будет работать на разных инфраструктурах по-разному. В облаке (например, Yandex Cloud) при включённом модуле cloud-provider контроллер cloud-provider-yandex отслеживает сервисы type: LoadBalancer

Что происходит «под капотом»:

  1. Как только в кластере появляется сервис LoadBalancer, cloud-provider заказывает в облаке Network Load Balancer (NLB) — L4-балансировщик с нужным набором Listener’ов и IP‑адресов (внешних или внутренних). 

  2. Сервису назначается VIP — публичный IP в облаке, который становится его ExternalIP.

  3. В настройках NLB cloud-provider определяет TargetGroup — список всех узлов кластера в формате NodeIP:NodePort

  4. NLB начинает активные TCP/HTTP‑проверки узлов из TargetGroup и, чтобы понимать их состояние, помечает их как healthy или unhealthy.

Тогда трафик от клиента до приложения, работающего внутри кластера, пойдёт следующим образом:

  • внешний клиент обращается к externalIP:port, где externalIP — адрес Listener’а Network Load Balancer в облаке (VIP, назначенный сервису LoadBalancer);

  • NLB по своей конфигурации маршрутизирует трафик на TargetGroup (NodeIP:NodePort);

  • когда трафик приходит на узел через NodePort, traffic control hook перехватывает его и передаёт на обработку сетевому плагину (CNI). Например, в DKP основным CNI является Cilium — он обрабатывает пакет напрямую через eBPF в ядре, по BPF-карте выбирая нужный под. Другие CNI (Flannel, Calico) доставляют трафик тем же путём, но через iptables/conntrack: результат одинаковый, просто при большом числе сервисов производительность и отладка хуже.

Обработка внешнего трафика сервисом LoadBalancer в облачной инфраструктуре
Обработка внешнего трафика сервисом LoadBalancer в облачной инфраструктуре

Сервис LoadBalancer в статической инфраструктуре

В bare metal нет готового облачного провайдера. В таком случае в качестве NLB может быть использован какой-либо сторонний балансировщик — аппаратный или программный (HAProxy, nginx, Envoy). 

При этом в качестве NLB могут выступать и узлы кластера. Для этого используется MetalLB. Например, в DKP он реализован с помощью одноимённого модуля, который поддерживает два режима работы:

  • BGP — на базе оригинального MetalLB;

  • Layer 2 — улучшенная реализация от команды Deckhouse.

Режим BGP MetalLB

В этом режиме для обмена маршрутной информацией используется протокол BGP (Border Gateway Protocol). С его помощью устройства из разных AS (автономных систем) могут обмениваться маршрутами. Путь до сети представляется как последовательность номеров автономных систем — ASN (AS_PATH), и каждый переход между AS добавляет свой номер в этот путь. 

Сначала маршрутизаторы устанавливают BGP-сессию, а затем начинают обмениваться маршрутной информацией (анонс префикса с разными атрибутами) через UPDATE-сообщения. Маршрутизатор, получивший анонс, добавляет маршрут в свою таблицу маршрутизации (в случае, если он является лучшим среди других маршрутов). 

В режиме BGP MetalLB устанавливает BGP-сессии с маршрутизаторами (или ToR-коммутаторами) и анонсирует IP-адреса сервисов LoadBalancer. Балансировка трафика при этом может осуществляться с помощью механизма ECMP (Equal-Cost Multi-Path), который распределяет трафик по нескольким равнозначным маршрутам.

Рассмотрим пример: у нас есть кластер DKP в AS 65001. В этой же AS есть пограничный BGP+ECMP-маршрутизатор (Router ID 10.12.0.94). Клиенты находятся в удалённой AS 65000, на границе которой есть свой BGP-маршрутизатор (Router ID 10.12.0.10). В кластере DKP есть выделенные узлы, которые будут выступать в роли MetalLB-спикеров.

Исходное состояние сети
Исходное состояние сети

Чтобы на выделенных узлах были размещены MetalLB-спикеры (для взаимодействия с другими соседями по BGP) и был создан пул IP-адресов, которые будут назначаться в качестве VIP сервисам LoadBalancer, необходимо включить MetalLB в режиме BGP:

apiVersion: deckhouse.io/v1alpha1
kind: ModuleConfig
metadata:
  name: metallb
spec:
  enabled: true
  settings:
    addressPools:
      - addresses:
          - 192.168.219.100-192.168.219.200
        name: mypool
        protocol: bgp
    bgpPeers:
      - hold-time: 30s
        my-asn: 65001
        peer-address: 10.12.0.94
        peer-asn: 65001
        peer-port: 179
    speaker:
      nodeSelector:
        node-role: front
  version: 2
  • addressPools — пул IP для ExternalIP сервисов LoadBalancer.

  • bgpPeers — настройки BGP-пира: hold-time, ASN, адрес и порт.

  • speaker.nodeSelector — на каких узлах размещать BGP-спикеры.

В таком случае сервисам LoadBalancer будут назначаться VIP из пула mypool и анонсироваться через BGP-спикеров, размещённых на выделенных узлах кластера.

Для публикации приложения создаём сервис LoadBalancer с аннотацией для выбора пула:

apiVersion: v1
kind: Service
metadata:
  name: nginx
  annotations:
    metallb.universe.tf/address-pool: mypool
spec:
  type: LoadBalancer
  ports:
    - port: 80
      targetPort: 80
  selector:
    app: nginx

После создания сервиса MetalLB назначает VIP из пула mypool и анонсирует его через BGP-спикеров. На маршрутизаторе AS 65001 в таблице BGP появляется запись: VIP доступен за IP узлов кластера. Маршрутизатор AS 65000 видит VIP с next-hop — маршрутизатор AS 65001. Тогда клиент обращается по адресу LoadBalancer приложения, а не по адресу BGP-спикера.

Путь трафика в режиме BGP MetalLB
Путь трафика в режиме BGP MetalLB

Таким образом, в режиме BGP MetalLB используется стандартизированный протокол BGP. С помощью механизма ECMP можно распределять нагрузку между узлами кластера, а при выходе из строя узла, который анонсирует IP-адрес, маршрутизаторы автоматически перенаправляют трафик на другие узлы, анонсирующие этот же IP. 

Однако зачастую маршрутизаторы с поддержкой BGP и ECMP недоступны для администрирования — например, используются операторские маршрутизаторы, доступ к которым ограничен. Поэтому добавление спикеров в качестве соседей BGP в такой системе может быть затруднено.

Режим Layer 2 MetalLB

Когда BGP-маршрутизатор недоступен, можно использовать L2-режим. Трафик принимается из локальных сетей (Intranet), смежных с кластером по L2. 

В стандартном режиме L2 от MetalLB для сервиса LoadBalancer выделяется IP из пула. И один из спикеров MetalLB, который был назначен лидером, начинает отвечать на ARP-запросы за этот IP. Для внешней сети это выглядит так, будто IP назначен на интерфейсе leader-узла. Однако в таком случае весь трафик проходит через один узел. Если leader падает, MetalLB выбирает нового — тот начинает отвечать на ARP. Но все активные соединения при этом разрываются. Таким образом, при failover — полный обрыв соединений.

Команда Deckhouse существенно улучшила стандартный L2-режим MetalLB. Теперь сервису может выделяться не один, а несколько VIP-адресов — их количество задаётся специальной аннотацией в манифесте сервиса. Например, можно взять число адресов, равное числу выделенных узлов для их анонсов, и тогда каждый узел анонсирует свой IP через ARP, а в DNS создаётся несколько A-записей. Клиент при подключении сам выбирает адрес, и нагрузка естественным образом распределяется между всеми узлами.

Допустим, в кластере три выделенных узла в одной L2-сети 10.241.36.0/22 с L2-коммутатором (или любым устройством, обрабатывающим L2-трафик в этом сегменте). Для активации режима L2 от Deckhouse необходимо включить модуль MetalLB без дополнительных настроек, а также создать MetalLoadBalancerClass. CRD MetalLoadBalancerClass определяет пул IP, интерфейсы и узлы для анонсов:

apiVersion: network.deckhouse.io/v1alpha1
kind: MetalLoadBalancerClass
metadata:
  name: front
spec:
  addressPool:
    - 10.241.36.100-10.241.36.200
  isDefault: false
  nodeSelector:
    node-role: front
  type: L2

После применения на узлах с лейблом node-role=front запускаются L2-спикеры. Далее можно публиковать приложение через сервис LoadBalancer:

apiVersion: v1
kind: Service
metadata:
  name: nginx-deployment
  annotations:
    network.deckhouse.io/l2-load-balancer-external-ips-count: "3"
spec:
  type: LoadBalancer
  loadBalancerClass: front
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: nginx

В сервисе указан loadBalancerClass: front — это имя нашего MetalLoadBalancerClass, именно он будет отвечать за заказ ExternalIP из пула адресов MetalLB. В аннотации указано, сколько внешних IP из пула MetalLB выдать сервису в L2‑режиме. В данном случае сервису будут назначены 3 VIP. MetalLB отвечает на ARP за каждый VIP со своего выделенного узла. ARP-таблица на L2-коммутаторе:

10.241.36.100    aa:bb:cc:00:00:10   # VIP → MAC front1
10.241.36.101    aa:bb:cc:00:00:13   # VIP → MAC front2
10.241.36.102    aa:bb:cc:00:00:16   # VIP → MAC front3

Тогда трафик будет идти до приложения следующим образом:

  • на основании ExternalIP сервиса трафик попадает на порт выделенного узла кластера;

  • далее трафик передаётся на сервис LoadBalancer по ClusterIP на порт 80;

  • на основании Endpoints сервис выполняет NAT трафика на IP-адреса подов на порт 80.

При потере одного выделенного узла MetalLB перераспределяет его VIP на оставшиеся. Проверить привязку можно через SDNInternalL2LBService:

$ kubectl -n test describe sdninternall2lbservices 
nginx-deployment-front-0
Events:
  Normal  nodeAssigned  metallb-speaker  announcing from node 
"demo-front-...-x8whc" with protocol "layer2"

$ kubectl -n test describe sdninternall2lbservices 
nginx-deployment-front-2
Events:
  Normal  nodeAssigned  metallb-speaker  announcing from node 
"demo-front-...-x8whc" with protocol "layer2"

Два VIP (10.241.36.100 и 10.241.36.102) переехали на один узел, а 10.241.36.101 остался на втором. ARP-таблица обновилась, соединения к «живым» VIP не пострадали.

LoadBalancer + ALB: связка NLB и ALB

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

В примерах выше MetalLB публиковал каждое приложение напрямую через свой сервис LoadBalancer. Это работоспособный, но не самый удобный вариант: каждому HTTP/HTTPS-приложению нужен отдельный внешний IP, а всю сложную логику — терминацию TLS, маршрутизацию по доменам и путям, аутентификацию, лимиты запросов — приходится настраивать внутри каждого приложения отдельно. Когда сервисов становится больше, публичные IP-адреса быстро заканчиваются, а управлять L7-политиками из десятка разных мест будет просто невозможно.

Поэтому на практике зачастую используют связку NLB + ALB: сервис type: LoadBalancer заказывается не для самого приложения, а для L7-балансировщика (ALB). ALB уже сам разбирает HTTP/HTTPS-трафик и маршрутизирует его до нужных приложений по Host/Path, терминирует TLS, навешивает политики безопасности и решает другие задачи прикладного уровня. Такой подход даёт сразу несколько важных преимуществ:

  • Экономия внешних IP. Один VIP MetalLB на ALB обслуживает десятки и сотни приложений вместо отдельного IP на каждое.

  • Единая точка L7-политик. TLS-сертификаты, аутентификация, WAF-правила, rate limiting, заголовки и редиректы настраиваются централизованно на ALB, а не дублируются в каждом приложении.

  • Чёткое разделение ответственности. NLB занимается тем, что умеет делать максимально эффективно, — балансировкой и failover на L4; ALB — тем, что требует понимания протокола, то есть прикладной маршрутизацией на L7.

  • Гибкость и масштабируемость. Добавление нового приложения не требует выделения нового LoadBalancer и нового IP — достаточно описать правило маршрутизации на уже работающем ALB.

При этом сам сервис LoadBalancer для ALB продолжает работать ровно так, как было описано выше: в облаке его обрабатывает cloud-provider, на bare metal — MetalLB в режиме BGP или L2. Разница только в том, что заказчиком этого сервиса теперь выступает контроллер ALB, а не отдельное приложение.

В такой связке MetalLB играет роль NLB: принимает TCP/UDP-трафик на VIP и распределяет его между подами ALB (по узлам, где они запущены). Конкретная реализация ALB при этом не имеет значения — для MetalLB это просто очередной сервис type: LoadBalancer. Благодаря этому один и тот же NLB-уровень переиспользуется для разных ALB и разных классов трафика.

Выводы

Для полноценной внешней балансировки приложений в Kubernetes нужен выделенный балансировщик L4 (NLB): он принимает TCP/UDP-трафик на VIP по стандартным портам, распределяет нагрузку между узлами кластера и обеспечивает failover. Его реализация зависит от инфраструктуры:

  • В облаке задачу решает cloud-provider: при создании сервиса type: LoadBalancer контроллер заказывает у провайдера готовый NLB с VIP и TargetGroup на NodeIP:NodePort. Проверки healthcheck и failover полностью работают на стороне облака.

  • На bare metal облачного провайдера нет, поэтому роль NLB берёт на себя MetalLB, превращая узлы кластера в балансировщик. В режиме BGP MetalLB устанавливает сессии с пограничным маршрутизатором и анонсирует VIP сервисов, а ECMP на роутере распределяет трафик по узлам с быстрым failover. Когда BGP на пограничном оборудовании недоступен, используется L2-режим: стандартный — с одним leader-узлом на VIP и обрывом соединений при переключении — либо улучшенный L2 от Deckhouse — с несколькими VIP на сервис, анонсом каждого VIP со своего узла по ARP и балансировкой на стороне клиента через несколько A-записей.

  • Связка NLB + ALB: если в кластере уже есть L7-балансировщик (ALB), MetalLB естественным образом встраивается перед ним как NLB. Один сервис type: LoadBalancer для ALB принимает весь входящий L4-трафик на VIP MetalLB, а ALB дальше терминирует TLS и маршрутизирует HTTP/HTTPS-запросы до конкретных приложений. Отдельный LoadBalancer на каждое приложение не нужен, а MetalLB обеспечивает отказоустойчивую точку входа на L4 даже там, где облачного провайдера нет.

Где изучить тему глубже

Тема MetalLB и балансировки L4-трафика подробно рассматривается на курсе «Сетевые возможности Deckhouse Kubernetes Platform» (5 дней, 5 лекций, онлайн). На остальных лекциях курса погрузимся в другие сетевые аспекты DKP. Курс охватывает полный путь от базовой работы с внутренней сетью до управления входящим и исходящим трафиком и сетевой безопасности. Подойдёт тем, кто хочет разобраться, как устроена сеть в DKP:

  • как готовить статическую и облачную инфраструктуру под кластер;

  • как работают CNI Cilium и внутренняя балансировка через Services и DNS;

  • как балансировать L4-трафик с помощью MetalLB и L7-трафик через NGINX Ingress Controller;

  • как управлять исходящим трафиком через Cilium Egress Gateway;

  • как создавать сетевые политики, настраивать mTLS с Istio и диагностировать сетевые компоненты DKP.

Каждая лекция включает теорию с демонстрацией в живом кластере и практику на учебном стенде.

P. S.

Читайте также в нашем блоге: