Learning Platform
Глоссарий Troubleshooting
Урок 09.01 · 22 мин
Средний
Network modelCNIPodIPPod-to-PodOverlayVXLANBGPeBPFCoreDNS

Сетевая модель Kubernetes: требования и CNI

В отличие от классической виртуализации, где сеть — это network team и кучу VLAN-ов, Kubernetes начинается с очень простой, почти радикальной модели: каждый Pod получает уникальный routable IP внутри кластера, и любой Pod может пойти на любой другой Pod по этому IP, без NAT, без port-mapping, без shenanigans.

Это звучит просто, но скрывает огромную работу. На каждом узле живёт CNI plugin (Calico, Cilium, Flannel, Weave), который и реализует эту модель — настраивает veth-пары, bridge-интерфейсы, маршруты, VXLAN-туннели, BGP-сессии или eBPF-программы. Понимать сетевую модель критично — потому что Services, Ingress и NetworkPolicies все строятся поверх неё.


Subnetting и CIDR: как делить IP-пространство

Четыре требования network model

Kubernetes не реализует сеть сам — он формулирует требования к сетевому решению. Любой CNI plugin, претендующий на conformance, обязан выполнять:

  1. Все Pods могут общаться со всеми Pods без NAT — независимо от node, на которой они работают.
  2. Все узлы могут общаться со всеми Pods без NAT — node-level процессы (kubelet, kube-proxy) видят Pods напрямую.
  3. IP, который Pod видит у себя — это IP, по которому к нему обращаются другие. Никакого “внутреннего” и “внешнего” адреса.
  4. Services получают отдельный virtual IP (ClusterIP), который реализуется уже kube-proxy (об этом — в следующих уроках).
NOTE

Эти требования формализованы в документе “Cluster Networking” в kubernetes.io. Они намеренно высокоуровневые: Kubernetes не диктует как делать (overlay, underlay, BGP) — только что должно работать. Это позволяет cloud-провайдерам и vendor-ам делать совершенно разные имплементации.

Сетевая модель — что разрешено
Pod A (node 1)PodIP 10.244.1.5. Полноценный routable адрес внутри кластера.
Pod B (node 2)PodIP 10.244.2.7 на другой ноде. Между ними — overlay или routing, но Pod А не знает об этом.
Pod A → Pod B напрямую (без NAT)
Node 1 (kubelet)Процессы на ноде (kubelet, kube-proxy) видят Pod IPs напрямую. Это нужно для probes, exec, port-forward.
self-view = external-view
Pod B видит свой IPВнутри Pod B команда `ip addr` или `hostname -i` покажет 10.244.2.7. Это тот же IP, по которому к нему ходит Pod A. Никакого внутреннего vs внешнего адреса.

В классическом Docker эти требования не выполняются — там по умолчанию NAT через docker0 bridge и port mapping наружу. Именно поэтому Kubernetes не использует Docker networking — он подменяет его CNI-плагином.


CNI: Container Network Interface

CNI — стандарт CNCF, описывающий как именно “сетевое решение” подключается к runtime. Спецификация простая: набор binary-плагинов в /opt/cni/bin/ и JSON-конфиг в /etc/cni/net.d/. Runtime (containerd, CRI-O) вызывает плагины с действиями ADD (создать сеть для контейнера), DEL (убрать), CHECK.

{
  "cniVersion": "1.0.0",
  "name": "k8s-pod-network",
  "type": "calico",
  "ipam": {
    "type": "calico-ipam"
  },
  "policy": {
    "type": "k8s"
  }
}

CNI plugin — это исполняемый файл, который получает на stdin JSON-описание сети, на stdout возвращает результат (выданный IP, маршруты).

Кто что делает в цепочке создания Pod

