В Kubernetes входящий трафик обычно быстро приводят в порядок через Ingress/LoadBalancer. А вот исходящий (egress) часто оставляют «как есть» — до первого требования от внешнего API или службы безопасности: нужен один static IP для allowlist, нужен egress control по namespace, нужен audit traffic (кто и куда ходит), и всё это не должно ломаться при пересоздании Pod или смене ноды.
Ниже — практическая шпаргалка, как именно в Kubernetes формируется egress, где появляется SNAT, какие есть варианты «выдать» подам стабильный внешний IP, и чем обычно отличаются подходы в Cilium и Calico.
Базовая анатомия egress: от Pod до интернета
Типовой путь пакета «Pod → внешний адрес» выглядит так:
- Приложение в Pod открывает соединение на внешний IP/домен.
- Трафик выходит из сетевого неймспейса Pod в CNI (veth/bridge/overlay).
- Дальше пакет попадает на ноду и уходит через её маршрут по умолчанию.
- Если сеть Pod не маршрутизируема во внешнюю сеть (что почти всегда так), где-то выполняется NAT, чаще всего SNAT (подмена source IP на внешний IP ноды или другого «шлюза»).
Ключевой вывод: «какой внешний IP увидит интернет» определяется точкой, где выполняется SNAT. Если SNAT делается на каждой ноде — внешний IP будет равен IP ноды, на которой прямо сейчас запущен Pod. Отсюда и «прыгание» адреса.
Проблема egress почти всегда сводится к одному: вам нужно управлять тем, где именно выполняется SNAT и каким адресом он подменяет source IP.
Почему одного NetworkPolicy обычно недостаточно
NetworkPolicy отвечает за «кому можно, кому нельзя» на уровне L3/L4 (иногда L7 — если ваш CNI это поддерживает). Но NetworkPolicy не решает задачу static IP: она не «собирает» трафик в одну точку и не гарантирует исходный адрес.
Поэтому в проде часто нужны две составляющие:
- Policy: ограничения egress по направлениям (CIDR/порты/DNS/FQDN).
- Gateway/NAT: стабильная точка выхода (Egress Gateway) и предсказуемый SNAT.
Вариант 1: SNAT на нодах (быстро, но IP не фиксированный)
Во многих кластерах egress работает «сам» благодаря NAT на нодах. Реализация зависит от дистрибутива, CNI и сетевой схемы, но результат обычно такой: внешние системы видят исходящие подключения с IP ноды.
Плюсы:
- Ничего дополнительно не настраивать.
- Нет единой точки отказа (egress распределён).
- Часто максимальная производительность — трафик выходит локально с ноды.
Минусы:
- Нет static IP: IP меняется при переселении Pod.
- Сложнее audit traffic: нужно собирать логи/flow с множества нод.
- Ограничения egress усложняются: можно запретить, но трудно «направить через один шлюз».
Как быстро понять, откуда реально идёт SNAT
Проверка «какой IP видит интернет» изнутри кластера:
kubectl run -it --rm nettest --image=curlimages/curl -- sh
curl -s ifconfig.me
Если вы видите IP ноды (или разные IP в разные запуски/после рескейла) — SNAT «размазан» по нодам.
Если под egress-ноды планируются отдельные инстансы (с фиксированными адресами и предсказуемыми маршрутами), практично держать их на отдельном пуле виртуальных машин: VDS удобно масштабировать и обслуживать независимо от рабочих нод кластера.
Чтобы оффер не мешал чтению, ниже — схема, а уже затем продолжим настройку.

