Где именно блокируется IP в Kubernetes: три уровня
Сетевой путь запроса в кластере проходит три зоны фильтрации, и каждая режет трафик своими средствами: NetworkPolicy внутри пода, iptables или IPVS на ноде, правила Ingress и облачного LoadBalancer снаружи. Диагностику начинайте с определения границы, до которой пакет ещё доходит.
Практическая примета для быстрого разделения: если curl из одного пода в другой висит, а тот же адрес и порт отвечает с ноды, проблема на уровне пода. Если с ноды тоже нет ответа, смотрите kube-proxy, локальный firewall и маршруты. Если изнутри кластера всё открывается, а снаружи нет, ищите причину на балансировщике и в Ingress.
| Уровень | Что блокирует | Где смотреть | Типичный симптом |
|---|---|---|---|
| Под | NetworkPolicy, которую применяет CNI (Calico, Cilium) | kubectl get networkpolicy -A, kubectl describe networkpolicy, логи пода CNI | Запрос из пода в под зависает, tcpdump показывает SYN без SYN-ACK |
| Нода | kube-proxy (iptables или IPVS), firewalld, ufw, security group облака | iptables-save, conntrack, kubectl get svc -o wide, kubectl get endpoints | NodePort не отвечает, соединение сбрасывается, Service без endpoints |
| Внешний балансировщик | Ingress-контроллер, облачный LoadBalancer, edge-firewall | kubectl get ingress -A, kubectl describe ingress, аннотации, логи контроллера | HTTP 403 от Ingress, сервис работает из кластера и недоступен снаружи |
Поды с hostNetwork: true используют сетевой стек ноды и не имеют собственного pod network namespace. По документации Kubernetes поведение NetworkPolicy для таких подов не определено, и на практике большинство CNI не применяют к ним политики, привязанные к интерфейсам подов. Поэтому правило может существовать, а трафик всё равно проходить. Такое поведение стоит отдельно проверить для вашего CNI.
Держите в голове два диапазона адресов: podCIDR, из которого выдаются IP подам, и nodeCIDR с адресами нод. В правилах ipBlock указывают именно их, а не DNS-имена сервисов.
Уровень пода: NetworkPolicy и CNI
NetworkPolicy это объект API networking.k8s.io/v1. Он выбирает поды по меткам через podSelector и описывает разрешённые направления в блоках ingress и egress. Правила нескольких политик, выбирающих один и тот же набор подов, складываются аддитивно: ingress-правила каждой политики комбинируются. Запрет получается только тогда, когда ни одна политика трафик не разрешает.
Если в namespace нет ни одной политики, весь ingress- и egress-трафик к подам и от них разрешён по умолчанию. Default-deny ingress создаётся политикой, которая выбирает все поды namespace, но не содержит разрешающих ingress-правил.
Сам API-сервер трафик не фильтрует. Блокировку выполняет CNI: Calico применяет правила через компонент Felix, который пишет цепочки iptables или, в eBPF-режиме, карты ядра. Cilium применяет политики eBPF-программами, привязанными к сетевым интерфейсам подов. Если CNI не поддерживает NetworkPolicy, объекты создаются и проходят валидацию, но не влияют ни на что.
Проверить, какой плагин установлен и поддерживает ли он политики:
kubectl get pods -n kube-system | grep -E 'calico|cilium|weave|flannel'
Пример политики, которая закрывает весь входящий трафик в namespace production:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
namespace: production
spec:
podSelector: {}
policyTypes:
- Ingress
Пустой podSelector соответствует всем подам в namespace политики, а пустой список ingress не разрешает ничего. Список политик и подробности по конкретной:
kubectl get networkpolicy -A
kubectl describe networkpolicy default-deny-ingress -n production
Синтаксис NetworkPolicy и семантику полей сверяйте с официальной документацией Kubernetes: NetworkPolicies и NetworkPolicy v1 API.
Уровень ноды: kube-proxy, iptables и NodePort
kube-proxy превращает объекты Service в правила DNAT: в режиме iptables это цепочки KUBE-SERVICES и KUBE-SVC-*, в режиме IPVS это виртуальные сервисы ядра. Разбор различий и команд диагностики есть в руководстве по маршрутизации трафика между подами и сервисами.
Если Service не отвечает, сначала проверьте, есть ли у него endpoints. Пустой список означает, что селектор не совпал с метками подов или поды не прошли readiness-пробу, и NetworkPolicy здесь не при чём:
kubectl get svc -o wide -n production
kubectl describe svc api -n production
kubectl get endpoints api -n production
Локальные фильтры ноды смотрят отдельно. Классический сценарий: NodePort открыт в кластере, но порт из диапазона 30000-32767 закрыт в security group облака или в firewalld на ноде.
iptables-save | grep -n 203.0.113.10
Уровень внешнего балансировщика: Ingress и LoadBalancer
Ingress-контроллер умеет резать трафик собственными правилами. У ingress-nginx ограничение доступа по IP задаётся аннотацией nginx.ingress.kubernetes.io/whitelist-source-range, значением которой служит список CIDR. Облачный LoadBalancer добавляет ещё один слой: security group, network ACL и firewall провайдера.
Посмотреть все Ingress и их аннотации:
kubectl get ingress -A
kubectl describe ingress api -n production
kubectl logs -n ingress-nginx deploy/ingress-nginx-controller --tail=100
Пример из практики: клиент получает HTTP 403 от Ingress-контроллера, при этом NetworkPolicy разрешает трафик с его адреса. Причина в whitelist-source-range, который ограничивает доступ корпоративной подсетью. Описание аннотации приведено в документации Ingress-Nginx Controller.
NetworkPolicy в Kubernetes: как посмотреть и настроить блокировку IP
По умолчанию в кластере нет default-deny: пока политик нет, все поды общаются между собой свободно. Запрет появляется только после создания политики, которая выбирает поды и не разрешает нужное направление.
Команды kubectl для вывода NetworkPolicy
Базовый набор команд, который закрывает большинство задач просмотра:
- kubectl get networkpolicy -A: список всех политик во всех namespace.
- kubectl describe networkpolicy <имя> -n <ns>: podSelector, policyTypes, ingress, egress, ipBlock и порты.
- kubectl get networkpolicy -A -o yaml: полный YAML для аудита и переноса в Git.
- kubectl get pods -n <ns> --show-labels: метки подов, чтобы понять, кого выбирает политика.
- kubectl get pods -o wide -A: IP подов и ноды, на которых они работают.
Блок ipBlock в выводе describe показывает, какие подсети разрешены, а какие исключены через except. Если подсеть клиента не попала ни в один разрешающий блок, соединение будет отброшено.
Пример YAML: блокировка IP на ingress и egress
Политика ниже разрешает входящий трафик к подам app=api только из корпоративной сети 10.0.0.0/8, кроме подсети 10.10.0.0/16, и только на порт 8080. Всё остальное блокируется.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-corp-only
namespace: production
spec:
podSelector:
matchLabels:
app: api
policyTypes:
- Ingress
ingress:
- from:
- ipBlock:
cidr: 10.0.0.0/8
except:
- 10.10.0.0/16
ports:
- protocol: TCP
port: 8080
Для исходящего трафика логика та же, но правило описывается в egress. Пример политики, которая запрещает подам app=api ходить в подсеть 203.0.113.0/24:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-egress-to-range
namespace: production
spec:
podSelector:
matchLabels:
app: api
policyTypes:
- Egress
egress:
- to:
- ipBlock:
cidr: 0.0.0.0/0
except:
- 203.0.113.0/24
Применение и проверка:
kubectl apply -f policy.yaml
kubectl describe networkpolicy deny-egress-to-range -n production
Комбинируйте селекторы, когда нужно соединить внутренние и внешние источники:
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: monitoring
- podSelector:
matchLabels:
role: jump-host
- ipBlock:
cidr: 198.51.100.0/24
Условия внутри одного элемента from складываются по ИЛИ, а namespaceSelector и podSelector в одном элементе списка работают по И. ipBlock описывает CIDR (например, 192.168.1.0/24 или 2001:db8::/64), а поле except задаёт CIDR, исключаемые из правила; DNS-имена ipBlock не понимает. Поле endPort задаёт диапазон портов от port до endPort включительно, разрешённый политикой; его поддержка зависит от CNI.
Если поле policyTypes не указано, оно по умолчанию определяется наличием ingress- и egress-правил: политики с секцией egress считаются влияющими на egress, а все политики — на ingress.
Default-deny и типичные ошибки
Самая жёсткая и самая полезная конфигурация: политика с пустым podSelector и policyTypes Ingress и Egress без единого правила. Она блокирует все входящие и исходящие соединения выбранных подов.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: production
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
Дальше выдавайте разрешения точечно. Схема с default-deny и постепенным расширением прав разобрана в материале о безопасности Kubernetes: RBAC, NetworkPolicies и управлении секретами.
Ошибки, которые чаще всего ломают работающий сервис после включения политик:
- Не разрешён DNS. После default-deny поды не резолвят имена, потому что исходящие запросы на 53-й порт в kube-system заблокированы. Добавьте отдельное egress-правило с namespaceSelector по метке kubernetes.io/metadata.name: kube-system и портами 53 UDP и TCP.
- Не разрешён трафик от Ingress-контроллера. Поды приложения получают запросы из namespace с контроллером, и без правила from по namespaceSelector внешние запросы отбиваются на уровне CNI.
- Расчёт на hostNetwork. Как сказано выше, такие поды живут в сети ноды, и большинство CNI не применяют к ним под сетевые политики.
- Забытая нумерация портов. Приложение слушает 8080, а политика разрешает 80, и соединение отбивается без внятного сообщения в логах приложения.
Перед применением в проде проверяйте манифест на стороне сервера и держите план отката:
kubectl apply -f policy.yaml --dry-run=server
kubectl delete networkpolicy default-deny-all -n production
Сначала прогоняйте политики в staging на копии манифестов приложения, потом переносите в прод. Полезно также проверять селекторы в визуальном редакторе NetworkPolicy, если такой есть в вашей консоли управления кластером, но применять изменения только через Git.
CNI и блокировка IP: Calico и Cilium
Разница между плагинами проявляется в том, как именно применяется правило и что вы можете увидеть при отладке.
| CNI | Механизм применения | CLI и API | Особенности |
|---|---|---|---|
| Calico | Felix, iptables или eBPF | calicoctl, kubectl, FelixConfiguration | GlobalNetworkPolicy с order и правилами action, BGP-маршруты |
| Cilium | eBPF-программы в ядре | cilium, hubble | CiliumNetworkPolicy с ограничениями по CIDR, наблюдение за дропами через Hubble |
Микросегментация с примерами политик для обоих плагинов и стратегией внедрения без остановки продакшена разобрана в гайде по микросегментации Kubernetes и модели Zero Trust с Cilium и Calico.
Calico: GlobalNetworkPolicy и Felix
Calico понимает стандартные NetworkPolicy, а сверху даёт GlobalNetworkPolicy — не namespaced ресурс, представляющий упорядоченный набор правил, которые применяются к коллекции endpoints, соответствующих selector. Порядок вычисления задаётся полем order: Calico применяет политику с наименьшим значением первой. Правила внутри политики выполняются по порядку, и каждое правило применяет некоторое действие (action) к совпавшим пакетам.
apiVersion: projectcalico.org/v3
kind: GlobalNetworkPolicy
metadata:
name: deny-egress-to-range
spec:
order: 100
selector: app == "api"
types:
- Egress
egress:
- action: Deny
destination:
nets:
- 203.0.113.0/24
Просмотр конфигурации и активных правил:
calicoctl get networkpolicy -A
calicoctl get globalnetworkpolicy
calicoctl get policy -o yaml
kubectl get felixconfiguration default -o yaml
Поле в FelixConfiguration подскажет, работает ли кластер в eBPF-режиме или на iptables. От этого зависит, где искать правила: в цепочках cali-* или в картах ядра. Описание ресурса приведено в документации Calico Global network policy.
Cilium: eBPF и Hubble
Cilium применяет политики на уровне ядра. Политика может ограничивать трафик по CIDR: добавление префикса в FromCIDR или в FromCIDRSet без ExcludeCIDRs эквивалентно, а FromCIDRSet поддерживает ExcludeCIDRs. Расширенная политика с ограничением по CIDR выглядит так:
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: allow-api-from-corp
namespace: production
spec:
endpointSelector:
matchLabels:
app: api
ingress:
- fromCIDRSet:
- cidr: 10.0.0.0/8
except:
- 10.10.0.0/16
toPorts:
- ports:
- port: '8080'
protocol: TCP
Диагностика в Cilium строится на наблюдаемости. После применения политики, блокирующей внешний трафик, запросы извне кластера отклоняются, а в логах cilium-agent виден вердикт DROPPED для http-request:
cilium policy get
cilium endpoint list
cilium monitor
kubectl logs -n kube-system -l k8s-app=cilium --tail=100
Для работы hubble observe нужен включённый Hubble в конфигурации Cilium. Если команда недоступна, проверьте параметры установки плагина. Пример политики и вердикт DROPPED описаны в документации Cilium по Ingress и Network Policy.
Диагностика: почему трафик блокируется - пошаговый чек-лист
Порядок шагов важен: идём от отправителя к получателю и на каждом шаге отвечаем на один вопрос, доходит ли пакет.
- Убедитесь, что CNI поддерживает NetworkPolicy и поды плагина в статусе Running.
- Выведите все политики в namespace приложения и посмотрите, какие из них выбирают его поды.
- Сверьте метки подов с podSelector, а порты приложения с портами в правилах.
- Проверьте DNS: резолвится ли имя сервиса изнутри пода.
- Проверьте kube-proxy и endpoints сервиса, затем локальные фильтры ноды.
- Проверьте Ingress, аннотации whitelist и security groups облака.
- Подтвердите вывод tcpdump: видно ли входящие пакеты на интерфейсе получателя.
Проверка на уровне пода: curl, nc, tcpdump
Начните с доступности из пода-источника и заодно зафиксируйте, на какой стадии рвётся соединение:
kubectl exec -it client-0 -n production -- curl -v http://10.244.3.15:8080
kubectl exec -it client-0 -n production -- nc -zv 10.244.3.15 8080
Дальше смотрите пакеты на стороне получателя:
kubectl exec -it api-7f9c -n production -- tcpdump -i any host 10.244.1.7 -n
Как читать результат. SYN без SYN-ACK означает, что пакет ушёл и был отброшен на входящем направлении: смотрите ingress-правила получателя. Отсутствие пакетов в tcpdump при живом curl говорит о блокировке на стороне отправителя, то есть в egress. Мгновенный RST вместо таймаута обычно указывает на reject на ноде или на отсутствие слушателя на порту.
Проверка на уровне ноды: iptables и conntrack
Если пакет не покидает под или теряется между нодами, смотрите правила и состояние соединений на ноде:
iptables-save | grep 10.244.3.15
conntrack -L | grep 10.244.3.15
kubectl get svc -o wide -n production
kubectl describe svc api -n production
Отдельно проверьте таблицы маршрутов пода и ноды: как CNI строит маршруты и какие ошибки их ломают, подробно разобрано в статье про таблицы маршрутов между подами и нодами. Если endpoints у сервиса пустые, проблема в селекторах или readiness-пробе, а не в сетевых политиках.
Логи самого CNI часто дают ответ быстрее, чем ручной разбор цепочек:
kubectl logs -n kube-system -l k8s-app=calico-node --tail=100
kubectl logs -n kube-system -l k8s-app=cilium --tail=100 --since=10m
kubectl get events -n production --sort-by=.lastTimestamp | tail -20
Проверка на уровне внешнего балансировщика
Последний шаг: убедиться, что запрос не отбит до попадания в кластер.
kubectl get ingress -A
kubectl describe ingress api -n production
kubectl logs -n ingress-nginx deploy/ingress-nginx-controller --tail=200
Ищите в аннотациях whitelist-source-range, а в консоли облака правила security group для портов LoadBalancer и NodePort. Признак этого уровня: одинаковый запрос изнутри кластера проходит, а с внешнего адреса возвращает 403 или таймаут.
Чек-лист и шпаргалка: блокировка IP в Kubernetes
Сведите проверки в одну таблицу и держите её под рукой во время разбора инцидента.
| Уровень | Команда | Что проверить |
|---|---|---|
| Политики | kubectl get networkpolicy -A | Какие объекты выбирают поды приложения, есть ли default-deny |
| Селекторы | kubectl describe networkpolicy <имя> -n <ns> | podSelector, policyTypes, from/to, ipBlock и except, порты |
| Метки | kubectl get pods -n <ns> --show-labels | Совпадают ли метки подов с политикой |
| Сервис | kubectl get endpoints -n <ns> | Есть ли адреса в endpoints, не пустой ли селектор |
| Нода | iptables-save, conntrack -L | grep <ip> | Правила DROP и REJECT, состояние соединений |
| CNI | calicoctl get globalnetworkpolicy, логи cilium-agent | Активные правила и причина дропа |
| Внешний контур | kubectl describe ingress, security groups | Whitelist, аннотации, правила облачного firewall |
Обязательный минимум по NetworkPolicy: apiVersion networking.k8s.io/v1, kind NetworkPolicy, metadata с namespace, spec с podSelector и policyTypes, а также правила в ingress или egress. Поле endPort и расширенные селекторы проверяйте на поддержку в конкретной версии плагина. Примеры в статье ориентированы на Kubernetes 1.29+, NetworkPolicy API v1, Calico 3.27+ и Cilium 1.15+, поведение более старых версий CNI может отличаться.
Синтаксис политик и набор поддерживаемых полей сверяйте с официальной документацией Kubernetes, Calico и Cilium для своей версии. Для плановой проверки сетевых правил в кластере используйте аудит безопасности Kubernetes за пять шагов.
Начните с команды kubectl get networkpolicy -A в проблемном namespace: чаще всего причина находится уже на этом шаге, а default-deny без правила для DNS объясняет внезапную потерю связности после планового деплоя.