kubelet → containerd → CNI: выдача PodIP
kubeletПолучил Pod из watch на apiserver. Видит spec.nodeName=своё имя — нужно запустить.
CRI gRPC: RunPodSandbox
containerdСоздаёт pause container с собственным network namespace. Это будущий network ns для всех контейнеров Pod-а — они шарят его.
exec /opt/cni/bin/calico ADD
CNI plugin (Calico/Cilium)Создаёт veth-пару: один конец внутри netns Pod-а (eth0), другой — на ноде. Назначает IP из node CIDR, прописывает маршруты, добавляет в bridge или ставит правила eBPF.
stdout: PodIP
containerd → kubeletcontainerd возвращает sandbox info с выданным IP. kubelet делает PATCH .status.podIP на apiserver — теперь Service-ы и DNS могут указать на Pod.

PodIP выдаётся ОДНОКРАТНО при создании Pod sandbox. При рестарте Pod-а (новый объект с тем же именем — например, через Deployment rolling update) выдаётся новый IP. Это критично: PodIP — ephemeral, не используйте его напрямую как target — вот зачем нужны Services со stable virtual IP.


Популярные CNI plugins

Все они реализуют одну спецификацию, но способы — разные.

Flannel

Простой, “по умолчанию minikube”. Использует overlay-сеть VXLAN: пакет от Pod A заворачивается в UDP-пакет (с VXLAN-заголовком, VNI), отправляется на node, где живёт Pod B, разворачивается и доставляется. Каждой ноде выделен sub-CIDR из общего Pod CIDR (например, node 1: 10.244.1.0/24, node 2: 10.244.2.0/24).

Pod A (10.244.1.5) → пакет → veth → flannel.1 (VXLAN encap) →
  UDP 8472 → eth0 ноды → сеть нод → eth0 другой ноды →
  flannel.1 (decap) → bridge → veth → Pod B (10.244.2.7)

Плюс: работает на любой сети между нодами (всё через UDP). Минус: overhead VXLAN, нет NetworkPolicies.

Calico

Без overlay по умолчанию: использует BGP для рассылки маршрутов между нодами. Каждая нода становится BGP-роутером, анонсирует свой sub-CIDR, остальные узнают и прописывают маршруты в kernel routing table напрямую. Encap не нужен.

Калико умеет fallback на IPIP или VXLAN, если ноды в разных AS/подсетях, где нельзя BGP. Также поддерживает NetworkPolicies через iptables правила.

Cilium

eBPF-based. Не использует iptables вообще: все Pod-to-Pod, Service load balancing, NetworkPolicy реализованы как eBPF-программы, прикреплённые к network interfaces в kernel. Это быстрее iptables/IPVS, особенно на больших кластерах с тысячами Services. Поддерживает L7 NetworkPolicies (фильтрация по HTTP path, Kafka topics).

В современных кластерах Cilium часто полностью заменяет kube-proxy (kubeProxyReplacement: true) — реализует ClusterIP через eBPF без вообще каких-либо netfilter правил.

Weave Net

Устаревший, рекомендации к продакшну сегодня нет. Использует собственный overlay с шифрованием.

TIP

На CKAD не требуют знания конкретного CNI и его конфигурации — это CKA. Но нужно понимать, что есть выбор, и что CNI определяет: тип сети (overlay vs routing), поддержка NetworkPolicy, производительность. На собеседовании “почему выбрали Calico/Cilium” — частый вопрос.


Pod-to-Pod внутри ноды: veth и bridge

Внутри одной ноды Pod-ы соединяются через veth-пары: пара виртуальных интерфейсов, как кабель между двумя network namespaces. Один конец — eth0 внутри Pod, второй — cali... / vethXXX на ноде. Все эти “ножные” концы либо подключены к bridge (Linux bridge), либо обрабатываются роутингом (Calico без bridge).

