← Back to notes

NetworkPolicy: сетевая изоляция
Pod в Kubernetes

2026-04-26


По умолчанию сеть в Kubernetes полностью открытая: любой Pod может обращаться к любому другому Pod в кластере, независимо от namespace. Это flat network model — удобно для разработки, но неприемлемо для продакшена с несколькими командами или микросервисами, где сервисы не должны видеть друг друга без явного разрешения.

NetworkPolicy — механизм для ограничения этого трафика на уровне L3/L4 (IP-адреса, порты, протоколы). Работает на уровне Pod, а не сервисов: правила применяются к конкретным Pod через label-селекторы.

[source: kubernetes.io/docs/concepts/services-networking/network-policies/]

Зависимость от CNI

NetworkPolicy — объект Kubernetes API, но его реализацию берёт на себя CNI-плагин. Это важно: если CNI не поддерживает NetworkPolicy — объекты создаются без ошибок, правила синтаксически корректны, но в реальности трафик не блокируется. Кластер молча игнорирует политики.

Перед применением NetworkPolicy убедись, что CNI её поддерживает. В кластерах на чистом Flannel NetworkPolicy создаётся без ошибки, но ничего не блокирует — самый опасный вариант, потому что создаётся иллюзия безопасности.

Проверить поддержку:

# Посмотреть CNI-плагин
kubectl get pods -n kube-system | grep -E 'calico|cilium|flannel|weave'

# Для Cilium  проверить статус
kubectl -n kube-system exec ds/cilium -- cilium status

Логика применения

Три ключевых правила, которые нужно понять один раз:

Правило 1: Если ни одна NetworkPolicy не выбирает Pod — весь трафик к нему и от него разрешён (default allow). Это поведение "по умолчанию" в Kubernetes.

Правило 2: Как только хотя бы одна NetworkPolicy выбирает Pod — для каждого указанного policyTypes (Ingress/Egress) всё запрещено, кроме явно разрешённого в правилах этой и других политик.

Правило 3: Политики аддитивны. Если несколько NetworkPolicy выбирают один Pod — результирующий набор правил равен объединению всех разрешений. Нельзя одной политикой "отменить" разрешение из другой.

Практический вывод: нельзя запретить что-то явно. Можно только не разрешать. Для полной изоляции нужно сначала применить deny-all, а потом явно разрешать нужные пути.

Структура NetworkPolicy

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: example-policy
  namespace: app
spec:
  podSelector:        # к каким Pod применяется эта политика
    matchLabels:
      role: backend
  policyTypes:        # какие направления регулируем (всегда указывай явно)
  - Ingress
  - Egress
  ingress:            # правила входящего трафика
  - from:
    - podSelector:
        matchLabels:
          role: frontend
    ports:
    - protocol: TCP
      port: 8080
  egress:             # правила исходящего трафика
  - to:
    - podSelector:
        matchLabels:
          role: database
    ports:
    - protocol: TCP
      port: 5432

Три способа указать источник или назначение трафика:

Поле policyTypes лучше всегда указывать явно. Без него поведение определяется наличием секций ingress/egress: если есть только ingress — только Ingress регулируется, Egress остаётся открытым. Явное указание устраняет неоднозначность.

Default Deny All Ingress

Первый шаг при создании изолированного namespace — запретить весь входящий трафик:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-ingress
  namespace: app
spec:
  podSelector: {}     # пустой selector = все Pod в namespace
  policyTypes:
  - Ingress
  # секция ingress отсутствует = ничего не разрешено

После применения все Pod в namespace app недоступны по сети со стороны других Pod и внешних источников. Egress при этом остаётся открытым — Pod могут инициировать исходящие соединения.

Default Deny All Egress

Аналогично для исходящего трафика:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-egress
  namespace: app
spec:
  podSelector: {}
  policyTypes:
  - Egress

Критичный момент: после deny egress Pod не сможет обращаться к DNS (CoreDNS работает на порту 53 UDP/TCP). Это ломает name resolution — curl http://my-service перестаёт работать, хотя IP-подключение разрешено. Всегда добавляй явное разрешение для DNS сразу:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-dns-egress
  namespace: app
