Зачем вообще нужен default deny в Kubernetes
Во многих кластерах Kubernetes модель «разрешено всё» действует до тех пор, пока вы не применили ни одной NetworkPolicy: поды могут общаться друг с другом и выходить наружу (egress) без ограничений. На старте это удобно, но в продакшене повышает риск: скомпрометированный контейнер получает простор для lateral movement и вывода данных.
Паттерн default deny делает сеть «закрытой по умолчанию»: сначала запрещаем всё, затем точечно открываем только те направления, которые реально нужны приложению. В итоге проще аудит и меньше «радиус поражения».
NetworkPolicy влияет на трафик только если ваш CNI-плагин реально применяет политики. Если поддержка отсутствует, манифесты создадутся, но связность не изменится.
Calico и Cilium: что важно знать заранее
Для стандартных Kubernetes NetworkPolicy (ingress/egress + селекторы) и Calico, и Cilium обычно подходят. Но на практике чаще всего всплывают три момента:
- Egress должен быть поддержан CNI (в актуальных Calico/Cilium это норма, но в «упрощённых» установках встречаются ограничения).
- DNS ломается первым: после default deny egress резолв имён перестаёт работать, пока вы явно не разрешите UDP/TCP 53 до CoreDNS/kube-dns или NodeLocal DNSCache.
- Расширения вендоров: Cilium умеет L7/FQDN-политики, у Calico есть свои CRD. Здесь — только стандартная NetworkPolicy, чтобы не привязываться к конкретному CNI.
Как понять, применяются ли NetworkPolicy в вашем кластере
Начните с банального: посмотрите, какой CNI у вас установлен и есть ли его поды в kube-system.
kubectl get nodes -o wide
kubectl get pods -n kube-system -o wide
Для Calico часто увидите calico-node, для Cilium — cilium (DaemonSet) и cilium-operator.
Дальше — быстрый sanity check: примените deny-политику в тестовом namespace и попробуйте «прострелить» соединение между подами. Если связность вообще не изменилась, обычно причина одна из двух: политики не применяются CNI или селекторы не совпали с целевыми подами.

Default deny: базовые политики для ingress и egress
Частая ошибка: включить только запрет входящих (ingress) и ожидать, что исходящий (egress) тоже закроется. В Kubernetes это независимые направления: Ingress и Egress. Для «полной изоляции» задайте оба.
Default deny для ingress (запрет входящих)
Политика ниже запрещает весь входящий трафик ко всем подам в namespace app.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
namespace: app
spec:
podSelector: {}
policyTypes:
- Ingress
Default deny для egress (запрет исходящих)
Эта политика запрещает весь исходящий трафик от всех подов в namespace app.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-egress
namespace: app
spec:
podSelector: {}
policyTypes:
- Egress
Применение:
kubectl apply -f default-deny-ingress.yaml
kubectl apply -f default-deny-egress.yaml
Почему после default deny «падает интернет»: DNS и egress
Почти любое приложение сначала резолвит имена. После default deny egress DNS-запросы до CoreDNS (обычно сервис kube-dns в namespace kube-system) блокируются. Симптомы выглядят как «не подключается база/внешний API», хотя первопричина — отсутствует резолв.
Разрешаем DNS: минимальная egress policy для namespace
Базовый и обычно самый устойчивый вариант — разрешить egress из вашего namespace к подам CoreDNS по UDP и TCP 53. Привязка к IP сервиса тоже возможна, но селекторы обычно долговечнее.
Во многих кластерах CoreDNS помечен лейблом k8s-app: kube-dns. Проверьте это перед применением политики:
kubectl get pods -n kube-system -l k8s-app=kube-dns --show-labels
kubectl get svc -n kube-system kube-dns -o wide
Пример политики, разрешающей DNS только до kube-dns/CoreDNS:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns-egress
namespace: app
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
Если namespaceSelector не матчится (например, нет лейбла kubernetes.io/metadata.name), добавьте собственный лейбл на namespace kube-system и используйте его в matchLabels.
Разрешаем egress наружу: что можно сделать стандартной NetworkPolicy
Стандартная Kubernetes NetworkPolicy ограничивает egress по:
- подам/неймспейсам (через селекторы);
- IP-блокам (через
ipBlock); - портам/протоколам.
При этом она не умеет разрешать трафик «по доменному имени». Если приложение ходит в сторонний API по FQDN, обычно выбирают одно из решений:
- разрешить egress на подсети через
ipBlock(если диапазоны известны и стабильны); - использовать возможности конкретного CNI (например, FQDN-политики) или выводить egress через контролируемую точку (прокси/egress gateway) как часть архитектуры.
Пример: временно разрешить HTTPS «куда угодно» (осознанно)
Иногда нужно быстро вернуть работоспособность и собрать фактический список зависимостей. Тогда можно временно разрешить исходящий трафик на 443 везде, оставив запрет на остальные порты.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-https-egress-any
namespace: app
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to:
- ipBlock:
cidr: 0.0.0.0/0
ports:
- protocol: TCP
port: 443
Это лучше, чем «разрешить всё», но в долгую обычно переходят к allowlist по подсетям или к управляемому egress.
Комбинируем: безопасный базовый набор для namespace
Типовой минимум после включения default deny egress:
- разрешить DNS к kube-dns/CoreDNS (или NodeLocal DNSCache);
- разрешить egress к внутренним сервисам по селекторам (БД, очередь, кэш);
- разрешить egress наружу только на нужные порты и адресные диапазоны.
Для внутреннего доступа удобнее привязываться к лейблам подов, а не к IP. Пример: приложение в namespace app ходит в PostgreSQL в namespace db на 5432, а поды БД имеют лейбл app: postgres.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-egress-to-postgres
namespace: app
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: db
podSelector:
matchLabels:
app: postgres
ports:
- protocol: TCP
port: 5432
Если часть ваших workloads живёт не в Kubernetes (например, выделенная БД или внешний балансировщик), часто удобнее вынести такие компоненты на отдельный VDS и уже его подсети явно добавить в ipBlock allowlist. Так проще контролировать адреса и маршрутизацию egress.
Проверка и диагностика: kubectl describe и быстрые тесты
Классическая ситуация: «правила созданы, но трафик не ходит». Ниже — чек-лист, который обычно находит причину за минуты.
1) Смотрим, какие политики вообще есть
kubectl get networkpolicy -n app
kubectl describe networkpolicy -n app default-deny-egress
kubectl describe networkpolicy -n app allow-dns-egress
В kubectl describe быстро сверяйте:
- policy в нужном namespace (ошибка №1);
podSelectorдействительно выбирает нужные поды (пустой{}означает «все поды»);policyTypesсодержит нужное направление (если ждёте контроль исходящего, должен бытьEgress);- селекторы namespace/pod реально совпадают с лейблами на объектах.
2) Проверяем лейблы, на которые вы ссылаетесь
kubectl get ns --show-labels
kubectl get pods -n kube-system --show-labels
kubectl get pods -n app --show-labels
Частая причина «поломанного DNS»: у CoreDNS другой лейбл, чем вы ожидали, или namespaceSelector не совпал.

