VK Cloud

Сеть в Kubernetes: Service, DNS, CNI и сетевые политики

28 июля 2026 г.
268267_007n7ek.jpg
Евгений Левашов
Автор статьи
_blog_head_32.png

Сеть в Kubernetes на первый взгляд выглядит набором разрозненных настроек: сегодня инженер выбирает тип Service, завтра разбирается с DNS-именами, потом пишет сетевую политику, которая почему-то не срабатывает. Каждую тему учат отдельно, и складывается впечатление, что Service, DNS, CNI и Network Policies — это независимые фичи, которые нужно зазубрить по очереди.

На самом деле всё это — реализации одной модели. В её основе три правила: у каждого пода свой IP, поды общаются без NAT, адресное пространство плоское. Service балансирует трафик к группе подов, DNS превращает имена в адреса, CNI прокладывает саму сеть, а Network Policies её сегментируют. Как только модель видна целиком, перестаёшь путать выбор типа Service с причинами, почему не срабатывает политика.

Дальше разберём модель снизу вверх: плоская сеть подов, четыре типа Service, DNS-резолвинг через CoreDNS, CNI-плагины и эволюция dataplane, сетевые политики и устройство сети в управляемом кластере VK Cloud.

Новиков2.jpg

Статья подготовлена вместе с экспертом

Сергей Новиков, менеджер продукта Managed Kubernetes

Сетевая модель Kubernetes

Прежде чем говорить про Service и DNS, нужно зафиксировать фундамент. Kubernetes не изобретает сеть с нуля — он задаёт требования к ней, а реализацию отдаёт плагину. Эти требования и есть сетевая модель.

Она держится на трёх правилах:

  • У каждого пода свой уникальный IP в пределах кластера. Контейнеры внутри одного пода делят этот адрес и общаются через localhost.
  • Поды общаются друг с другом без NAT: под на одном узле видит под на другом по его реальному IP, без трансляции адресов между ними.
  • Сеть плоская. Все поды живут в едином адресном пространстве, как будто подключены к одному большому коммутатору, независимо от того, на каком физическом узле они запущены.

Важно понимать, что под не знает и не должен знать, где физически крутится его собеседник. Приложение обращается к IP пода, и сеть доставляет пакет. Реализует эти правила CNI-плагин, а Kubernetes требует их соблюдения от любой реализации.

Вот как это выглядит:

text

Узел A (10.0.1.0/24) Узел B (10.0.2.0/24) ┌──────────────────────┐ ┌──────────────────────┐ │ Pod 10.244.1.5 │ │ Pod 10.244.2.7 │ │ Pod 10.244.1.6 │◄────►│ Pod 10.244.2.8 │ └──────────────────────┘ └──────────────────────┘ │ │ └────────── плоская сеть ──────┘ Каждый под — свой IP, связь без NAT, единое адресное пространство кластера

Поды на разных узлах общаются по реальным IP напрямую. Адреса узлов и адреса подов лежат в разных подсетях, и задача CNI — связать их так, чтобы под с узла A дошёл до пода на узле B без трансляции адресов.

Почему это важно для всего остального? Под — сущность эфемерная: его пересоздают при обновлении, переносят при отказе узла, масштабируют, и IP пода меняется при каждом пересоздании. Обращаться к поду по адресу напрямую нельзя, завтра адрес будет другим. Отсюда и потребность в стабильной точке входа, которая переживёт пересоздание подов. Эту роль берёт на себя Service.

Типы Service: ClusterIP, NodePort, LoadBalancer

Service — стабильный сетевой адрес для группы подов. Поды за ним приходят и уходят, а адрес и DNS-имя Service остаются на месте. Service выбирает поды по меткам (label selector) и балансирует на них трафик. По способу доступа различают четыре типа.

ClusterIP — адрес, доступный только внутри кластера. Это тип по умолчанию и основа сервис-дискавери: на него указывает DNS-имя сервиса. Снаружи такой Service недоступен, он существует для общения подов между собой.

text

apiVersion: v1 kind: Service metadata: name: backend spec: type: ClusterIP selector: app: backend ports: - port: 80 targetPort: 8080

NodePort открывает один и тот же порт на каждом узле кластера и открывает порт на каждом узле и проксирует трафик на поды . Диапазон портов по умолчанию — 30000–32767, его задаёт флаг --service-node-port-range. Подходит для отладки или интеграции с внешним балансировщиком, но в проде напрямую используется редко: клиенту нужно знать адреса узлов и держать в голове нестандартный порт.

