Troubleshooting NetworkPolicy
NetworkPolicy ломается тихо. Никакой ошибки в API server, никакого warning в kubectl describe. Просто Pod не может достучаться или, наоборот, продолжает достукиваться. Дальше — алгоритм пошаговой диагностики, который работает на CKAD и в production. Не угадывать, не править YAML наугад, а проверять гипотезы по порядку.
Сетевая диагностика: ping, dig, ss, nc, traceroute
Чеклист из 6 шагов
Идём по очереди. На каждом шаге проверка занимает 10-30 секунд — это быстрее, чем редактировать YAML вслепую.
Шаг 1: CNI installed and supports NetworkPolicy
# Какие CNI работают в кластере
kubectl get pods -n kube-system | grep -E 'calico|cilium|antrea|weave|kube-router'
# Расширенная проверка — что лежит в /etc/cni/net.d на node
kubectl debug node/<NODE-NAME> -it --image=busybox -- ls /host/etc/cni/net.d/
Если ни одного CNI с поддержкой NetworkPolicy — задача нерешаема в принципе. На CKAD в задаче явно сказано «cluster уже настроен с поддержкой NetworkPolicy», но в production проверить — first thing.
Шаг 2: podSelector matches target Pod
# Какие labels у Pod
kubectl get pod <POD-NAME> --show-labels
# Что policy targets
kubectl describe networkpolicy <NETPOL-NAME>
# Видим раздел PodSelector: app=postgres
# Видим в labels Pod: app=postgres,version=14
# Match — yes
Если labels отличаются хотя бы одним символом (App vs app, postgres vs postgresql) — selector ничего не matchит, policy для Pod-а не применяется, он остаётся non-isolated, и весь трафик идёт по default-allow.
Это особенно коварно при «deny-all», который не работает: вы думаете, что Pod должен быть запрещён, но он просто не matchится с policy. Pod остаётся в default-allow и принимает любой трафик. Часто бывает после копипасты — забыли поменять label в template.
Шаг 3: policyTypes — критически важное поле
kubectl describe networkpolicy <NAME>
# В выводе ищите:
# Policy Types: Ingress, Egress
Если видите только Ingress, а у вас есть egress блок — он игнорируется. Edit policy и добавьте Egress.
kubectl edit networkpolicy <NAME>
# Или
kubectl get netpol <NAME> -o yaml > netpol.yaml
# Поправьте, kubectl apply -f netpol.yaml
Самая частая CKAD-ошибка: «написал egress, но не сработало». В 80% случаев — забыли [Egress] в policyTypes. Без явного указания egress блок может молчаливо игнорироваться (поведение зависит от API server version, на 1.35 обычно поправляется эвристикой, но лучше не полагаться).
Шаг 4: AND vs OR в peers
kubectl describe networkpolicy показывает peers таким образом:
Allowing ingress traffic:
To Port: 80/TCP
From:
NamespaceSelector: team=backend
PodSelector: app=web
Без отступа между NamespaceSelector и PodSelector — это OR (два peers). С отступом или явно в одном блоке — AND.
Проще проверять напрямую YAML:
kubectl get netpol <NAME> -o yaml
И смотреть на расположение дефисов в from:
# OR
from:
- namespaceSelector: {...}
- podSelector: {...}
# AND
from:
- namespaceSelector: {...}
podSelector: {...}
Тестовый запуск из source Pod:
kubectl exec -it <SOURCE-POD> -n <SOURCE-NS> -- nc -zv <TARGET-IP> <PORT>
Если timeout — policy не разрешает. Если succeeded — разрешает.
Шаг 5: DNS, DNS и ещё раз DNS
# Внутри Pod тестируем name resolution
kubectl exec -it <POD> -- nslookup postgres.production.svc.cluster.local
# или
kubectl exec -it <POD> -- getent hosts postgres
Если nslookup тоже timeout-ит — это DNS, а не target Service. Проверяем allow-dns policy:
kubectl get netpol -A | grep dns
kubectl describe netpol allow-dns-egress
Должен быть egress на UDP/53 (минимум) в Pods с label k8s-app=kube-dns в kube-system.
Если нет — добавьте policy из урока 03.
Шаг 6: Обе стороны разрешают?
Если у source policy запрещает egress, и/или у target policy запрещает ingress — обе должны разрешить конкретный flow.
# Какие policies применяются к Pod
kubectl describe pod <POD> | grep -A5 "Labels"
kubectl get netpol -n <NS> -o json | jq '.items[] | select(.spec.podSelector.matchLabels // {} | to_entries | all(.value == "ALLOWED-LABEL-VALUE"))'
# Проще — все netpol в ns:
kubectl get netpol -n <NS>
# И смотрим describe на каждый, у которых podSelector сматчится с labels Pod
Стратегия: если есть default-deny-all в namespace, нужны как минимум три policy для каждого flow:
- ingress на target (от source);
- egress на source (к target);
- egress на source (к kube-dns).
Часто люди забывают вторую или третью, и удивляются «policy есть, но не работает».
kubectl describe networkpolicy: что читать
Name: api-allowlist
Namespace: production
Created on: 2026-05-13 10:00:00 +0000 UTC
Labels: <none>
Annotations: <none>
Spec:
PodSelector: app=api
Allowing ingress traffic:
To Port: 8080/TCP
From:
PodSelector: app=web
Allowing egress traffic:
To Port: 5432/TCP
To:
PodSelector: app=postgres
Policy Types: Ingress, Egress
Что проверять в порядке:
- PodSelector — к каким Pods применяется (Шаг 2).
- Policy Types — Ingress, Egress или оба (Шаг 3).
- Allowing ingress / egress traffic — список разрешённых rules. Внимательно на отступы From: вложенные блоки = AND, новый блок без отступа = OR.
Если какого-то rule нет в kubectl describe — значит API server его не разобрал, проверяйте YAML.
Типичные ошибки CKAD
Канонический CKAD-сценарий: «разрешить app к одному Service + DNS»
Задача: в namespace app есть Pod с label app=consumer. Создать NetworkPolicy так, чтобы:
- Consumer мог достучаться только к Pod с label
app=apiв namespacebackendна :8080. - Consumer мог разрешать DNS.
- Никакого другого egress.
Решение — одна policy с двумя egress rules:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: consumer-egress
namespace: app
spec:
podSelector:
matchLabels:
app: consumer
policyTypes:
- Egress
egress:
# Разрешить к api Service в backend
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: backend
podSelector:
matchLabels:
app: api
ports:
- port: 8080
protocol: TCP
# Разрешить DNS
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- port: 53
protocol: UDP
Что критично:
policyTypes: [Egress]— явно, обязательно.namespaceSelector+podSelectorв одном peer (AND): только Pods api в backend, а не «или api в любом ns, или Pods в backend».- DNS — UDP/53. Без него
consumerне resolveтapi.backend.svc.
Шаги проверки после apply:
kubectl exec -it consumer-pod -n app -- nslookup api.backend.svc.cluster.local
# должно работать
kubectl exec -it consumer-pod -n app -- nc -zv api.backend 8080
# должно работать
kubectl exec -it consumer-pod -n app -- nc -zv google.com 443
# должно timeout-ить — это deny по умолчанию
Инструменты сверх kubectl
- calicoctl — для Calico-кластеров.
calicoctl get globalnetworkpolicy,calicoctl get hostendpoint, плюс Calico-specific объекты. - cilium connectivity test — Cilium-агент в namespace
cilium-testподнимает тестовые Pods и автоматически проверяет matrix всех flow. Очень удобно для smoke-test policy после изменения. - kubectl np-viewer plugin —
kubectl np-viewer pod <POD>показывает effective ingress/egress правила для конкретного Pod в человекочитаемом виде. - netshoot image (
nicolaka/netshoot) — заранее установлены tcpdump, dig, mtr, nmap, curl. Запускаем как ephemeral Pod и debug.
kubectl run debug --rm -it --image=nicolaka/netshoot --restart=Never \
-n production -l app=consumer -- bash
# Теперь Pod в namespace production с label app=consumer — applies те же policies, что и реальный consumer
Важный приём: запускайте debug Pod с теми же labels, что target Pod. Тогда policy будет действовать на него, и тесты дают честный ответ. Без labels debug Pod останется non-isolated и будет ходить куда угодно.
Killer момент: kubectl exec — это не Pod-network трафик
Когда вы делаете:
kubectl exec -it pod -- nc -zv target 80
Цепочка:
- ваш CLI → API server (HTTPS, через kubeconfig auth);
- API server → kubelet на узле Pod (HTTPS, через bearer token);
- kubelet → CRI socket → container runtime;
- runtime открывает exec session в namespace контейнера;
- внутри session запускается
nc.
Сам канал «CLI → Pod» не идёт через сеть кластера. Policy его не видит. Если вы пишете NetworkPolicy «запретить весь ingress на Pod» — exec продолжит работать, потому что это control-plane operation.
Что policy видит: только nc → target. То есть исходящий трафик от Pod к target внутри сети — это и есть Pod egress, policy на него действует.
Если хотите запретить exec — это RBAC, не NetworkPolicy. pods/exec subresource в Role/RoleBinding. NetworkPolicy фильтрует dataplane трафик между Pods, не control-plane операции через API server.
Это часто путает CKAD-кандидатов: «применили deny-all, но я могу зайти в Pod» — да, потому что exec идёт через API server, не через Pod-сеть. Тестируйте Pod-Pod трафик через nc/wget изнутри Pod, а не через kubectl exec к target.
Минимальная шпаргалка команд
# Все policies в namespace
kubectl get netpol -n <NS>
# Подробно
kubectl describe netpol <NAME> -n <NS>
# Labels Pod
kubectl get pod <NAME> --show-labels
# Тестовый Pod с labels
kubectl run test --rm -it --image=nicolaka/netshoot --restart=Never \
-l app=consumer -- bash
# Тесты внутри Pod
nslookup api.backend.svc.cluster.local
nc -zv <IP> <PORT>
wget -O- --timeout=3 http://api:8080/healthz
dig +short kube-dns.kube-system.svc
# Labels Pods kube-dns
kubectl get pods -n kube-system -l k8s-app=kube-dns
# Удалить все policies в ns (для быстрого reset на CKAD)
kubectl delete netpol --all -n <NS>