3) Запускаем тестовый pod и проверяем DNS/egress
Для диагностики удобно поднять временный pod в проблемном namespace и запускать тесты оттуда.
kubectl run -n app net-debug --rm -it --restart=Never --image=busybox:1.36 -- sh
Внутри pod:
nslookup kubernetes.default.svc.cluster.local
nslookup example.com
wget -S -O - https://example.com 2>&1 | head
Если nslookup не работает — сначала чините DNS policy. Если DNS работает, но HTTPS нет — проверяйте egress правила на 443/подсети и наличие NAT/маршрутизации для выхода из кластера (это уже вне зоны NetworkPolicy).
4) Помним про Service vs Pod: куда реально идёт трафик
NetworkPolicy применяется на уровне pod. Когда вы обращаетесь к сервису (ClusterIP), фактическое соединение устанавливается до выбранного endpoint-pod. Поэтому в egress-правилах чаще нужно разрешать именно pod-цели через podSelector, а не «IP сервиса».
5) Отдельный класс проблем: DNS топология нестандартная
Иногда в кластере включён NodeLocal DNSCache или CoreDNS развёрнут нестандартно. Тогда политика должна отражать реальную схему:
- При NodeLocal DNSCache резолв идёт на локальный IP ноды (часто из link-local диапазона). Может понадобиться egress на этот IP и порт 53 через
ipBlock. - Если CoreDNS в другом namespace или с другими лейблами — исправляйте
namespaceSelector/podSelector.
Рекомендации по эксплуатации: как не превратить политики в «хрупкий пазл»
Договоритесь о лейблах
NetworkPolicy держится на селекторах. Если лейблы ставятся хаотично, политики неизбежно становятся «разовыми» и ломкими. Практичный минимум:
app— имя приложения/компонента;role— web/api/worker/db;teamилиowner— владелец.
Храните политики рядом с приложением
Если манифесты NetworkPolicy лежат рядом с Helm chart/деплойментом, изменения зависимостей (добавился внешний API, поменялась БД) легче отражать сразу в том же PR.
Избегайте «0.0.0.0/0 навсегда»
Широкий egress (например, на 0.0.0.0/0) иногда оправдан как временная мера. Но в проде он часто «прилипает». Лучше хотя бы частично перейти на allowlist: порты + подсети, а затем уточнять диапазоны по реальным потребностям.
Короткий чек-лист troubleshoot NetworkPolicy
- CNI точно поддерживает NetworkPolicy и egress?
- Политика применена в нужном namespace?
podSelectorвыбирает нужные pod?policyTypesсодержит нужное направление (Ingress/Egress)?- DNS разрешён (UDP и TCP 53) до CoreDNS/NodeLocal DNS?
- В правилах вы разрешаете трафик к pod/endpoints (а не к абстрактному «сервису»)?
- Тест изнутри pod подтверждает проблему: сначала
nslookup, затемwgetилиnc?
Итог
Kubernetes NetworkPolicy — рабочий способ ввести минимально необходимую сетевую модель: default deny + точечные разрешения для ingress/egress. В 80% случаев после запрета исходящего трафика первым делом нужно явно открыть DNS (UDP/TCP 53) и убедиться, что селекторы совпадают с реальными лейблами. Для диагностики держите под рукой kubectl describe и тестовый pod: это самый быстрый путь понять, где именно пропала связность.