text

apiVersion: v1 kind: Service metadata: name: backend-nodeport spec: type: NodePort selector: app: backend ports: - port: 80 targetPort: 8080 nodePort: 30080

LoadBalancer запрашивает у облака внешний IP и поднимает балансировщик поверх NodePort. Это штатный способ выставить сервис наружу: облако выдаёт внешний IP для сервиса, а трафик распределяется по подам. Маршрутизация по HTTP-путям и хостам — уже задача Ingress в Kubernetes, а не самого Service.

text

apiVersion: v1 kind: Service metadata: name: backend-lb spec: type: LoadBalancer selector: app: backend ports: - port: 80 targetPort: 8080

ExternalName не проксирует трафик и не имеет селектора — он отдаёт CNAME на внешнее имя, то есть даёт внутреннее имя внешнему ресурсу, например базе данных за пределами кластера.

text

apiVersion: v1 kind: Service metadata: name: external-db spec: type: ExternalName externalName: db.example.com

Все четыре типа Service отвечают на один вопрос: откуда придёт трафик — изнутри, с узла, из облака или с внешнего имени. Для внутреннего общения подходит ClusterIP, наружу выставляют через LoadBalancer, а мост к внешнему ресурсу даёт ExternalName.

DNS внутри кластера

Service даёт стабильный IP, но обращаться к сервисам по IP неудобно: адрес ClusterIP назначается при создании и держится за Service, а помнить его никто не хочет. Для этого работает сервис-дискавери — поиск сервисов по именам. За него отвечает DNS.

DNS-сервер в кластере — CoreDNS. Это обычный Deployment в namespace kube-system, как правило с ClusterIP 10.96.0.10. CoreDNS следит за API-сервером: появился Service — появилась DNS-запись.

Полное имя сервиса (FQDN) строится по шаблону <сервис>..svc.cluster.local и резолвится в ClusterIP этого сервиса. Например:

text

my-svc.my-namespace.svc.cluster.local → 10.96.x.x (ClusterIP)

Проверить резолвинг можно изнутри любого пода:

text

$ nslookup backend.production.svc.cluster.local Server: 10.96.0.10 Address: 10.96.0.10#53 Name: backend.production.svc.cluster.local Address: 10.96.x.x

Здесь есть деталь, на которой многие спотыкаются. Внутри своего namespace под обращается к сервису по короткому имени — просто backend, DNS дописывает суффикс текущего namespace и резолвит его. Но если сервис живёт в другом namespace, короткое имя не сработает: нужно полное имя backend.production или лучше FQDN backend.production.svc.cluster.local. Большинство проблем вида «сервис недоступен, хотя поды живые» сводится именно к обращению в чужой namespace по короткому имени.

DNS — тоже реализация сетевой модели: имена существуют, потому что у каждого Service есть стабильный ClusterIP, а у ClusterIP — место в плоской сети. Без модели резолвить было бы просто нечего. В VK Cloud управляемый Kubernetes идёт с готовой сетью: CoreDNS, балансировщик и сетевые политики уже настроены, и разворачивать сетевой слой вручную не нужно. До 12 000 ₽ на тест → Cloud Containers.

CNI-плагины

Несколько раз выше упоминалось «реализует CNI» без объяснения, что это. CNI (Container Network Interface) — стандарт, по которому подключаемый плагин выполняет три правила сетевой модели. Именно CNI назначает подам IP, прокладывает маршруты между узлами и обеспечивает связь без NAT. Без CNI поды не получат сеть вообще.

Плагины различаются тем, как они доставляют пакет от пода на одном узле до пода на другом:

  • Overlay (Flannel, Weave): пакеты подов упаковываются в дополнительный заголовок, обычно VXLAN, и идут поверх сети узлов. Проще в настройке, работает почти везде, но добавляет латентность из-за инкапсуляции. Важная оговорка: Flannel сам по себе не поддерживает Network Policies, для сегментации нужен Calico или Cilium.
  • Underlay / routing (Calico с BGP): узлы анонсируют маршруты к подам по BGP, пакеты идут без инкапсуляции, как обычная маршрутизация. Ниже накладные расходы, но нужна поддержка со стороны сети.
  • eBPF (Cilium): обработка трафика уходит в ядро Linux через eBPF, минуя цепочки iptables. Cilium получил статус Graduated в CNCF 11 октября 2023 года.