Pod-to-Pod внутри одной node
netns Pod ANetwork namespace Pod A. Внутри только eth0 (10.244.1.5) и lo. Нет видимости других интерфейсов ноды.
netns Pod BNetwork namespace Pod B. Свой eth0 (10.244.1.6).
veth-пары
cali123 (host)Конец veth-пары на хосте. Виден через `ip link` на ноде. Префикс зависит от CNI: cali для Calico, lxc для Cilium, vethXX для Flannel.
cali456 (host)Второй veth-конец на хосте. Подключён к bridge или обрабатывается прямым routing.
bridge или host routing
cni0 bridge / kernel routingВ Flannel/bridge подходе — Linux bridge cni0 объединяет всех. В Calico — нет bridge, kernel routing table содержит /32 routes на каждый PodIP через нужный veth.
# На ноде посмотреть veth-пары
ip link | grep cali

# Routing table
ip route
# 10.244.1.5 dev cali123 scope link
# 10.244.1.6 dev cali456 scope link
# 10.244.2.0/24 via 192.168.1.20 dev eth0   ← маршрут на другую ноду

Pod-to-Pod внутри ноды — это просто bridging/routing в kernel, никакого encapsulation. Latency микросекундная.


Pod-to-Pod между нодами: overlay vs underlay

Когда Pod на node 1 пишет Pod-у на node 2 — пакет должен пройти через физическую сеть между нодами. Здесь два подхода:

Overlay (VXLAN, Geneve)

Пакет упаковывается в UDP (VXLAN: UDP 8472, Geneve: UDP 6081). Внешний IP — IP ноды-источника, внутренний — Pod-у-получателю. Преимущество: не нужно конфигурировать underlying сеть, работает где угодно. Недостаток: MTU overhead (50 байт на VXLAN), CPU на encap/decap.

Underlay routing (BGP, direct routes)

Маршруты до Pod CIDR-ов нод рассылаются по BGP, либо прописываются вручную/cloud route table. Пакет от Pod A идёт прямо как src=10.244.1.5, dst=10.244.2.7 без encap — сеть знает как маршрутизировать. Преимущество: без overhead, нативная производительность. Недостаток: требует контроля над сетью между нодами.

eBPF (Cilium)

Использует eBPF-программы на TC (traffic control) hooks для туннелирования напрямую через VXLAN или native routing, плюс полностью свой стек для NodePort, ClusterIP, NetworkPolicy. Скоростной плюс и observability.


Killer момент: Pod не знает о Service ClusterIP

Это важный концептуальный поворот, который проваливают многие.

WARNING

Когда Pod отправляет HTTP-запрос на http://my-service:80 — он резолвит DNS → получает ClusterIP (например, 10.96.0.10) → отправляет TCP SYN на 10.96.0.10:80. Pod НЕ ЗНАЕТ, что это виртуальный IP. С его точки зрения он шлёт пакет на конкретный адрес.

Что происходит дальше — это работа kube-proxy (или его замены через eBPF в Cilium). На локальной ноде Pod-а netfilter (через iptables/IPVS/nftables правила, сгенерированные kube-proxy) перехватывает пакет:

  1. Видит dst 10.96.0.10:80 — это ClusterIP Service my-service.
  2. Выбирает один из endpoints (Pod IP) — например, 10.244.2.7:8080.
  3. Делает DNAT (destination NAT): меняет dst в IP-заголовке на 10.244.2.7:8080.
  4. Пакет уходит дальше по обычным CNI-маршрутам до этого Pod-а.
Pod → ClusterIP → backend Pod (DNAT)
Pod (client)Пишет на ClusterIP. Не знает, что это виртуал. С его стороны это обычный TCP connect.
пакет: dst=10.96.0.10:80
netfilter на той же нодеiptables/IPVS правила, написанные kube-proxy. Видит ClusterIP, выбирает endpoint (round-robin или probability).
DNAT
conntrack entryLinux conntrack запоминает преобразование: исходный пакет 10.244.1.5 → 10.96.0.10, преобразованный → 10.244.2.7. Это нужно чтобы reverse-пакеты (от Pod B обратно) восстановили src=10.96.0.10 — иначе клиент не примет ответ.
forward по CNI
Pod backendПолучает пакет с src=10.244.1.5 (PodIP клиента), dst=10.244.2.7 (свой PodIP). Не знает, что клиент писал в Service — для него это прямое соединение.