Вариант 2: Egress Gateway — централизованный выход и предсказуемый static IP
Egress Gateway — это архитектурный паттерн и/или функция CNI, которая делает так, чтобы выбранные Pod/namespace/сервисы выходили наружу через выделенный набор нод-шлюзов. На этих нодах вы:
- выполняете SNAT на заранее заданный адрес;
- получаете управляемый egress control;
- упрощаете audit traffic (одна точка агрегации).
Ключевая идея: отделяем «рабочие» ноды от «egress» нод
Обычно делают отдельный пул egress-нод (например, 2–3 штуки), помечают их label’ом и настраивают CNI так, чтобы трафик выбранных подов туннелировался или маршрутизировался к этим нодам, а уже там делался SNAT.
Чтобы IP был действительно «статичным», у egress-нод должен быть:
- стабильный публичный IPv4 (или несколько, если нужен шардинг);
- предсказуемый маршрут в аплинк;
- отсутствие автозамены адреса при пересоздании (в облаках это Elastic/Floating IP; в собственной инфраструктуре — резервирование адреса).
Cilium Egress Gateway: типовая схема
Cilium Egress Gateway — один из самых практичных способов сделать управляемый egress без ручных «простыней» iptables. Что обычно используют:
- выбор источников (по label Pod/namespace);
- выбор назначения (CIDR; FQDN обычно решается через политики/механизмы уровня Cilium отдельно);
- привязка к конкретному egress-node;
- наблюдаемость (например, через Hubble) для audit traffic.
Набросок конфигурации (логика, не «копипаста в прод»)
Логика: поды с label egress=internet должны выходить через egress-ноды с label role=egress-gw, а SNAT делать на конкретный адрес.
kubectl label node node-1 role=egress-gw
kubectl label node node-2 role=egress-gw
apiVersion: cilium.io/v2
kind: CiliumEgressGatewayPolicy
metadata:
name: egress-internet
spec:
selectors:
- podSelector:
matchLabels:
egress: internet
destinationCIDRs:
- 0.0.0.0/0
egressGateway:
nodeSelector:
matchLabels:
role: egress-gw
egressIP: 203.0.113.10
Смысл: вы фиксируете точку выхода и IP для SNAT. При этом «рабочие» ноды могут быть сколько угодно динамичными.
Что проверить, если после включения Egress Gateway «ничего не работает»
- Есть ли у egress-ноды маршрут по умолчанию и доступ во внешнюю сеть.
- Разрешён ли форвардинг и не режется ли трафик хостовым firewall.
- Совпадают ли PodCIDR/ServiceCIDR с тем, что ожидает CNI.
- Не конфликтуют ли политики egress (например,
NetworkPolicyзапрещает DNS или 443). - Есть ли наблюдаемость по потокам (flow logs), чтобы быстро понять, где пакет «пропал».
Calico: natOutgoing и контроль egress
В мире Calico часто встречается запрос «calico nat outgoing»: включить или выключить NAT для пула IP и добиться предсказуемого поведения исходящего трафика.
В Calico есть IPPool и флаг natOutgoing, который определяет, будет ли трафик из этого пула «маскараден» при выходе из кластера.
apiVersion: projectcalico.org/v3
kind: IPPool
metadata:
name: pod-pool
spec:
cidr: 10.244.0.0/16
natOutgoing: true
vxlanMode: Always
Это решает базовую задачу «подам нужен интернет», но обычно не решает static IP, потому что SNAT происходит на той ноде, где трафик вышел наружу.
Как в Calico обычно приходят к static IP для egress
Чаще всего используют один из подходов:
- Выделенные egress-ноды + policy based routing и SNAT на них (ручная схема, требует дисциплины и тестов).
- Внешний NAT Gateway (маршрутизатор/фаервол), через который идёт весь egress кластера.
- Маршрутизация без NAT (обычно для корпоративных сетей, реже — для выхода в интернет).
NetworkPolicy для egress control: практичные паттерны
Частая ошибка — добавить одну «разрешающую» egress-политику и думать, что «теперь всё контролируется». На практике egress control начинается с default deny и аккуратного разрешения нужного.
Паттерн 1: default deny egress в namespace
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-egress
namespace: app
spec:
podSelector: {}
policyTypes:
- Egress
egress: []
Дальше добавляете точечные разрешения. Минимальный набор почти всегда включает DNS.
Паттерн 2: разрешить DNS (иначе «сломается всё»)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns
namespace: app
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
В некоторых кластерах DNS работает не из kube-system или с нестандартными label’ами. Это частая причина «всё упало после default deny».
Паттерн 3: разрешить только конкретные CIDR и порты
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: 198.51.100.0/24
ports:
- protocol: TCP
port: 443
Так вы закрываете «куда угодно», но оставляете доступ к нужному API. В комбинации с Egress Gateway это даёт и ограничения, и фиксированный источник.
SNAT, static IP и почему внешний сервис всё равно видит разные адреса
Даже если вы уверены, что настроили SNAT, внешняя система может фиксировать разные IP по нескольким причинам:
- Несколько egress-нод: трафик распределяется, и у каждой свой публичный IP.
- Разные пути: часть трафика идёт через gateway, часть — напрямую (часто из-за selector’ов или отсутствия политики на некоторых Pod).
- IPv6: приложение может выходить по AAAA, и внешний сервис видит IPv6-адрес или другой egress-путь.
- Прокси/sidecar: egress идёт не из Pod напрямую, а через прокси, который расположен иначе и NAT’ится по-другому.
- Conntrack и долгоживущие соединения: вы поменяли правила, а старые TCP-сессии ещё живут с прежними параметрами NAT.
Практическая диагностика: где именно «теряется» контроль
Минимальный набор проверок (на тестовом стенде или аккуратно в проде):
kubectl exec -it -n app deploy/backend -- sh -c "getent hosts example.com || nslookup example.com"
kubectl exec -it -n app deploy/backend -- sh -c "curl -s ifconfig.me"
conntrack -L | head
Если вам важно не только «разрешить/запретить», но и учитывать TLS-нюансы (цепочки, сроки, исключения), полезно заранее выстроить процесс управления сертификатами; см. также автоматизацию wildcard SSL через DNS-01.