Отдельная история — как dataplane обрабатывает правила Service. Исторически kube-proxy в режиме iptables строит ruleset размером O(N) от суммы сервисов и эндпойнтов: чем больше сервисов, тем длиннее цепочки правил, через которые проходит каждый пакет. На больших масштабах, порядка 5000 сервисов, такой кластер начинает заметно проседать. Поэтому dataplane эволюционировал: iptables → IPVS → nftables (стал stable в Kubernetes 1.33) → eBPF. Cilium на eBPF использует хеш-таблицы и даёт lookup за O(1) вместо O(N) у iptables.

Практический вывод такой: пока кластер маленький, разница между подходами почти не ощущается, и можно ставить LoadBalancer, не думая о dataplane. На сотнях сервисов незнание модели уже стоит дорого: линейный рост правил iptables бьёт по задержкам, а отсутствие поддержки Network Policies в CNI оставляет кластер без сегментации.

Здесь стоит упомянуть и слой L7. Маршрутизация по HTTP, mTLS, трафик между сервисами — это уровень выше базовой сети, за него отвечает service mesh в Kubernetes и Ingress, а не CNI. CNI закрывает L3/L4 — то, чтобы пакет вообще дошёл.

Network Policies

По умолчанию в Kubernetes любой под может обратиться к любому другому: сеть плоская и открытая. Для прода это риск — скомпрометированный фронтенд не должен ходить напрямую в базу данных. Сегментацию задают Network Policies — правила, описывающие, кому с кем можно общаться. Подробнее про защиту кластера — в материале про безопасность Kubernetes.

Логика NetworkPolicy строится на нескольких правилах. podSelector выбирает поды, к которым применяется политика, а пустой селектор {} означает «все поды namespace». Под не изолирован до тех пор, пока его не выбрала хотя бы одна политика, но как только он попал под политику, для него начинает действовать принцип: всё, что не разрешено, запрещено. default-deny — это политика с пустым селектором, блокирующая весь ingress и egress: она изолирует все поды namespace, после чего нужный трафик открывают отдельными разрешающими политиками.

Ключевой момент в том, что политики реализует CNI. Если плагин их не поддерживает, как Flannel без надстройки, манифест NetworkPolicy применится без ошибок, но трафик ограничен не будет. Это коварно: кажется, что сегментация настроена, а на деле её нет.

Рабочий пример — default-deny плюс разрешение для бэкенда ходить только к базе, с обязательным egress на DNS, без которого под не сможет резолвить имена:

text

# 1. Закрываем весь трафик в namespace apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: default-deny namespace: production spec: podSelector: {} policyTypes: - Ingress - Egress --- # 2. Разрешаем backend ходить к db и в DNS apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: backend-to-db namespace: production spec: podSelector: matchLabels: app: backend policyTypes: - Egress egress: # доступ к базе - to: - podSelector: matchLabels: app: db ports: - protocol: TCP port: 5432 # доступ к DNS (иначе сломается резолвинг) - to: - namespaceSelector: {} podSelector: matchLabels: k8s-app: kube-dns ports: - protocol: UDP port: 53 - protocol: TCP port: 53

Частая ошибка — забыть про egress на порт 53. После включения default-deny под перестаёт резолвить DNS-имена, и всё внезапно ломается, хотя политика формально верна. Это прямое следствие того, что DNS — часть сетевой модели, а не отдельный сервис вне неё.

Сеть в VK Cloud: SDN Sprut

В управляемом кластере VK Cloud всё описанное выше уже собрано и настроено, и сервис Cloud Containers показывает, как сетевая модель Kubernetes ложится на реальную инфраструктуру.

Физический слой обеспечивает SDN Sprut — собственная программно-определяемая сеть VK Cloud, которую используют вместо OpenStack Neutron. Sprut работает на BGP с BFD и даёт низкую задержку между нодами кластера, реализуя плоскую сеть подов на уровне физической инфраструктуры.