spec:
  podSelector: {}
  policyTypes:
  - Egress
  egress:
  - ports:
    - protocol: UDP
      port: 53
    - protocol: TCP
      port: 53

Обрати внимание: здесь нет секции to — разрешён egress на порт 53 к любому адресу. Это нужно для резолвинга как внутренних сервисов, так и внешних DNS.

[source: kubernetes.io/docs/concepts/services-networking/network-policies/#default-deny-all-egress-traffic]

Разрешить трафик между конкретными Pod

Frontend к backend на порт 8080:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-frontend-to-backend
  namespace: app
spec:
  podSelector:
    matchLabels:
      role: backend
  policyTypes:
  - Ingress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          role: frontend
    ports:
    - protocol: TCP
      port: 8080

Политика применяется к Pod с label role: backend и разрешает входящий трафик только от Pod с label role: frontend на порт 8080. Всё остальное запрещено для этих Pod (потому что политика теперь их выбирает).

Разрешить трафик из другого namespace

Сценарий: monitoring-агент в namespace monitoring должен собирать метрики с Pod в namespace app.

Начиная с Kubernetes 1.22, все namespace автоматически получают label kubernetes.io/metadata.name: <name>. Используй его — не нужно вручную навешивать label на namespace:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-from-monitoring
  namespace: app
spec:
  podSelector:
    matchLabels:
      role: backend
  policyTypes:
  - Ingress
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          kubernetes.io/metadata.name: monitoring
    ports:
    - protocol: TCP
      port: 9090

Если используешь namespaceSelector без kubernetes.io/metadata.name, нужно вручную поставить label на namespace:

kubectl label namespace monitoring env=monitoring

AND vs OR в правилах from/to

Это самая частая ошибка при написании NetworkPolicy. Разница определяется положением селекторов в YAML — в одном элементе массива или в разных.

AND — оба условия должны совпасть одновременно. Pod с label role: frontend и в namespace с label env: prod. Оба селектора в одном элементе (один дефис):

ingress:
- from:
  - podSelector:          # один элемент массива
      matchLabels:
        role: frontend
    namespaceSelector:    # в том же элементе (нет дефиса перед namespaceSelector)
      matchLabels:
        env: prod

OR — любое из условий подходит. Любой Pod с label role: frontend или любой Pod из namespace с label env: prod. Каждый селектор — отдельный элемент (свой дефис):

ingress:
- from:
  - podSelector:          # первый элемент
      matchLabels:
        role: frontend
  - namespaceSelector:    # второй элемент (свой дефис)
      matchLabels:
        env: prod

Разница в одном символе — дефис перед namespaceSelector. Смысл противоположный. Ошибку сложно заметить визуально, но последствия критичны — можно случайно разрешить трафик от всего namespace вместо конкретных Pod.

[source: kubernetes.io/docs/concepts/services-networking/network-policies/#behavior-of-to-and-from-selectors]

Комбинированная политика: полная изоляция backend

Backend принимает только от frontend, отправляет только к database и DNS:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: backend-policy
  namespace: app
spec:
  podSelector:
    matchLabels:
      role: backend
  policyTypes:
  - Ingress
  - Egress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          role: frontend
    ports:
    - protocol: TCP
      port: 8080
  egress:
  - to:
    - podSelector:
        matchLabels:
          role: database
    ports:
    - protocol: TCP
      port: 5432
  - ports:              # DNS  без ограничения по to
    - protocol: UDP
      port: 53
    - protocol: TCP
      port: 53

Внешний трафик через ipBlock

Разрешить egress к конкретному внешнему API:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-external-api
  namespace: app
spec:
  podSelector:
    matchLabels:
      role: backend
  policyTypes:
  - Egress
  egress:
  - to:
    - ipBlock:
        cidr: 203.0.113.0/24
        except:
        - 203.0.113.10/32   # исключить конкретный IP из диапазона
    ports:
    - protocol: TCP
      port: 443

ipBlock работает с IP-адресами вне кластера. Для Pod-IP внутри кластера используй podSelector/namespaceSelectoripBlock для внутреннего трафика ненадёжен, потому что IP Pod могут меняться.

Отладка NetworkPolicy

Инструментов для отладки NetworkPolicy в стандартном kubectl немного. Практические подходы:

# Запустить временный Pod-клиент и проверить соединение вручную
kubectl run nettest --image=busybox:1.36 -n app --rm -it \
  --labels="role=frontend" -- sh

# Внутри пода:
# wget -qO- http://backend-service:8080/health
# nc -zv backend-service 8080

# Посмотреть применённые NetworkPolicy в namespace
kubectl get networkpolicies -n app

# Детально посмотреть конкретную политику
kubectl describe networkpolicy backend-policy -n app

Для Cilium доступна более богатая диагностика:

# Показать, почему трафик заблокирован/разрешён
kubectl -n kube-system exec ds/cilium -- \
  cilium policy trace --src-k8s-pod app:frontend-pod --dst-k8s-pod app:backend-pod

Когда использовать

Изоляция namespace по умолчанию — стандартная практика для prod-кластеров с несколькими командами. Применяй default-deny-ingress и default-deny-egress в каждом namespace, затем явно открывай нужные пути. Это требует дисциплины, но даёт контроль.

Микросегментация — явные политики между микросервисами. Frontend → backend → database, но не frontend → database напрямую. Снижает радиус поражения при компрометации одного компонента.

Изоляция между командами — разные команды в разных namespace не должны видеть друг друга по сети. NetworkPolicy + RBAC дают полную изоляцию.

Не нужна, если кластер имеет один namespace и одно приложение, или это dev/staging кластер с минимальными требованиями к безопасности, или CNI не поддерживает NetworkPolicy.

Типичные ошибки

Flannel без Calico — правила молча не работают. Самая опасная ошибка: NetworkPolicy создана, команда думает, что трафик ограничен, а на деле ничего не изменилось. Проверяй CNI перед применением в новом кластере.

Забыть разрешить DNS после deny egress. После default-deny-egress сервисы не могут резолвить имена. Всегда добавляй allow для порта 53 UDP/TCP. Симптом: curl http://service-name зависает, curl http://10.96.0.1:8080 (прямой IP) работает.

Перепутать AND/OR (разница в дефисе YAML). Проверяй написанное правило вручную: запусти Pod с нужными label и без них, убедись, что трафик блокируется/разрешается корректно.

Не указать policyTypes явно. Без policyTypes поведение неочевидно. Если есть только ingress-секция — Egress остаётся полностью открытым. Указывай policyTypes всегда.

Забыть, что podSelector в from/to выбирает Pod в namespace самой NetworkPolicy. Если хочешь разрешить Pod из другого namespace — нужен namespaceSelector, иначе podSelector игнорирует чужие namespace.

ipBlock для внутрикластерного трафика. IP Pod нестабильны и меняются при перезапуске. Для трафика между Pod внутри кластера используй только podSelector/namespaceSelector.

Альтернативы

CiliumNetworkPolicy — расширение от Cilium, добавляет L7-правила на уровне HTTP, gRPC, Kafka. Позволяет написать "разрешить только GET /api/v1/health от frontend" вместо "разрешить TCP 8080". Требует Cilium как CNI. Работает рядом со стандартным NetworkPolicy.

Service Mesh (Istio, Linkerd) — mTLS между всеми сервисами и политики на уровне L7 через AuthorizationPolicy (Istio). Работает через sidecar-прокси, независимо от CNI. Более мощно: можно ограничивать по HTTP-методу, пути, JWT-claims. Значительно сложнее в эксплуатации и добавляет latency.

Egress Gateway — паттерн для контроля исходящего трафика кластера. Весь egress проходит через единую точку (Istio Egress Gateway или отдельный proxy). Упрощает аудит, позволяет применять единую политику для внешних зависимостей.

PodSecurity Standards — не прямая альтернатива, но дополняет сетевую изоляцию: запрещает hostNetwork: true (Pod с hostNetwork обходит NetworkPolicy), hostPort, привилегированные контейнеры.

[source: kubernetes.io/docs/concepts/services-networking/network-policies/#behavior-of-to-and-from-selectors]


16: ServiceAccount | 18: Scheduling Constraints

NetworkPolicy: сетевая изоляция Pod в Kubernetes | Aleksandr Suprun