Learning Platform
Глоссарий Troubleshooting
Урок 11.03 · 25 мин
Продвинутый
NetworkPolicydeny-allallow-dnskube-dnscross-namespace

Типовые паттерны 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 моделью: ничего не работает, пока вы явно не разрешите.

NOTE

Это не запрещает 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
DANGER

Сам по себе 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 с label k8s-app=kube-dns И в namespace kube-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.
WARNING

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 в namespace production;
  • они становятся isolated по ingress;
  • разрешён ingress от Pods с label app=web (в этом же namespace) на TCP/5432.
Эффект postgres-ingress
Pod webPod с label app=web. Не затронут этой policy. Может ходить куда угодно (default-allow egress, если нет других policies).
5432 OK
Pod postgresPod с label app=postgres. Isolated по ingress. Принимает только от Pods app=web на 5432. Любой другой трафик блокируется.
Pod analyticsPod без label app=web. Не имеет права достучаться до postgres по policy. Запрос будет тихо отброшен (drop), приложение увидит timeout.
5432 DROP
Pod postgresТот же postgres Pod. Для analytics он недоступен.

Важно: эта 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. Всё остальное запрещено.

TIP

Принцип «двусторонней 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 в namespace frontend (AND внутри одного peer — это критично, см. предыдущий урок);
  • порт 8080 TCP.
WARNING

Не забывайте 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. Сценарий:

  1. Применяете default-deny-all на namespace.
  2. Pod пытается достучаться до Service postgres.production.svc.cluster.local.
  3. Pod делает getaddrinfo → DNS-запрос на kube-dns:53 → DROP.
  4. Pod видит «name resolution failure», fail.
  5. Вы пишете allow-policy на postgres:5432.
  6. Pod продолжает падать. Логи приложения: dial tcp: lookup postgres.production.svc.cluster.local: no such host.

Решение: всегда после deny-all egress сразу добавляйте allow-dns policy. На CKAD это kubectl apply -f двух YAML файлов один за другим — даже если в задаче DNS не упомянут.

Цепочка после deny-all egress
Pod appХочет подключиться к Service postgres. Имеет IP DNS-сервера в /etc/resolv.conf, формирует UDP-запрос на 10.96.0.10:53.
UDP/53 → kube-dns
NetworkPolicy egressdefault-deny-all для всех Pods в ns. UDP/53 не разрешён. Drop пакета на iptables/eBPF.
getaddrinfo fails
Pod app errorgetaddrinfo вернул EAI_AGAIN или timeout. Приложение интерпретирует это как «postgres недоступен». В логах часто misleading сообщение про connection refused, хотя реально упало раньше — на DNS.

Проверка знанийKnowledge check
В namespace применили только default-deny-all. Pod не может подключиться к Service по имени. Что добавить?
ОтветAnswer
Allow-DNS egress policy: разрешить UDP/53 (и TCP/53) к Pods с label k8s-app=kube-dns в namespace kube-system. Без DNS приложения не резолвят Service names и видят name resolution failure. Это #1 ошибка CKAD.
Проверка знанийKnowledge check
Хотите разрешить только web → postgres на :5432. Что нужно создать?
ОтветAnswer
NetworkPolicy на podSelector app=postgres, policyTypes [Ingress], ingress.from с podSelector app=web и ports 5432/TCP. Если в namespace есть default-deny-egress, дополнительно — policy на app=web с egress to app=postgres :5432 И egress к kube-dns на :53.
Проверка знанийKnowledge check
Какая разница между ingress: - {} и отсутствующим блоком ingress (при policyTypes: [Ingress])?
ОтветAnswer
ingress: - {} — список с одним пустым rule, разрешает всё (любой источник, любой порт). Отсутствующий ingress — список пустой, никаких разрешений, всё запрещено (default-deny). Первое часто используется для override deny-all для конкретного Pod.
Проверка знанийKnowledge check
Pod-Pod трафик идёт по PodIP, но Service адресуется по ClusterIP. На какой IP сматчится egress ipBlock?
ОтветAnswer
На PodIP backend, не на ClusterIP. kube-proxy подменяет ClusterIP на конкретный PodIP через DNAT раньше, чем пакет видит policy enforcement. Поэтому ipBlock-based egress правила для intra-cluster Services работают через Pod CIDR, а не Service CIDR.

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

Результат: 0 из 0
Прикладной
Вопрос 1 из 5. Минимальный YAML, чтобы запретить весь ingress trafic в namespace production (deny-all-ingress)?

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

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

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

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