За CNI отвечает Calico: сеть подов и Network Policies строятся на маршрутизации по BGP с инкапсуляцией IPIP/VXLAN при необходимости, и сетевые политики из примеров выше работают здесь без дополнительной настройки плагина. CoreDNS обслуживает сервис-дискавери, а метрики уходят в Prometheus. kube-proxy работает в режиме iptables. Load Balancer построен на Octavia (HAProxy) с поддержкой L4/L7 и SSL-терминацией, а реальный IP клиента пробрасывается через Proxy Protocol или externalTrafficPolicy: Local. Kube-in-Kube изолирует Control Plane клиента внутри системного кластера, защищая DNS и сетевые плагины от изменений со стороны пользователя.

Production-кластер Kubernetes разворачивается автоматически с изолированным control plane по модели Kubernetes-in-Kubernetes. Self-Healing автоматически заменяет отказавшие ноды без ручного вмешательства инженера, однако отказоустойчивость приложения по-прежнему зависит от корректной настройки PDB и распределения реплик.

Частые вопросы про сеть в Kubernetes

Как устроена сеть в Kubernetes?

Сеть держится на трёх правилах модели: у каждого пода уникальный IP в кластере, поды общаются без NAT, адресное пространство плоское. Реализует эти правила CNI-плагин. Поверх модели работают Service (стабильный адрес для группы подов), DNS (имена вместо адресов) и Network Policies (сегментация).

Чем отличаются ClusterIP, NodePort и LoadBalancer?

ClusterIP даёт адрес только внутри кластера и используется по умолчанию. NodePort открывает порт на каждом узле в диапазоне 30000–32767. LoadBalancer запрашивает у облака внешний IP и поднимает балансировщик поверх NodePort — это штатный способ выставить сервис наружу.

Как работает DNS в кластере?

DNS обслуживает CoreDNS — Deployment в namespace kube-system, обычно с ClusterIP 10.96.0.10. Сервис резолвится по FQDN <сервис>..svc.cluster.local. Внутри своего namespace работает короткое имя, а в чужой namespace нужно обращаться по полному имени.

Что такое CNI?

CNI (Container Network Interface) — стандарт подключаемого плагина, который реализует сетевую модель: назначает подам IP, прокладывает маршруты, обеспечивает связь без NAT. Подходы различаются: overlay (Flannel), routing с BGP (Calico), eBPF (Cilium). Часть плагинов, например Flannel, не поддерживает Network Policies напрямую.

Как ограничить трафик между подами?

Через Network Policies. По умолчанию все поды открыты, а политика с пустым селектором и блоком ingress/egress даёт default-deny, после чего нужный трафик открывают отдельными правилами. Важно не забыть egress на DNS (порт 53), иначе сломается резолвинг имён. Политики реализует CNI, и если плагин их не поддерживает, манифест применится, но не сработает.

Заключение

Сеть Kubernetes — единая модель с тремя правилами: свой IP у пода, связь без NAT, плоская сеть. Всё остальное лишь надстраивается над ней: Service даёт стабильный адрес группе подов, DNS превращает имена в адреса, CNI прокладывает саму сеть и определяет, как быстро dataplane обрабатывает правила, а Network Policies сегментируют трафик поверх плоской сети.

На небольшом кластере эта конструкция прощает почти всё: можно ткнуть LoadBalancer наугад, забыть про egress на DNS, не думать про dataplane — и всё будет работать. А вот на сотнях сервисов та же беспечность превращается в задержки, залипшие правила iptables и NetworkPolicy, которая существует только на бумаге. Разница между «сеть работает» и «сеть работает предсказуемо» — это ровно та модель, которую мы разобрали: зная её, вы выбираете тип Service не гадая, чините DNS за минуту и пишете политики, которые действительно ограничивают трафик, а не просто красиво выглядят в манифесте.

Оставьте заявку, чтобы получить консультацию

Наши специалисты свяжутся с вами в ближайшее время и ответят на все вопросы.

section-subscribe_2x.png

            Узнавайте о выходе новых статей в блоге первыми!

            Будем держать в курсе новостей и облачных трендов

            section-subscribe_2x.png
              section-subscribe_2x.png
              Теги: kubernetes, DNS, VK Cloud
              Ссылка скопирована
              Поделиться

              Почитать по теме

              _blog_head_158.png
              24 июля

              External Secrets Operator в Managed Kubernetes: безопасное управление секретами без ручной работы

              _blog_head_100.png
              24 июля

              S3-CSI в Managed Kubernetes: масштабирование приложений без потери общих файлов

              blog_head_39_69.png
              20 июля

              Kubernetes для разработчика: локально (Minikube, kind) и в облаке

              40+ готовых сервисов