NetworkPolicy: основы
NetworkPolicy — это декларативный firewall на уровне L3/L4 (IP-адреса и порты) для Pods в Kubernetes. API группа — networking.k8s.io/v1, существует с 1.7. Идея простая: вы пишете манифест, в котором говорите «к Pod-ам с такими-то labels разрешён трафик только от таких-то источников на такие-то порты» — и кластер должен это применить. На самом деле кластер ничего сам не применяет. Применяет CNI plugin, и тут начинается главный подводный камень всей темы.
Firewalls: stateful vs stateless, iptables, security groups
Critical: NetworkPolicy без CNI с поддержкой — это no-op
В Kubernetes есть две независимые вещи:
- API-объект
NetworkPolicy, который вы создаёте черезkubectl apply; - его enforcement — реальное применение iptables/eBPF правил на узлах.
API-сервер примет любой валидный YAML и положит его в etcd. На этом ответственность core Kubernetes заканчивается. Если ваш CNI plugin не умеет читать NetworkPolicy — объект существует, kubectl get netpol показывает его, describe показывает правила, но в сети не происходит ничего. Pods как общались, так и общаются.
Первое, что нужно проверять на CKAD и в production при отладке NetworkPolicy — это CNI. Если в kube-system нет ни одного из Calico/Cilium/Antrea/Weave — вы пишете правила в пустоту.
Быстрая проверка:
kubectl get pods -n kube-system | grep -E 'calico|cilium|antrea|weave'
Если выводится пусто — скорее всего, policy не работает. Дополнительно: kubectl describe node | grep -i cni или ls /etc/cni/net.d/ на самом узле покажет имя активного plugin.
Default behavior: open by default
Базовая модель Kubernetes сети — трафик между Pod-ами разрешён по умолчанию. Любой Pod может достучаться до любого другого Pod в любом namespace на любой порт. Это противоположно тому, как обычно работают firewall (default-deny).
Это сделано специально: чтобы кластер «просто работал» без обязательной настройки. Микросервисы видят друг друга, init-контейнеры могут дёрнуть API, миграции БД проходят. Но это же означает, что из коробки кластер не изолирован: скомпрометированный Pod может сканировать весь под-CIDR.
NetworkPolicy переключает эту модель в selective deny: для конкретных Pods вы говорите «вот этому Pod-у разрешено только то-то и то-то, остальное — запрещено».
Isolation: что значит «policy применилась к Pod»
Ключевое понятие — isolation. Pod считается isolated в направлении (ingress или egress), если в его namespace существует хотя бы одна NetworkPolicy, чей spec.podSelector matches его labels, и в policyTypes указано это направление.
- Не isolated — default behavior, всё разрешено в этом направлении.
- Isolated — разрешено только то, что явно перечислено в
ingress/egressrules всех применимых policies.
Несколько policies, применимых к одному Pod, объединяются логическим OR. Это и хорошее свойство (легко добавлять новые правила), и опасное (нет способа сказать «запрети, даже если другая policy разрешает» — для этого нужен AdminNetworkPolicy (beta на v1.35, требует enable feature-gate и CRDs из k/networking-policy-api проекта) или CNI-specific CRD).
Структура манифеста
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: api-allowlist
namespace: production
spec:
podSelector:
matchLabels:
app: api
policyTypes:
- Ingress
- Egress
ingress:
- from:
- podSelector:
matchLabels:
app: web
ports:
- port: 8080
protocol: TCP
egress:
- to:
- podSelector:
matchLabels:
app: postgres
ports:
- port: 5432
protocol: TCP
Что делает этот манифест:
- podSelector: к каким Pod-ам применяется policy. Здесь — Pods с label
app=apiв namespaceproduction. - policyTypes: какие направления policy контролирует. Здесь — оба.
- ingress: список разрешённых источников и портов входящего трафика.
- egress: список разрешённых направлений и портов исходящего трафика.
Pods, у которых нет label app=api, этой policy не затронуты. Они остаются non-isolated (если на них не действует другая policy).
podSelector — что и куда
Поле spec.podSelector определяет, к каким Pod-ам в namespace применяется policy. Это не «откуда трафик» и не «куда» — это «кого policy защищает».
# Применяется ко всем Pods в namespace
spec:
podSelector: {}
# Применяется к Pods с label tier=backend
spec:
podSelector:
matchLabels:
tier: backend
# Применяется к Pods с одним из двух labels
spec:
podSelector:
matchExpressions:
- key: tier
operator: In
values: [backend, db]
podSelector: {} — это «все Pods в namespace». Это самый частый паттерн для policy уровня namespace (deny-all, allow-dns). Не путайте с from: [] (никто не разрешён) и отсутствием поля (все разрешены).
NetworkPolicy — namespaced объект. podSelector всегда работает в namespace самой policy. Чтобы выбрать Pods в другом namespace, нужны namespaceSelector внутри from/to — это про разрешённые peers, не про targets.
policyTypes: убийственная ловушка CKAD
Поле spec.policyTypes — список направлений, которые контролирует policy. Возможные значения: Ingress, Egress, либо оба.
Главное правило: если policyTypes не указан явно — Kubernetes выводит его по эвристике: всегда добавляет Ingress, и плюс Egress, если в манифесте есть секция egress. Звучит спасительно, но ловушка остаётся: если вы явно указали policyTypes: [Ingress] и при этом написали egress: секцию — секция просто игнорируется, без diagnostic, без warning. Always-safe правило: указывайте policyTypes явно — [Ingress], [Egress], либо [Ingress, Egress] — это исключает любые сюрпризы.
# ПЛОХО: egress блок написан, но не указан в policyTypes
# (если policyTypes отсутствует, K8s по эвристике добавит Egress —
# но на CKAD проще не полагаться, а указать явно)
spec:
podSelector:
matchLabels:
app: api
egress: # эти правила
- to:
- podSelector:
matchLabels: {app: db}
# policyTypes отсутствует — рискованно
# ХОРОШО: явно указано
spec:
podSelector:
matchLabels:
app: api
policyTypes:
- Egress
egress:
- to:
- podSelector:
matchLabels: {app: db}
На CKAD всегда указывайте policyTypes явно. Это снимает половину ошибок и не зависит от того, какую логику вывода применяет конкретная версия API server. И никогда не пишите egress без Egress в policyTypes.
Killer момент: ingress vs egress, кто куда смотрит
«Ingress» и «egress» — с точки зрения Pod, к которому применяется policy (то есть подходящий под podSelector).
- ingress — трафик, входящий в этот Pod. Поле
from— источник этого трафика. - egress — трафик, исходящий из этого Pod. Поле
to— назначение этого трафика.
Звучит банально, но на CKAD легко перепутать, если думать «policy для Pod web, разрешает доступ к db» — это egress (web смотрит наружу к db), а не ingress (db получает от web). Симметричную policy на стороне db (если она тоже isolated) — придётся писать отдельно как ingress на её манифесте.