Никакой Pod в кластере не отвечает на ARP по адресу 10.96.0.10. Этот IP не привязан ни к какому интерфейсу. Если запустить ping 10.96.0.10 — пакет уйдёт, но никто на ARP-запрос не ответит. Service работает только для TCP/UDP, потому что только эти протоколы обрабатываются netfilter-правилами kube-proxy для DNAT.

# Это НЕ будет работать на ClusterIP:
kubectl exec -it pod -- ping 10.96.0.10

# А это будет:
kubectl exec -it pod -- curl http://10.96.0.10:80

CoreDNS: in-cluster DNS

Без DNS все Pod-ы должны были бы знать ClusterIP-ы Service-ов через env vars или конфиги. Но ClusterIP может меняться (если пересоздать Service). Поэтому используют DNS: <service>.<namespace>.svc.cluster.local.

В кластере есть Deployment coredns в namespace kube-system, expose-нутый как Service kube-dns (имя историческое — от старого kube-dns server-а до v1.11). kubelet прописывает каждому Pod-у в /etc/resolv.conf IP этого Service-а в качестве nameserver:

# Внутри Pod:
$ cat /etc/resolv.conf
nameserver 10.96.0.10
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5

При обращении к my-service — резолвер по search-domains пробует my-service.default.svc.cluster.local → отправляет DNS query на 10.96.0.10:53 → CoreDNS возвращает ClusterIP. Дальше — DNAT через kube-proxy на endpoint.

CoreDNS — это обычные Pod-ы. Они отвечают на DNS-запросы, общаются с apiserver через watch на Services/Endpoints/Pods и кэшируют записи. Подробнее DNS — в уроке 4.


Полная картина: путь HTTP-запроса

Соберём всё вместе. Pod frontend в namespace default пишет curl http://api:8080/users:

  1. DNS resolve: api через /etc/resolv.confapi.default.svc.cluster.local → DNS query на 10.96.0.10:53 (CoreDNS).
  2. Pod-to-Pod на ClusterIP CoreDNS: пакет DNS-query летит на ClusterIP 10.96.0.10 → netfilter DNAT на один из CoreDNS Pod-ов (например, 10.244.3.4). Ответ обратно через conntrack.
  3. CoreDNS отвечает: ClusterIP service api10.96.0.42.
  4. TCP connect на api ClusterIP: пакет на 10.96.0.42:8080 → netfilter DNAT на один из endpoints api-сервиса (например, 10.244.2.7:8080).
  5. CNI доставляет пакет: внутри ноды через veth/bridge, между нодами — overlay/BGP. Pod api получает пакет от frontend IP.

Всё это происходит на каждом TCP-handshake. Понимание этой цепочки — основа диагностики проблем connectivity.


Проверка знанийKnowledge check
Можно ли пингануть (ICMP) Service ClusterIP, например `ping 10.96.0.10`? И почему?
ОтветAnswer
Нет, в большинстве случаев ping на ClusterIP не работает. ClusterIP — это виртуальный IP, не привязанный ни к какому network interface в кластере. Никакой Pod не отвечает на ARP по этому адресу. kube-proxy реализует ClusterIP правилами в netfilter (iptables/IPVS/nftables), которые перехватывают пакеты только для определённых протоколов и портов (TCP/UDP), которые объявлены в spec.ports Service-а. ICMP не маршрутизируется через эти правила, поэтому пакет уходит в никуда. Для проверки работы Service нужно делать запрос на конкретный порт (curl, telnet, nc), а не ping.

Проверьте понимание

Результат: 0 из 0
Концептуальный
Вопрос 1 из 5. Какое из этих требований НЕ является частью Kubernetes network model?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 5