Ingress и egress rules: как читать
После того как вы разобрались с podSelector и policyTypes, дальше всё держится на одном умении — правильно читать список правил. Здесь нет сложной семантики, но есть две конструкции, которые выглядят почти одинаково и означают противоположное. Их путают на CKAD чаще всего.
NAT: как роутер прячет внутренние IP за одним внешним
Структура одного rule
И ingress, и egress блоки — это списки rules. Каждый rule состоит из двух частей:
ingress:
- from: # с какого направления разрешено
- <peer>
- <peer>
ports: # на какие порты разрешено
- port: 8080
protocol: TCP
egress:
- to: # куда разрешено
- <peer>
- <peer>
ports: # на какие destination ports
- port: 5432
protocol: TCP
Каждый rule — это конъюнкция (AND) внутри себя:
разрешён трафик, который идёт от одного из перечисленных peers И на один из перечисленных ports
Между разными rules в списке — disjunction (OR):
трафик разрешён, если он matches rule #1 ИЛИ rule #2 ИЛИ …
Это удобно, потому что разрешения можно дробить: «от web на :80» и «от monitoring на :9100» — два отдельных rules, каждый со своим набором портов.
Peers: podSelector, namespaceSelector, ipBlock
«Peer» — это описание стороны, с которой (для ingress) или к которой (для egress) разрешён трафик. Три типа peers:
podSelector
Внутри from или to, без namespaceSelector рядом, podSelector matches Pods в namespace policy (там, где NetworkPolicy создана):
ingress:
- from:
- podSelector:
matchLabels:
app: web
«Разрешён ingress от Pods с label app=web в этом же namespace». Pods с тем же label в другом namespace не подходят.
namespaceSelector
ingress:
- from:
- namespaceSelector:
matchLabels:
team: backend
«Разрешён ingress от всех Pods в любом namespace, который имеет label team=backend». Если в кластере несколько namespaces с этим label — все они matchятся.
Очень полезная вещь: с Kubernetes 1.22+ kubelet автоматически добавляет к каждому namespace label kubernetes.io/metadata.name: <namespace-name>. Это позволяет матчить namespace по имени без необходимости вручную лейблить его:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: monitoring
ipBlock
egress:
- to:
- ipBlock:
cidr: 0.0.0.0/0
except:
- 10.0.0.0/8
- 169.254.169.254/32
«Разрешён egress в любой IPv4-адрес, КРОМЕ 10.0.0.0/8 и метаданных AWS». Используется для разрешения Internet с одновременным запретом внутренних сетей.
ipBlock matches на IP назначения трафика. Это значит, что egress в ClusterIP Service срабатывает на IP бэкенд-Pod, а не на ClusterIP. Это потому что kube-proxy подменяет ClusterIP на PodIP через DNAT раньше, чем policy enforcement видит пакет. Учитывайте это при написании egress правил.
AND vs OR: главная ловушка
Внутри from (или to) можно перечислить несколько peers. И вот здесь — самая частая ошибка CKAD.
# OR: matches Pods в namespace с label team=backend
# ИЛИ Pods в этом же ns с label app=web
ingress:
- from:
- namespaceSelector:
matchLabels:
team: backend
- podSelector:
matchLabels:
app: web
# AND: matches только Pods, которые И в namespace team=backend
# И имеют label app=web
ingress:
- from:
- namespaceSelector:
matchLabels:
team: backend
podSelector:
matchLabels:
app: web
Различие — наличие или отсутствие дефиса перед podSelector. С дефисом — это новый элемент списка, OR. Без дефиса — поле того же peer-объекта, AND. На CKAD используйте kubectl describe networkpolicy NAME и читайте раздел Allowing ingress traffic — там видно, как K8s интерпретировал ваш YAML.
Чтобы запомнить: представьте, что один peer — это «одно описание точки», и AND внутри сужает множество matched Pods. Список peers — несколько описаний, и любое подходит.
ports внутри rule
ports — список разрешённых портов и протоколов. По умолчанию protocol: TCP.
ingress:
- from:
- podSelector:
matchLabels:
app: web
ports:
- port: 80
protocol: TCP
- port: 53
protocol: UDP
- port: dns
protocol: UDP
port может быть числом или именем порта из containerPort.name target Pod-а — это удобно для портов с именами (http, metrics).
С 1.25+ stable поддерживается endPort для диапазонов:
ports:
- port: 32000
endPort: 32767
protocol: TCP
Если ports отсутствует — разрешены все порты для matching peers. Если ports есть, но пустой — это синтаксическая ошибка (валидатор API server отбросит).
Empty selector behavior: четыре формы
Это место, где синтаксис YAML и семантика расходятся. Запомните таблицу:
# Pod isolated, ingress полностью запрещён (no rules)
spec:
podSelector: {}
policyTypes: [Ingress]
# Pod isolated, ingress от ВСЕХ Pods во всех ns
spec:
podSelector: {}
policyTypes: [Ingress]
ingress:
- from:
- namespaceSelector: {}
# Pod isolated, ingress от ВСЕХ Pods и ВСЕХ внешних IP на :80
spec:
podSelector: {}
policyTypes: [Ingress]
ingress:
- ports:
- port: 80
# Pod isolated, ingress полностью запрещён даже с указанными правилами
spec:
podSelector: {}
policyTypes: [Ingress]
ingress:
- from: [] # пустой список — никто не подходит
ports:
- port: 80
from: [] против отсутствующего from — самая subtle разница в API. Пустой список — ничего не разрешено, отсутствие поля — всё разрешено. Это противоречит интуиции «отсутствие — это null, который ничего не значит». Поэтому всегда явно: либо from: со списком peers, либо вообще не пишите блок from.
ipBlock против podSelector в одном peer
ipBlock нельзя объединить с podSelector или namespaceSelector в одном peer объекте. Они взаимоисключающи. API server отвергает такую комбинацию.
# Невалидно — API отвергнет
from:
- ipBlock:
cidr: 10.0.0.0/8
podSelector:
matchLabels:
app: web
Логика: ipBlock — про L3 IP-адрес, podSelector — про Pod identity. Они matchятся на разных уровнях и комбинировать их в одном AND не имеет смысла.
Если нужно «или IP-блок, или Pods» — это OR, два разных peer элемента:
from:
- ipBlock:
cidr: 10.0.0.0/8
- podSelector:
matchLabels:
app: web
Killer момент: namespace policy и cross-namespace peers
Когда вы пишете podSelector внутри from без namespaceSelector, поиск идёт только в namespace policy. Если хотите матчить Pods в другом namespace — обязательно указывайте namespaceSelector. Иначе ваш «allow от Pods app=web» молча превратится в «allow от Pods app=web в моём namespace».
# Разрешает Pods app=web ТОЛЬКО в этом ns
from:
- podSelector:
matchLabels: {app: web}
# Разрешает Pods app=web ВО ВСЕХ ns
from:
- namespaceSelector: {}
podSelector:
matchLabels: {app: web}
# Разрешает Pods app=web в ns с label team=frontend
from:
- namespaceSelector:
matchLabels: {team: frontend}
podSelector:
matchLabels: {app: web}
Это классическая ошибка: задача звучит как «разрешить web из frontend-namespace к API в backend-namespace», люди пишут только podSelector — и удивляются, почему не работает. NetworkPolicy в namespace backend, поиск app=web идёт в backend, там таких Pods нет, никто не подходит.