Сеть кластера: CNI, NetworkPolicy и CoreDNS

Рейтинг: 64.6% · 7 голосов
Kubernetes для разработчиков: поды, деплойменты, сервисы, ingress, конфиги и отладка. Уроки по главам с обсуждением.
Ответить
Аватара пользователя
anton_k8s
Сообщения: 55
Зарегистрирован: 12 май 2026, 03:23

Сеть кластера: CNI, NetworkPolicy и CoreDNS

Сообщение anton_k8s »

Оглавление курса (48)
  1. Зачем нужен Kubernetes и из чего состоит кластер
  2. Поднимаем локальный кластер: minikube и kind
  3. Поды: базовая единица запуска
  4. Deployment и ReplicaSet: управляем репликами
  5. Service: сетевой доступ к подам
  6. ConfigMap и Secret: выносим конфигурацию
  7. Ingress: пускаем трафик снаружи
  8. Хранилище: Volumes и PersistentVolumeClaim
  9. Namespaces, requests и limits
  10. Health checks: liveness и readiness пробы
  11. Отладка: почему под не стартует
  12. Helm: пакетный менеджер для Kubernetes
  13. Базовая безопасность: RBAC и доступы
  14. Job и CronJob: разовые и периодические задачи
  15. StatefulSet и DaemonSet: stateful-нагрузки и системные агенты
  16. Стратегии обновления и планирование: rollout и rollback, graceful shutdown, nodeSelector, affinity, taints
  17. Автомасштабирование: HPA по метрикам, обзор VPA и Cluster Autoscaler
  18. Наблюдаемость: логи, метрики, events, обзор Prometheus и Grafana
  19. Безопасность глубже: securityContext, Pod Security Standards, NetworkPolicy, шифрование секретов
  20. Архитектура control plane: api-server, etcd, scheduler
  21. Узел кластера: kubelet, kube-proxy и container runtime
  22. Декларативная модель: reconciliation, контроллеры, CRD
  23. kubectl профессионально: get, describe, explain, jsonpath
  24. Под глубже: init, sidecar, lifecycle hooks и QoS
  25. Метки, селекторы и организация ресурсов
  26. Сервисы и kube-proxy глубже: типы, IPVS, EndpointSlices
  27. Ingress, ingress-контроллеры и Gateway API
  28. Секреты в кластере: шифрование, External Secrets, Vault
  29. Хранилище глубже: PV, StorageClass, CSI и StatefulSet
  30. Сеть кластера: CNI, NetworkPolicy и CoreDNS (вы здесь)
  31. Планировщик: affinity, taints, topology spread
  32. Ресурсы, QoS и вытеснение: requests, limits, eviction
  33. Helm глубже: шаблоны, хуки, зависимости, OCI
  34. Kustomize и управление конфигурацией без шаблонов
  35. RBAC и аутентификация глубже
  36. Admission и Pod Security: контроль на входе
  37. Policy as code: Kyverno и OPA Gatekeeper
  38. GitOps: Argo CD и Flux
  39. Операторы и CRD: расширяем Kubernetes
  40. Сервис-меш: Istio, Linkerd и когда он нужен
  41. Эксплуатация кластера: апгрейд, узлы, бэкап etcd
  42. Где запускать кластер: managed, self-hosted, k3s, Deckhouse
  43. Траблшутинг кластера: Pending, CrashLoop, узлы NotReady
  44. Стоимость и эффективность кластера: FinOps
  45. CI/CD в Kubernetes: сборка, деплой, прогрессивные релизы
  46. Лучшие практики и антипаттерны Kubernetes
  47. Сквозной проект и путь дальше: от манифеста до прода
  48. Деплой приложений через Argo CD: Application, sync и App-of-Apps
Боль, с которой начинается любой сетевой инцидент

Под не достучался до базы. Сервис отвечает то 200, то таймаут. DNS внутри пода резолвит внешний домен по полсекунды, а у соседнего пода - мгновенно. Половина команд в такой ситуации идет в логи приложения, хотя проблема живет на уровень ниже - в сети кластера. Понять, как именно kubernetes сеть устроена под капотом, это не академический интерес, а способ за пять минут локализовать инцидент вместо двух часов гадания.

В этом уроке разберем три кита: модель сети Kubernetes (почему у каждого пода свой IP и нет NAT), плагины kubernetes cni (Flannel, Calico, Cilium), service discovery через CoreDNS с его знаменитыми ndots-граблями, и изоляцию через NetworkPolicy. Везде - не определения из документации, а механика: как пакет реально идет от пода к поду и от пода к сервису, и где это ломается.

Изображение

Сетевая модель: плоская сеть и под-к-поду без NAT

Kubernetes навязывает жесткие правила, которые обязан выполнить любой CNI. Их всего четыре, и они звучат обманчиво просто:
  • каждый под получает собственный уникальный IP в пределах кластера;
  • под-к-поду общается напрямую, без NAT - под видит реальный IP собеседника, а не подмененный;
  • под и нода, на которой он живет, тоже видят друг друга без трансляции адресов;
  • IP, который под видит у себя (через ip addr), совпадает с тем, каким его видят другие.
Это и есть "плоская сеть": с точки зрения приложения весь кластер - одна большая локалка, где любой под дотягивается до любого по IP. Никаких port mapping, как в Docker bridge по умолчанию. Контейнеры внутри одного пода делят сетевой namespace - у них общий localhost и общий IP, поэтому два контейнера в поде не могут слушать один порт. Это ключевой момент: граница сетевой изоляции - именно под, а не контейнер.

Почему "без NAT" так важно. Если бы под-к-поду шел через трансляцию, приложение видело бы чужой адрес шлюза вместо реального источника. Сломались бы все механизмы, завязанные на source IP: аудит, rate limiting по клиенту, mTLS-валидация, да и сами NetworkPolicy (они фильтруют по реальным IP подов). Поэтому модель и требует прозрачности.

Важно различать три плоскости адресов. Pod CIDR - диапазон, из которого раздаются IP подам (например 10.244.0.0/16), его режет CNI. Service CIDR - виртуальные адреса сервисов (ClusterIP), их не существует ни на одном интерфейсе, это правила в iptables/IPVS/eBPF. Node IP - реальные адреса нод. Когда новичок путает ClusterIP с подовым IP и пытается пингануть сервис - пинг не идет, потому что ClusterIP не отвечает на ICMP, он живет только как DNAT-правило.

CNI-плагины: кто и как реализует kubernetes cni

Сам Kubernetes сеть не строит. Kubelet через стандарт CNI (Container Network Interface) дергает бинарь плагина в момент создания пода: "дай этому сетевому namespace интерфейс и IP". Плагин создает veth-пару (один конец в поде как eth0, другой на ноде), назначает адрес из Pod CIDR и прописывает маршруты. Дальше начинается специфика конкретного плагина - и именно от выбора CNI зависит, заработают ли у тебя сетевые политики.

Flannel - самый простой. Дает плоскую сеть через оверлей VXLAN: пакет пода заворачивается в UDP и едет между нодами. Просто, надежно, но не поддерживает NetworkPolicy вообще. Если ты ставишь Flannel и применяешь политику - она создастся в etcd и молча не сработает. Это грабли номер один в сети кластера. Flannel хорош для учебных стендов и k3s "из коробки".

Calico - рабочая лошадка продакшена. Умеет два режима: оверлей (IP-in-IP/VXLAN) и нативный роутинг через BGP, когда ноды анонсируют свои Pod CIDR соседям как настоящие маршрутизаторы - тогда оверлея нет вовсе, пакеты идут чистым L3 с минимальным оверхедом. Calico полноценно реализует NetworkPolicy плюс свой расширенный GlobalNetworkPolicy. Связка calico cilium - типичный предмет выбора при проектировании.

Cilium - тренд 2026 и де-факто новый стандарт. Построен на eBPF: вместо тысяч правил iptables политики и балансировка компилируются в программы, которые исполняются прямо в ядре Linux на хуках сетевого стека. Это дает три вещи. Первая - скорость: на больших кластерах iptables линейно деградирует, eBPF держит O(1) по хешам. Вторая - политики L7: можно разрешить не просто "порт 8080", а "только GET /api/v1/orders", фильтруя HTTP, gRPC, Kafka. Третья - наблюдаемость через Hubble: видно поток каждого пакета и причину дропа. Cilium полностью совместим со стандартным networking.k8s.io/v1 NetworkPolicy, а для L7 добавляет свой CRD cilium.io/v2 CiliumNetworkPolicy - его берешь точечно, только когда нужен слой 7. Именно поэтому в спорах calico cilium для новых кластеров чаша все чаще склоняется к Cilium.

CoreDNS и service discovery: как coredns kubernetes находит сервисы

ClusterIP сервиса - случайное число из Service CIDR, оно меняется при пересоздании. Хардкодить его нельзя, поэтому поды ходят по именам. За резолв имен в кластере отвечает coredns kubernetes - это поды (обычно в kube-system), которые сами выступают сервисом kube-dns с фиксированным ClusterIP, и этот адрес прописан в /etc/resolv.conf каждого пода.

Формат FQDN сервиса: <service>.<namespace>.svc.cluster.local. Из того же namespace достаточно короткого имени, из другого - service.namespace. Посмотрим реальный resolv.conf внутри пода:

Код: Выделить всё

$ kubectl exec -it web-7c9f -- cat /etc/resolv.conf
nameserver 10.96.0.10
search myapp.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
Разберем по полям. nameserver - ClusterIP CoreDNS. search - суффиксы, которые libc по очереди подставляет к коротким именам: запрос db превратится в попытки db.myapp.svc.cluster.local, потом db.svc.cluster.local и так далее. И главный виновник медленного DNS - ndots:5.

Грабли ndots на пальцах. ndots:5 означает: если в имени меньше 5 точек, считай его неполным и сначала пройдись по всем search-доменам, и только потом пробуй как есть. Теперь смотри, что происходит при запросе внешнего api.github.com (две точки, меньше пяти):

Код: Выделить всё

api.github.com.myapp.svc.cluster.local  -> NXDOMAIN
api.github.com.svc.cluster.local        -> NXDOMAIN
api.github.com.cluster.local            -> NXDOMAIN
api.github.com.                         -> A 140.82.x.x  (наконец-то)
Четыре запроса вместо одного, три из них - заведомо мусорные NXDOMAIN, и для IPv4+IPv6 это удваивается. На нагруженном сервисе с тысячами внешних вызовов в секунду CoreDNS захлебывается, а приложение ловит "медленный DNS". Лечится двумя способами: либо ставить точку в конце домена (api.github.com. - libc считает имя полным и не перебирает search), либо понизить ndots для конкретного пода через dnsConfig:

Код: Выделить всё

spec:
  dnsConfig:
    options:
    - name: ndots
      value: "2"
С ndots:2 имя api.github.com (две точки) сразу пробуется как есть. Понижение ndots на ворклоадах с обилием внешних запросов - самый дешевый и безопасный фикс. Внутренние короткие имена при этом продолжат резолвиться через search, потому что у них точек ноль.

NetworkPolicy: default-deny плюс явные allow

По умолчанию сеть полностью открыта - любой под достучится до любого. Для продакшена это недопустимо: скомпрометированный фронтенд не должен лезть напрямую в базу платежей. NetworkPolicy - это файрвол на уровне подов, отбирающий цели по меткам, namespace и портам. Критично помнить: политика работает только если ее поддерживает CNI. На Flannel она бесполезна (нужен Calico, Cilium или Calico-режим у managed-провайдера).

Логика построена на белых списках. Как только хотя бы одна политика выбрала под через podSelector для данного направления (Ingress или Egress), под переключается в режим "запрещено все, кроме явно разрешенного" по этому направлению. Стандартный паттерн - сначала глобальный default-deny на весь namespace, потом точечные allow.

Код: Выделить всё

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: shop
spec:
  podSelector: {}
  policyTypes:
  - Ingress
  - Egress
Пустой podSelector: {} выбирает все поды namespace, а отсутствие правил allow означает "ничего нельзя". Теперь добавим явное разрешение: пускать к подам с меткой app=db только трафик от app=api на порт 5432, и разрешить DNS на выход (иначе резолв сервисов умрет - частая ловушка после default-deny egress):

Код: Выделить всё

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-api-to-db
  namespace: shop
spec:
  podSelector:
    matchLabels:
      app: db
  policyTypes:
  - Ingress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: api
    ports:
    - protocol: TCP
      port: 5432
Несколько тонкостей, на которых горят. Внутри одного блока from селекторы namespaceSelector и podSelector, стоящие в одном элементе списка, соединяются логическим И (под с такой меткой в namespace с такой меткой). Если же это разные элементы списка через -, то ИЛИ. Перепутал - и политика разрешает в разы больше или меньше нужного. И помни: NetworkPolicy аддитивны, явного "deny" в стандартном API нет, итог - объединение всех allow.

Поток пакета: под-к-поду и под-к-сервису

Под-к-поду на одной ноде: пакет выходит из eth0 пода в его veth, попадает на ноду, плагин знает локальные маршруты и кладет пакет прямо в veth целевого пода. На разных нодах: CNI либо заворачивает в оверлей (VXLAN у Flannel, IP-in-IP у Calico), либо маршрутизирует нативно по BGP (Calico) или eBPF (Cilium) - source IP при этом сохраняется, что и требует модель.

Под-к-сервису сложнее, потому что ClusterIP - фикция. Когда под шлет на 10.96.0.50:80, в дело вступает kube-proxy (или eBPF у Cilium). Он заранее разложил для каждого сервиса DNAT-правило: "пакеты на этот ClusterIP подменяй на IP одного из живых эндпоинтов". Список живых подов сервиса берется из EndpointSlices - это пришедшая на смену старому Endpoints структура, которая шардит эндпоинты по 100 штук на объект, чтобы не перегружать API при тысячах подов. То есть ClusterIP - это просто стабильное имя для меняющегося набора реальных адресов, а трансляция происходит на исходящей ноде до отправки в сеть.

Диагностика сети: команды и разбор вывода

Базовый чек-лист, когда "под не ходит". Сначала смотрим, есть ли вообще эндпоинты у сервиса - пустой список означает, что селектор сервиса не совпал с метками подов или поды не готовы:

Код: Выделить всё

$ kubectl get endpointslices -l kubernetes.io/service-name=db -n shop
NAME       ADDRESSTYPE   PORTS   ENDPOINTS      AGE
db-x7k2q   IPv4          5432    10.244.1.23    12d
Если в колонке ENDPOINTS пусто или <none> - проблема не в сети, а в селекторе/readiness. Дальше проверяем DNS и связность изнутри пода эфемерным контейнером (не нужно тащить в образ curl):

Код: Выделить всё

