Как опубликовать приложение из 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.
Что происходит «под капотом»:
Как только в кластере появляется сервис
LoadBalancer,cloud-providerзаказывает в облаке Network Load Balancer (NLB) — L4-балансировщик с нужным набором Listener’ов и IP‑адресов (внешних или внутренних).Сервису назначается VIP — публичный IP в облаке, который становится его
ExternalIP.В настройках NLB
cloud-providerопределяетTargetGroup— список всех узлов кластера в форматеNodeIP:NodePort.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 в статической инфраструктуре
В 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. С помощью механизма 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.
Читайте также в нашем блоге:
