Типовые паттерны NetworkPolicy
На CKAD у вас будет не больше 5-10 минут на каждую задачу с NetworkPolicy. Не время изобретать структуру. Время применять шаблоны. В этом уроке — набор паттернов, которые покрывают 90% задач: deny-all, allow-only-specific, cross-namespace, allow-DNS. Запомнить наизусть.
Типичные сетевые атаки: MITM, ARP poisoning, DNS spoofing
Паттерн 1: deny-all ingress в namespace
Запрещает любой входящий трафик ко всем Pods в namespace. Egress не затронут.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
namespace: production
spec:
podSelector: {}
policyTypes:
- Ingress
Почему это deny-all: podSelector: {} matches все Pods, policyTypes: [Ingress] делает их isolated по входящему, ingress блок отсутствует — значит, нет разрешённых источников. Pods становятся недоступными для всех остальных. Это baseline для namespace с zero-trust моделью: ничего не работает, пока вы явно не разрешите.
Это не запрещает egress. Pods из этого namespace могут выходить наружу, могут резолвить DNS, могут стучаться к другим Service. Они просто не принимают входящих соединений.
Паттерн 2: deny-all egress в namespace
Симметрично, запрещает любой исходящий трафик.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-egress
namespace: production
spec:
podSelector: {}
policyTypes:
- Egress
Сам по себе deny-all egress сломает DNS для всех Pods в namespace. Application пытается connect("postgres.db.svc.cluster.local") — нужен UDP/53 в kube-dns в kube-system. Policy блокирует. Connection refused, или timeout. Никогда не применяйте deny-all egress без сопровождающей allow-dns policy.
Паттерн 3: full deny-all (ingress + egress)
Полная изоляция всех Pods в namespace.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: production
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
Это самая безопасная starting point. Все приложения сломаются мгновенно — и это правильно. Дальше вы по очереди добавляете allow-policies для каждого реального flow трафика. Подход называется default-deny-then-allow и считается best practice для production namespaces.
Паттерн 4: allow DNS egress (обязательный!)
Без этого правила deny-all egress смертелен. DNS в кластере работает через CoreDNS (или kube-dns) в namespace kube-system. Pod-ы общаются с ним по UDP/TCP на порт 53.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns-egress
namespace: production
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- port: 53
protocol: UDP
- port: 53
protocol: TCP
Что здесь важно:
namespaceSelector+podSelectorв одном peer (AND): Pods с labelk8s-app=kube-dnsИ в namespacekube-system. Без AND вы рисковали бы разрешить любой Pod сk8s-app=kube-dnsв любом namespace.- Порт 53 разрешён по обоим протоколам. Запросы DNS обычно UDP, но крупные ответы (DNSSEC, длинные TXT) — TCP fallback.
- Label
k8s-app: kube-dns— это конвенция CoreDNS в kube-system; в managed Kubernetes может отличаться. Проверяйте черезkubectl get pods -n kube-system --show-labels.
DNS — это #1 источник ошибок CKAD с NetworkPolicy. Если приложение использует Service names — оно делает getaddrinfo, который идёт в DNS. Без egress на kube-dns ничего не работает, и timeout выглядит как «netpol запрещает Service», хотя реально упало раньше — на name resolution.
Паттерн 5: allow app → db, конкретный порт
Канонический паттерн микросервисной защиты: разрешить только web достучаться до postgres на 5432.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: postgres-ingress
namespace: production
spec:
podSelector:
matchLabels:
app: postgres
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: web
ports:
- port: 5432
protocol: TCP
Что эта policy делает:
- targets — Pods с label
app=postgresв namespaceproduction; - они становятся isolated по ingress;
- разрешён ingress от Pods с label
app=web(в этом же namespace) на TCP/5432.
Важно: эта policy только ingress на postgres. Чтобы web мог реально общаться, у web не должно быть egress policy, которая блокирует исходящий на postgres. Если есть default-deny-egress — нужна симметричная egress policy на web.
Паттерн 6: симметричная egress policy на web
В zero-trust namespace, где есть deny-all egress, на web нужно разрешить выход к postgres:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: web-egress-to-postgres
namespace: production
spec:
podSelector:
matchLabels:
app: web
policyTypes:
- Egress
egress:
- to:
- podSelector:
matchLabels:
app: postgres
ports:
- port: 5432
protocol: TCP
# И обязательно DNS, иначе web не разрешит postgres.production.svc
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- port: 53
protocol: UDP
- port: 53
protocol: TCP
В одной policy два egress rules, объединённых OR: разрешён egress либо к postgres:5432, либо к kube-dns:53. Всё остальное запрещено.
Принцип «двусторонней policy»: для каждого реального flow вам нужны обе стороны — ingress на target и egress на source. Если только одна из них isolated — этого достаточно. Если обе — обе должны разрешить. Это базовая отладочная гипотеза при «policy применил, не работает».
Паттерн 7: allow-all ingress (override deny-all)
Иногда нужно для одной группы Pods разрешить любой ingress, переопределив namespace-wide deny-all.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: public-ingress
namespace: production
spec:
podSelector:
matchLabels:
role: public-gateway
policyTypes:
- Ingress
ingress:
- {}
ingress: - {} — список из одного rule, который не указывает ни from, ни ports. Это значит «разрешить любой ingress на любой порт от кого угодно». В сочетании с deny-all namespace-policy на role=public-gateway Pods получается «защищены все, кроме gateway».
Паттерн 8: cross-namespace ingress
Разрешить frontend-namespace достучаться до api в backend-namespace.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: api-from-frontend
namespace: backend
spec:
podSelector:
matchLabels:
app: api
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: frontend
podSelector:
matchLabels:
app: web
ports:
- port: 8080
protocol: TCP
- policy в namespace
backend(там, где live target Pods api); - targets —
app=api; - разрешён ingress от Pods
app=webв namespacefrontend(AND внутри одного peer — это критично, см. предыдущий урок); - порт 8080 TCP.
Не забывайте kubernetes.io/metadata.name. Без явной лейблировки namespace его не получится отличить друг от друга. С 1.22+ этот label автоматически выставляется kubelet-ом, можно полагаться.
Паттерн 9: allow Internet egress, deny intra-cluster
Распространённое требование: Pod должен ходить наружу (S3, API третьей стороны), но не должен сканировать внутреннюю сеть кластера.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: external-only-egress
namespace: jobs
spec:
podSelector:
matchLabels:
app: scraper
policyTypes:
- Egress
egress:
- to:
- ipBlock:
cidr: 0.0.0.0/0
except:
- 10.0.0.0/8 # RFC1918 — типичный Pod/Service CIDR
- 172.16.0.0/12 # RFC1918
- 192.168.0.0/16 # RFC1918
- 169.254.169.254/32 # AWS metadata (защита от SSRF)
# DNS всё ещё нужен
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- port: 53
protocol: UDP
Пояснение: cidr: 0.0.0.0/0 разрешает все IPv4 адреса, except вычитает из этого все приватные подсети. Получается «только Internet». Метаданные облака — частая SSRF мишень, исключение 169.254.169.254/32 — это hardening.
Killer момент: deny-all egress без DNS
Это главная ловушка CKAD по NetworkPolicy. Сценарий:
- Применяете
default-deny-allна namespace. - Pod пытается достучаться до Service
postgres.production.svc.cluster.local. - Pod делает getaddrinfo → DNS-запрос на kube-dns:53 → DROP.
- Pod видит «name resolution failure», fail.
- Вы пишете allow-policy на postgres:5432.
- Pod продолжает падать. Логи приложения:
dial tcp: lookup postgres.production.svc.cluster.local: no such host.
Решение: всегда после deny-all egress сразу добавляйте allow-dns policy. На CKAD это kubectl apply -f двух YAML файлов один за другим — даже если в задаче DNS не упомянут.