$ kubectl debug -it web-7c9f --image=nicolaka/netshoot -- bash
# nslookup db.shop.svc.cluster.local
Server:    10.96.0.10
Address:   10.96.0.10:53
Name:      db.shop.svc.cluster.local
Address:   10.244.1.23
# nc -zv 10.244.1.23 5432
Connection to 10.244.1.23 5432 port [tcp/*] succeeded!
Резолв есть, а nc висит и отваливается по таймауту - почти наверняка режет NetworkPolicy. На Cilium это видно прямо в Hubble:

Код: Выделить всё

$ hubble observe --pod shop/web-7c9f --verdict DROPPED
... web-7c9f -> db-0 Policy denied DROPPED (TCP Flags: SYN)
Вердикт Policy denied однозначно указывает на политику - не нужно гадать. На Calico аналог - calicoctl и логи. Эта наблюдаемость и есть практическая причина, почему в дилемме calico cilium многие выбирают eBPF.

Типичные грабли и антипаттерны
  • Политики на CNI без поддержки. Самая коварная: NetworkPolicy на Flannel применяется без ошибок и молча не действует. Перед тем как полагаться на изоляцию, проверь, что CNI умеет политики.
  • default-deny egress без разрешения DNS. Закрыли весь исходящий - и сломали резолв, потому что запрос к CoreDNS на UDP/TCP 53 тоже исходящий. Всегда добавляй allow на kube-dns.
  • ndots:5 и внешние домены. Медленный DNS у приложений с обилием внешних вызовов - почти всегда это. Точка в конце или ndots:2.
  • Путаница ClusterIP и Pod IP. Пинг ClusterIP не отвечает - это нормально, он не существует как интерфейс.
  • И-против-ИЛИ в selector. Неверная вложенность namespaceSelector и podSelector тихо расширяет или сужает доступ.
Мини-лаба: повторить руками

На kind или k3s с Calico/Cilium (не Flannel - на нем шаг 4 не сработает):
  • Создай namespace shop, подними два пода: api и db (например образ nginx), навесь метки app=api и app=db.
  • Загляни в resolv.conf: kubectl exec в под и cat /etc/resolv.conf - найди ndots и search.
  • Из api эфемерным netshoot-контейнером проверь связь до db по имени и порту - должно работать.
  • Примени default-deny-all и allow-api-to-db из урока. Повтори проверку: из api доступ остался, а с третьего пода без метки app=api - таймаут.
  • На Cilium запусти hubble observe --verdict DROPPED и поймай Policy denied.
Контрольные вопросы
  • Почему модель Kubernetes запрещает NAT в трафике под-к-поду и что сломается, если его включить?
  • Что произойдет с запросом api.github.com при ndots:5 и как это исправить, не трогая код приложения?
  • Чем ClusterIP отличается от Pod IP на уровне того, где он "физически" существует?
  • Как с помощью двух NetworkPolicy реализовать схему "по умолчанию запрещено, разрешен только api->db:5432", и почему важно не забыть про DNS?
Итог

Сеть кластера стоит на трех опорах. Модель дает плоскую сеть с уникальным IP на под и без NAT - это контракт, который выполняет CNI. Выбор kubernetes cni определяет возможности: Flannel прост, но без политик; Calico дает BGP и полноценный файрвол; Cilium на eBPF добавляет L7 и наблюдаемость, и в 2026 это магистраль. CoreDNS превращает имена в адреса, но ndots:5 портит жизнь внешним вызовам. А NetworkPolicy по схеме default-deny плюс явные allow закрывает кластер - но только если CNI это умеет. Держа в голове путь пакета под-к-поду и под-к-сервису, ты диагностируешь сетевой инцидент по эндпоинтам, DNS и вердиктам политик за минуты.
👍3 ❤️2 🔥1 😄 🤔3
Аватара пользователя
kiddnets
Сообщения: 1
Зарегистрирован: 11 май 2026, 22:35

Re: Сеть кластера: CNI, NetworkPolicy и CoreDNS

Сообщение kiddnets »

Полночи дебажил медленный DNS на сервисе с кучей вызовов внешнего API - оказалось ровно ndots:5. Поставил точку в конце домена и латенси упало в разы. Спасибо, что разжевали с примером перебора search-доменов.
👍2 ❤️1 🔥 😄 🤔1
Аватара пользователя
llamasre
Сообщения: 1
Зарегистрирован: 23 май 2026, 01:48

Re: Сеть кластера: CNI, NetworkPolicy и CoreDNS

Сообщение llamasre »

Вопрос про grabli с Flannel: получается если у managed-кластера под капотом Flannel, то мои NetworkPolicy просто декорация? Как заранее проверить, поддерживает CNI политики или нет, не накатывая боевую изоляцию?
👍2 ❤️ 🔥1 😄 🤔
Ответить
← Предыдущая глава
Хранилище глубже: PV, StorageClass, CSI и StatefulSet
Следующая глава →
Планировщик: affinity, taints, topology spread

Все главы курса «Kubernetes: оркестрация контейнеров от основ до продакшена»

Поделиться темой: ✈ Telegram VK
Похожие запросы: minikube или kind что выбрать для локального кластера kuberneteskubectl get describe logs основные команды для работы с кластеромчто такое service в kubernetes и как поды находят друг другаnamespace в kubernetes зачем нужен и как разделить ресурсыhelm chart как установить приложение в kuberneteshelm глубже шаблоны хуки и зависимости чартов

Вернуться в «Kubernetes на практике»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 1 гость