Audit traffic: что логировать и как не утонуть в данных
Задача audit для egress обычно отвечает на вопросы:
- какой Pod/namespace ходил наружу;
- куда именно (IP/порт, иногда домен);
- сколько трафика и как часто;
- что было заблокировано политиками.
Практичный подход: сначала включить наблюдаемость на egress-ноды или на внешнем NAT (там меньше точек), затем при необходимости детализировать до namespace/Pod.
Главная ошибка в audit — пытаться логировать «всё и сразу». Начните с точки egress и событий deny, а уже потом увеличивайте детализацию.
Рекомендованные схемы для продакшена
Схема A: нужен static IP для внешних API + базовый контроль
- Egress Gateway (Cilium или внешний NAT) с 1–2 публичными IP.
NetworkPolicy: default deny egress в чувствительных namespace.- Разрешения: DNS + конкретные CIDR/порты.
Схема B: разные приложения — разные внешние IP
- Несколько egress IP (или несколько egress-нод) и политики маршрутизации по label.
- Раздельный allowlist у внешних поставщиков.
- Отдельный учёт и лимиты трафика по направлениям.
Схема C: жёсткий egress control и соответствие требованиям ИБ
- Default deny egress почти везде.
- Централизованный egress gateway и прокси (если требуется L7-контроль).
- Регулярный отчёт deny/allow и ревизия исключений.
Чек-лист внедрения: чтобы не «положить» кластер
- Описываем требования: один IP или несколько, какие направления разрешены, нужен ли FQDN-контроль.
- Делаем тестовый namespace и прогоняем типовые запросы (DNS, HTTPS, обновления пакетов, внешние API).
- Включаем default deny egress только после того, как есть явные allow для DNS и критичных направлений.
- Внедряем Egress Gateway по меткам, начиная с одного сервиса.
- Добавляем наблюдаемость: где смотреть allow/deny и как быстро понять, что сломалось.
- Фиксируем операционные процедуры: как добавлять новое направление и кто утверждает исключения.
Итог
SNAT на нодах даёт «просто интернет», но почти никогда не даёт стабильный static IP и удобный audit traffic. Для управляемого выхода чаще всего нужен Egress Gateway (функция CNI или выделенная NAT-инфраструктура). А NetworkPolicy закрывает вопрос «куда можно ходить», но не «с какого IP выйдем».
Как только вы осознанно выбираете точку SNAT и делаете её управляемой, egress перестаёт быть источником сюрпризов — и для внешних allowlist, и для внутренней ИБ-отчётности.


