Kubernetes для самых маленьких. Часть 20: Как работает NetworkPolicy?
Все части серии «Kubernetes для самых маленьких»В части 14 мы видели главное правило сетевой модели Kubernetes: любой под может обратиться к любому другому поду по его адресу, без NAT. Потом к этому добавились Service, DNS, внешний адрес от MetalLB, шлюз на Traefik и сертификаты из прошлой части — трафик снаружи пришёл в порядок. Но внутри кластера по-прежнему нет ни одной стены: под из чужого неймспейса может открыть соединение с базой данных, взломанный фронтенд — пойти куда угодно в локальную сеть. Сегодня разберём NetworkPolicy — стандартный объект Kubernetes для ограничения трафика между подами, — затем политики самого Calico, которые умеют больше, и посмотрим, во что всё это превращается на узле.
По умолчанию разрешено всё
Пока в неймспейсе нет ни одной политики, его поды не изолированы: принимают соединения от кого угодно и сами идут куда угодно — к другим подам, в сеть узлов, в интернет через NAT узла. Так задумано: кластер должен работать сразу после установки, без настройки межсетевого экрана.
Изоляция включается политикой, которая выбирает под. Как только под попал под селектор хотя бы одной NetworkPolicy с типом Ingress, он принимает только то, что явно разрешено одной из выбирающих его политик. То же отдельно для Egress. Направления независимы: политика только на вход не трогает исходящий трафик пода.
Отсюда три свойства модели, которые надо запомнить:
- Политика выбирает поды, к которым применяется, а не трафик. Поле
spec.podSelector— «к кому», правила внутри — «что разрешено». - Политики аддитивны. Разрешения всех политик, выбирающих под, складываются. Порядка нет, приоритетов нет, и запрещающих правил в стандартном API нет вообще: запрет — это отсутствие разрешения у изолированного пода. Нельзя написать «всем можно, кроме этого адреса».
- Политики учитывают состояние соединений. Если разрешено соединение от A к B, ответные пакеты B к A пропускаются без отдельного правила — сетевой плагин опирается на conntrack, таблицу отслеживания соединений ядра.
И три исключения, прописанные в документации Kubernetes:
- трафик между подом и его узлом разрешён всегда: иначе kubelet не смог бы выполнять пробы liveness и readiness;
- под не может закрыть доступ к самому себе;
- поведение для подов с
hostNetwork: trueне определено, и большинство плагинов, Calico в том числе, их просто не видят: такой под живёт в сетевом пространстве имён узла, и его трафик выглядит как трафик узла.
NetworkPolicy реализует сетевой плагин (CNI), а не сам Kubernetes: API-сервер примет объект в любом кластере, но если плагин политик не умеет (как, например, flannel — простой CNI-плагин, который даёт только связность между подами), ничего не произойдёт и никто об этом не предупредит. Calico, который стоит у нас с части 4, умеет.
Объект NetworkPolicy
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: hello-from-traefik
namespace: demo
spec:
podSelector:
matchLabels:
app.kubernetes.io/name: hello
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: traefik
podSelector:
matchLabels:
app.kubernetes.io/name: traefik
ports:
- protocol: TCP
port: 8080
podSelector — к каким подам своего неймспейса применяется политика. Пустой селектор {} — ко всем подам неймспейса. Политика никогда не выбирает поды другого неймспейса.
policyTypes — Ingress, Egress или оба. Если поле опустить, Ingress подразумевается всегда, а Egress — только если в политике есть секция egress. Лучше писать явно: политика «запретить весь исходящий» как раз выглядит как policyTypes: [Egress] без секции egress, и без явного поля её смысл потеряется.
ingress[].from и egress[].to — список участников (peers), каждый одного из видов:
podSelector— поды своего неймспейса с такими метками;namespaceSelector— все поды неймспейсов с такими метками. По имени неймспейс выбирают меткойkubernetes.io/metadata.name, которую control plane ставит на каждый неймспейс автоматически и менять не даёт;namespaceSelectorиpodSelectorв одном элементе — поды с такими метками в неймспейсах с такими метками;ipBlock— диапазон адресов (cidr) с исключениями (except).
Самая частая ошибка — лишний дефис. Сравните:
# один участник: поды traefik В неймспейсе traefik (И)
- from:
- namespaceSelector: {matchLabels: {kubernetes.io/metadata.name: traefik}}
podSelector: {matchLabels: {app.kubernetes.io/name: traefik}}
# два участника: ВСЕ поды неймспейса traefik ИЛИ поды traefik своего неймспейса
- from:
- namespaceSelector: {matchLabels: {kubernetes.io/metadata.name: traefik}}
- podSelector: {matchLabels: {app.kubernetes.io/name: traefik}}
Разница в один символ, и вторая запись открывает больше, чем кажется.
ports — протокол (TCP, UDP или SCTP — транспортный протокол с несколькими потоками в одном соединении, который встречается в основном в телеком-оборудовании), порт числом или именем порта контейнера, диапазон через endPort (только числами). Список участников и список портов внутри одного правила связаны через И: «от этих участников на эти порты». Правило без from разрешает от всех, правило без ports — на все порты.
Два тонких места:
- Порты и адреса — после DNAT. Документация Kubernetes оставляет на усмотрение плагина, видит ли политика адрес до или после подмены, а Calico проверяет пакет уже после того, как kube-proxy заменил ClusterIP сервиса на адрес пода. Поэтому писать надо порт контейнера (8080), а не порт сервиса (80), и выбирать поды метками, а не адресом ClusterIP:
ipBlockс адресом из сети сервисов10.96.0.0/12не сработает. Ниже, в разделе «Смотрим руками», это видно на сервисеkubernetes. ipBlock— для адресов вне кластера. Адреса подов меняются при каждом перезапуске, для них есть селекторы. И помните про NAT: под, выходящий наружу, у нас получает адрес узла (natOutgoing: trueв Calico), поэтому внешний сервер видит не под, а узел, а политика на стороне кластера, наоборот, видит настоящий адрес пода до NAT.
Закроет ли новая политика уже открытые соединения, документация Kubernetes тоже оставляет плагину. В Calico первое правило цепочки пода пропускает пакеты установленных соединений, поэтому уже открытые соединения продолжают работать, а новая политика действует на новые.
Default deny и что после него ломается
Правильная отправная точка для неймспейса с приложениями — запретить всё и открыть нужное. Запрет — это политика, которая выбирает все поды и ничего не разрешает:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny
namespace: demo
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
Сразу после неё в demo перестаёт работать всё, и первое — DNS. Под ищет postgres.demo.svc.cluster.local, отправляет запрос на 10.96.0.10:53, kube-proxy переписывает адрес на под CoreDNS в kube-system, а исходящий трафик пода запрещён. Приложение видит не «соединение запрещено», а «имя не найдено» или таймаут, и ищут проблему обычно не там. Поэтому вторая политика — всегда разрешение DNS:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns
namespace: demo
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
TCP нужен тоже: DNS переходит на TCP, когда ответ не помещается в UDP-датаграмму. Метка k8s-app: kube-dns стоит на подах CoreDNS в кластере kubeadm по историческим причинам.
Что ещё ломается после default deny и о чём стоит подумать заранее:
| Что | Почему | Чем открыть |
|---|---|---|
| входящий трафик от Traefik | поды стали изолированы на вход | политика from неймспейса traefik |
поды обращаются друг к другу внутри demo |
изоляция действует и внутри неймспейса | правила для конкретных пар |
| приложение ходит к API Kubernetes | 10.96.0.1:443 после DNAT — адреса control plane, 192.168.10.17:6443 |
ipBlock с адресом API-сервера и портом 6443 |
| приложение ходит во внешний мир (обновления, внешние API) | исходящий запрещён | ipBlock с нужными адресами или отказ от этого вовсе |
| сбор метрик Prometheus | входящий на порт метрик | политика from неймспейса мониторинга |
| пробы kubelet | не ломаются: трафик узла к своему поду разрешён всегда | — |
Набор политик для demo
В demo живут hello (принимает HTTP от Traefik на порту 8080) и postgres — учебная база, к которой ходит только hello:
apiVersion: apps/v1
kind: Deployment
metadata:
name: postgres
namespace: demo
labels:
app.kubernetes.io/name: postgres
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: postgres
template:
metadata:
labels:
app.kubernetes.io/name: postgres
spec:
containers:
- name: postgres
image: postgres:18.6-alpine
env:
- name: POSTGRES_HOST_AUTH_METHOD # только для опыта: вход без пароля
value: trust
ports:
- name: postgres
containerPort: 5432
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
memory: 256Mi
---
apiVersion: v1
kind: Service
metadata:
name: postgres
namespace: demo
spec:
selector:
app.kubernetes.io/name: postgres
ports:
- name: postgres
port: 5432
targetPort: postgres
Настоящую базу без пароля не запускают: пароль кладут в Secret (часть 12), а данные — на том, о котором следующая часть. Здесь база нужна только как сервер на порту 5432.
Политики default-deny и allow-dns уже есть. Добавим ещё три.
Вход в hello от Traefik — политика hello-from-traefik из начала. Traefik, как мы видели в части 18, ходит прямо на адреса подов, а не через ClusterIP, поэтому источник — под Traefik, и селектор по его неймспейсу и меткам работает. Если бы Traefik работал с hostNetwork: true, источником был бы адрес узла, и пришлось бы разрешать адреса узлов через ipBlock.
Выход из hello к базе:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: hello-to-postgres
namespace: demo
spec:
podSelector:
matchLabels:
app.kubernetes.io/name: hello
policyTypes:
- Egress
egress:
- to:
- podSelector:
matchLabels:
app.kubernetes.io/name: postgres
ports:
- protocol: TCP
port: 5432
И вход в базу от hello:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: postgres-from-hello
namespace: demo
spec:
podSelector:
matchLabels:
app.kubernetes.io/name: postgres
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app.kubernetes.io/name: hello
ports:
- protocol: TCP
port: 5432
При default deny на оба направления соединение должно быть разрешено дважды: на выход у клиента и на вход у сервера. Забыть одну из двух половин — вторая по частоте ошибка после DNS.
Если база не в кластере, а на отдельном сервере, вместо селектора в hello-to-postgres пишется ipBlock с его адресом /32, а вход на сервере ограничивается его собственным файрволом — по адресам узлов, потому что снаружи поды видны с адресами узлов.
Итог по неймспейсу:
┌──────────────── demo (default deny) ────────────────┐
Traefik ───────►│ hello :8080 ──────────────────► postgres :5432 │
(ns traefik) │ │ │
│ └──► CoreDNS :53 (kube-system) │
│ │
│ всё остальное: вход и выход запрещены │
└─────────────────────────────────────────────────────┘
Политики неймспейса — ответственность команды приложения: у кого есть права на demo, тот может их менять и удалять. Ограничения, которые должны действовать для всех и всегда, независимо от того, что напишут команды, стандартный API выразить не может: он неймспейсный и только разрешающий. Для этого нужны политики Calico.
Как Calico применяет политики
Calico на каждом узле работает DaemonSet'ом calico-node, и внутри него главный компонент — Felix, агент, который программирует датаплейн узла: маршруты к подам и правила фильтрации. Политики попадают к нему так:
kubectl apply ──► kube-apiserver ──► etcd
│ watch
▼
Typha (calico-system) раздаёт изменения многим узлам,
│ чтобы не нагружать API-сервер
▼
Felix в calico-node на каждом узле
│ пересчитывает: какие политики выбирают
│ какие поды ЭТОГО узла
▼
цепочки и наборы адресов в таблице nftables ip calico
для интерфейса caliXXXXXXXXXXX каждого пода
Typha — промежуточный кэш между API-сервером и агентами Felix: на больших кластерах сотни Felix не держат каждый свой watch к API-серверу, а получают изменения от нескольких реплик Typha.
Felix видит каждый под как WorkloadEndpoint — внутренний объект Calico с адресом пода, его метками, метками неймспейса и именем veth-интерфейса на узле (cali плюс первые 11 символов SHA-1 от строки <неймспейс>.<под>, как мы считали в части 14). Через kubectl эти объекты не видны. Kubernetes NetworkPolicy Felix переводит в свою внутреннюю модель, где она хранится рядом с политиками Calico и отличается видом, и всё вместе превращает в правила nftables. На стенде Calico работает в режиме linuxDataplane: Nftables (часть 4), и все правила лежат в его таблице ip calico — той же, где живёт маскарадинг natOutgoing из части 14. Имена цепочек в ней — с префиксом бывшей таблицы iptables, filter-:
- две карты вердиктов (verdict map — хеш-таблица nftables, которая по ключу сразу выдаёт действие, мы видели такие у kube-proxy в части 15):
filter-cali-to-wl-dispatchиfilter-cali-from-wl-dispatch, где ключ — имя интерфейсаcali…, а значение — переход в цепочку этого пода; одноимённые цепочки применяют их правилом видаoifname vmap @filter-cali-to-wl-dispatch(для выхода из пода — по входному интерфейсу), а пакет для неизвестного интерфейса отбрасывают; - на каждый интерфейс пода две цепочки:
filter-cali-tw-<интерфейс>(to workload — трафик в под, то есть ingress) иfilter-cali-fw-<интерфейс>(from workload — трафик из пода, egress); - на каждую политику — цепочки
filter-cali-pi-<вид>/<неймспейс>/<имя>(policy ingress) иfilter-cali-po-…(policy egress), где вид —knpдля Kubernetes NetworkPolicy,npдля NetworkPolicy Calico иgnpдля GlobalNetworkPolicy; на них прыгают цепочки интерфейсов выбранных подов. Если у нескольких политик одного tier'а одинаковый селектор, Felix собирает их в цепочку-группуfilter-cali-gi-…/filter-cali-go-…и прыгает уже на неё; - после политик в цепочке пода стоят профили
filter-cali-pri-kns.<неймспейс>иfilter-cali-pri-ksa.<неймспейс>.<ServiceAccount>(pro-для egress) — их Calico заводит на каждый неймспейс и ServiceAccount, и они разрешают всё. Именно так устроено «по умолчанию разрешено всё»: если ни одна политика под не выбрала, пакет доходит до профиля и принимается; - селекторы в правилах («поды с меткой
app.kubernetes.io/name=traefikв неймспейсеtraefik») превращаются в наборы адресов (sets) nftables с именамиcali40s-<хеш селектора>. Когда появляется новый под с такой меткой, Felix добавляет его адрес в набор, а цепочки не трогает.
Другие датаплейны Calico устроены так же по смыслу: в режиме Iptables те же цепочки (cali-tw-…, cali-pi-…) лежат в таблице filter iptables, а наборы — в ipset; в режиме eBPF (eBPF — механизм ядра Linux для загрузки в ядро проверенных программ обработки пакетов) правила исполняются такими программами на интерфейсах, без netfilter; этот режим разобран в части 5 серии про Kubernetes на BGP.
Благодаря двум цепочкам на интерфейс политика проверяется на узле отправителя (egress) и на узле получателя (ingress) — каждая на своём конце, даже если поды на разных узлах.
Политики Calico
Calico добавляет свои ресурсы в группе projectcalico.org/v3. Оператор Calico 3.32 по умолчанию ставит Calico API server — расширение API Kubernetes, через которое эти ресурсы доступны обычному kubectl (без него понадобилась бы утилита calicoctl). Проверить: kubectl get tigerastatus apiserver. С этим связана ловушка имён: networkpolicies есть и в networking.k8s.io, и в projectcalico.org, а короткое имя cnp занято сразу двумя ресурсами. kubectl get networkpolicies показывает только политики Kubernetes, поэтому политики Calico запрашивают полным именем ресурса:
$ kubectl get networkpolicies -A
No resources found
$ kubectl get networkpolicies.projectcalico.org -A
NAMESPACE NAME CREATED AT
calico-system calico-system.apiserver-access 2026-10-24T10:37:10Z
calico-system calico-system.default-deny 2026-10-24T10:37:10Z
calico-system calico-system.kube-controller-access 2026-10-24T10:37:10Z
kube-system calico-system.cluster-dns 2026-10-24T10:36:50Z
Эти четыре политики создал оператор Calico для своих компонентов — о них ниже.
| Kubernetes NetworkPolicy | Calico NetworkPolicy | Calico GlobalNetworkPolicy | |
|---|---|---|---|
| Область | неймспейс | неймспейс | весь кластер, в том числе узлы (HostEndpoint) |
| Действия | только разрешить | Allow, Deny, Pass, Log | Allow, Deny, Pass, Log |
| Порядок | нет, всё складывается | поле order |
поле order |
| Селекторы | matchLabels, matchExpressions |
выражения: app.kubernetes.io/name == 'hello' && tier != 'test' |
то же, плюс namespaceSelector |
| Прочее | порты, ipBlock |
ServiceAccount, ICMP, сети, наборы сетей | то же |
HostEndpoint — объект Calico для интерфейса самого узла: с ним политики начинают фильтровать трафик узла, а не только подов. Сразу после создания HostEndpoint без политик Calico запрещает интерфейсу всё, кроме служебных исключений (failsafe), поэтому на рабочем кластере их заводят осторожно; сегодня мы их не трогаем.
Действия:
Allow— пропустить, дальше не проверять;Deny— отбросить, дальше не проверять;Log— записать пакет в журнал ядра (с префиксомcalico-packet, он задаётся полемlogPrefixв FelixConfiguration) и продолжить со следующего правила;Pass— прекратить проверку политик текущего уровня (tier) и перейти к следующему.
order — число; политики с меньшим значением проверяются раньше, правила внутри политики — сверху вниз. Первое сработавшее Allow или Deny решает судьбу пакета.
Tiers — уровни политик. Политики группируются в tier'ы, tier'ы проверяются по своему order от меньшего к большему, и внутри каждого — свои политики по их order. Так администраторы безопасности могут держать свои правила на уровне, который проверяется раньше командных и который команды не могут менять (права на tier выдаются через RBAC отдельно). Встроенные уровни в Calico 3.32:
| Tier | order |
defaultAction |
Что в нём |
|---|---|---|---|
kube-admin |
1 000 | Pass |
ClusterNetworkPolicy с tier: Admin (о них ниже) |
default |
1 000 000 | Deny |
все Kubernetes NetworkPolicy и политики Calico без явного tier |
kube-baseline |
10 000 000 | Pass |
ClusterNetworkPolicy с tier: Baseline |
По документации Calico порядок уровня default изменить нельзя, а у kube-admin и kube-baseline зафиксировано действие Pass. Кроме них, оператор Calico создаёт уровень calico-system с order: 100 для собственных политик — тех четырёх, что мы видели выше. calico-system.default-deny закрывает поды в calico-system, кроме calico-apiserver, две другие политики в calico-system открывают компонентам нужные им пути, а calico-system.cluster-dns в kube-system выбирает поды CoreDNS: разрешает им весь исходящий трафик и входящий от компонентов Calico, а остальной входящий передаёт дальше правилом Pass. Трогать этот уровень не нужно.
Неявный запрет в конце tier'а. Это главное, что надо понимать про уровни. Если хотя бы одна политика tier'а выбрала под, а ни одно правило в этом tier'е не приняло решения, пакет отбрасывается — как у изолированного пода в стандартном API. Tier, в котором ни одна политика под не выбрала, просто пропускается. Чтобы пакет, не подпавший под правила, ушёл проверяться дальше, нужно либо поставить tier'у defaultAction: Pass, либо закончить политику явным правилом action: Pass.
Закрыть подам путь к служебному серверу
Теперь общий приём, который понадобится в части 22. В сети узлов есть серверы, к которым должны ходить узлы, но не поды: NFS-сервер nfs.example.com (192.168.10.30), который мы настроим в части 22, файловый сервер files.example.com (192.168.10.31) с общими папками по SMB (протокол доступа к файлам Windows, порт 445) для части 24, сервер резервных копий, интерфейсы управления оборудованием. На таких серверах доступ обычно ограничивают адресами узлов 192.168.10.17, .20 и .21. Но под, который идёт во внешнюю сеть, у нас выходит через NAT узла — с адресом узла. Для сервера он неотличим от самого узла, и ограничение по адресам узлов перестаёт что-либо значить.
Закрывать это нужно со стороны кластера: запретить всем подам исходящий трафик к служебным адресам. Командные NetworkPolicy для этого не годятся — они неймспейсные, только разрешающие, и любую из них команда может удалить. Нужна GlobalNetworkPolicy в отдельном tier'е, который проверяется раньше командных политик.
Почему это не ломает сами тома: сетевые тома монтирует ядро узла (о том, кто и как отдаёт ему команду, — в следующей части), и драйверы CSI, которые этим управляют, работают с hostNetwork: true. Их трафик — это трафик узла, а не WorkloadEndpoint, и политики подов к нему не применяются.
Сначала tier:
apiVersion: projectcalico.org/v3
kind: Tier
metadata:
name: infra
spec:
order: 200
defaultAction: Pass
order: 200 — после служебного calico-system (100), но раньше kube-admin (1 000) и default (1 000 000). defaultAction: Pass — пакеты, которые не попали ни под одно правило этого tier'а, идут дальше, к обычным политикам.
Политика:
apiVersion: projectcalico.org/v3
kind: GlobalNetworkPolicy
metadata:
name: infra.deny-pods-to-service-hosts
spec:
tier: infra
order: 100
namespaceSelector: all() # только поды: у узлов неймспейса нет
types:
- Egress
egress:
- action: Log
destination:
nets:
- 192.168.10.30/32 # nfs.example.com
- 192.168.10.31/32 # files.example.com
- action: Deny
destination:
nets:
- 192.168.10.30/32
- 192.168.10.31/32
- action: Pass # всё остальное — на проверку дальше
Разберём, почему именно так.
namespaceSelector: all()выбирает конечные точки во всех неймспейсах — то есть поды. Без него селекторall()у GlobalNetworkPolicy выбрал бы и ресурсы без неймспейса — конечные точки узлов (HostEndpoint), если они когда-нибудь появятся, — и тогда политика закрыла бы служебным серверам дорогу от самих узлов. Для них в Calico есть отдельный селекторglobal().LogпередDeny— каждая попытка попадёт в журнал ядра узла, и будет видно, какой под пытался достучаться до сервера. Когда журнал перестанет быть нужен, правило убирают.- Последнее правило
Pass— обязательно, даже приdefaultAction: Pass. Эта политика выбирает все поды кластера, и без явногоPassсудьбу «остального» трафика решало бы поведение tier'а по умолчанию. Если положить такую политику в tierdefaultтолько с правиломDeny, неявный запрет в конце tier'а отбросит весь исходящий трафик всех подов, кроме разрешённого командными политиками, — и в неймспейсах без политик кластер разом потеряет связь. ЯвныйPassделает намерение очевидным и не зависит от настроек tier'а. - Если какому-то поду доступ всё же нужен (например, задаче резервного копирования), для него ставится отдельное правило
Allowс селектором доDeny— и это решение администратора, видимое в одном месте.
Адресов обычно больше двух; их удобно собрать в GlobalNetworkSet — именованный набор сетей с метками — и ссылаться на него селектором (destination: selector: role == 'service-hosts'), а не повторять адреса в каждом правиле.
Тем же способом в Calico делают и запрет по умолчанию сразу для всего кластера, которого нет в стандартном API: GlobalNetworkPolicy с namespaceSelector, исключающим системные неймспейсы (kubernetes.io/metadata.name not in {"calico-system", "kube-public", "kube-system", "tigera-operator"}), и правилом, разрешающим DNS, — такой пример есть в документации Calico.
ClusterNetworkPolicy
То, что мы сделали Calico-политикой, — администраторские правила поверх командных — давно хотели получить в стандартном API. В SIG Network этим занимается проект Network Policy API. Сначала он выпустил пару ресурсов AdminNetworkPolicy и BaselineAdminNetworkPolicy (версия v1alpha1), а в апреле 2026 года в выпуске v0.2.0 объединил их в один ресурс ClusterNetworkPolicy (policy.networking.k8s.io/v1alpha2) с полем tier: Admin — проверяется раньше NetworkPolicy и может запрещать, Baseline — проверяется после и задаёт правила по умолчанию, которые команды могут переопределить. Внутри уровня политики упорядочены полем priority от 0 до 1000, действия — Accept, Deny и Pass (передать решение дальше).
Статус на момент написания: это альфа, в сам Kubernetes не входит и ставится отдельными CRD, и поддерживают его не все сетевые плагины. Calico 3.32 его понимает и ставит CRD сам — на нашем кластере clusternetworkpolicies.policy.networking.k8s.io есть с установки, — а политики раскладывает по tier'ам kube-admin и kube-baseline, переводя priority в order. Для учебного кластера это интересно попробовать, но опираться в эксплуатации на альфа-API, поля которого ещё меняются (между v1alpha1 и v1alpha2 поменялись и названия ресурсов, и действия: Allow стал Accept), рано — политики Calico здесь надёжнее.
Смотрим руками
Стенд — как в конце прошлой части: Traefik в неймспейсе traefik отдаёт hello.apps.example.com из demo, где работают три пода hello и postgres из манифеста выше. Подготовим пробный под в demo и второй — в неймспейсе sandbox, где политик нет:
$ kubectl -n demo run probe --image=busybox:1.37 --labels=app.kubernetes.io/name=probe -- sleep 1d
pod/probe created
$ kubectl create namespace sandbox
namespace/sandbox created
$ kubectl -n sandbox run probe --image=busybox:1.37 -- sleep 1d
pod/probe created
$ kubectl -n demo get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
hello-6d5c6db6b4-2wvbm 1/1 Running 0 54s 172.20.211.30 k8s-node-1 <none> <none>
hello-6d5c6db6b4-nt56v 1/1 Running 0 54s 172.20.172.44 k8s-node-2 <none> <none>
hello-6d5c6db6b4-snq5t 1/1 Running 0 54s 172.20.211.27 k8s-node-1 <none> <none>
postgres-dbd894d99-cqrv5 1/1 Running 0 53s 172.20.211.34 k8s-node-1 <none> <none>
probe 1/1 Running 0 41s 172.20.172.50 k8s-node-2 <none> <none>
$ kubectl -n traefik get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
traefik-6b9b5d5f95-j9rhw 1/1 Running 0 69s 172.20.172.47 k8s-node-2 <none> <none>
traefik-6b9b5d5f95-vvfzh 1/1 Running 0 69s 172.20.211.35 k8s-node-1 <none> <none>
До политик всё открыто:
$ kubectl -n sandbox exec probe -- nc -zv -w 2 postgres.demo 5432
postgres.demo (10.106.39.155:5432) open
$ kubectl -n demo exec probe -- wget -qO- -T 2 http://hello.demo/
hello from hello-6d5c6db6b4-nt56v
$ kubectl -n demo exec probe -- nc -zv -w 2 192.168.10.31 445
192.168.10.31 (192.168.10.31:445) open
Под из чужого неймспейса открыл соединение с базой, а под из demo — с файловым сервером. Применяем default-deny:
$ kubectl apply -f default-deny.yaml
networkpolicy.networking.k8s.io/default-deny created
$ kubectl -n demo exec probe -- nslookup hello.demo.svc.cluster.local
;; connection timed out; no servers could be reached
command terminated with exit code 1
$ kubectl -n demo exec probe -- wget -qO- -T 2 http://hello.demo/
wget: bad address 'hello.demo'
command terminated with exit code 1
DNS сломался первым, как и ожидалось, и wget жалуется не на соединение, а на имя. Добавляем allow-dns и остальные политики:
$ kubectl apply -f allow-dns.yaml
networkpolicy.networking.k8s.io/allow-dns created
$ kubectl apply -f hello-from-traefik.yaml -f hello-to-postgres.yaml -f postgres-from-hello.yaml
networkpolicy.networking.k8s.io/hello-from-traefik created
networkpolicy.networking.k8s.io/hello-to-postgres created
networkpolicy.networking.k8s.io/postgres-from-hello created
$ kubectl -n demo get networkpolicy
NAME POD-SELECTOR AGE
allow-dns <none> 40s
default-deny <none> 91s
hello-from-traefik app.kubernetes.io/name=hello 20s
hello-to-postgres app.kubernetes.io/name=hello 20s
postgres-from-hello app.kubernetes.io/name=postgres 20s
$ kubectl -n demo exec probe -- nslookup hello.demo.svc.cluster.local
Server: 10.96.0.10
Address: 10.96.0.10:53
Name: hello.demo.svc.cluster.local
Address: 10.103.28.1
$ kubectl -n demo exec probe -- wget -qO- -T 2 http://hello.demo/
wget: download timed out
command terminated with exit code 1
$ kubectl -n sandbox exec probe -- nc -zv -w 2 postgres.demo 5432
nc: postgres.demo (10.106.39.155:5432): Connection timed out
command terminated with exit code 1
Полное имя в nslookup — не случайность: с коротким hello.demo busybox печатает NXDOMAIN для каждого суффикса поиска и выходит с ошибкой, даже найдя адрес (часть 16). DNS работает; probe не может к hello (у него нет разрешения на выход, а у hello — на вход от него), из sandbox к базе тоже не пройти. Заметьте: отказ выглядит как таймаут, а не «connection refused» — пакет отбрасывается молча.
Теперь проверим, что hello ходит к базе. Образ hello собран на distroless, в нём нет ни оболочки, ни nc, поэтому добавим в под эфемерный контейнер командой kubectl debug. Он запускается внутри уже работающего пода и делит с ним сетевое пространство имён — у него тот же адрес и те же политики. Удалить эфемерный контейнер из пода нельзя, он исчезнет вместе с подом:
$ kubectl -n demo debug hello-6d5c6db6b4-nt56v --image=busybox:1.37 --profile=general -c nettest -- sleep 1d
$ kubectl -n demo exec hello-6d5c6db6b4-nt56v -c nettest -- nc -zv -w 2 postgres 5432
postgres (10.106.39.155:5432) open
$ kubectl -n demo exec hello-6d5c6db6b4-nt56v -c nettest -- nc -zv -w 2 192.168.10.32 443
nc: 192.168.10.32 (192.168.10.32:443): Connection timed out
command terminated with exit code 1
К базе можно, к серверу в локальной сети 192.168.10.32 — нет: исходящий трафик hello тоже под default-deny. А вот что бывает, если забыть одну половину соединения:
$ kubectl -n demo delete networkpolicy postgres-from-hello
networkpolicy.networking.k8s.io "postgres-from-hello" deleted from demo namespace
$ kubectl -n demo exec hello-6d5c6db6b4-nt56v -c nettest -- nc -zv -w 2 postgres 5432
nc: postgres (10.106.39.155:5432): Connection timed out
command terminated with exit code 1
$ kubectl apply -f postgres-from-hello.yaml
networkpolicy.networking.k8s.io/postgres-from-hello created
$ kubectl -n demo exec hello-6d5c6db6b4-nt56v -c nettest -- nc -zv -w 2 postgres 5432
postgres (10.106.39.155:5432) open
Снаружи сайт работает — Traefik входит в hello по разрешению hello-from-traefik. В этой проверке к Traefik обращаемся напрямую через NodePort его сервиса (32486 на k8s-node-1), с ноутбука и с заголовком Host; полный путь через адрес MetalLB и TLS — тот, что собран в части 17 и двух следующих:
$ curl -s -m 5 -H 'Host: hello.apps.example.com' http://192.168.10.20:32486/
hello from hello-6d5c6db6b4-nt56v
Через шлюз с сертификатом это была бы команда curl -s https://hello.apps.example.com/; её вывод здесь не снимался.
Политика видит адреса после DNAT. Разрешим probe выход к API Kubernetes — сначала по адресу сервиса kubernetes, как подсказывает интуиция:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: probe-to-api
namespace: demo
spec:
podSelector:
matchLabels:
app.kubernetes.io/name: probe
policyTypes:
- Egress
egress:
- to:
- ipBlock:
cidr: 10.96.0.1/32
ports:
- protocol: TCP
port: 443
$ kubectl apply -f probe-to-api.yaml
networkpolicy.networking.k8s.io/probe-to-api created
$ kubectl -n demo exec probe -- nc -zv -w 2 kubernetes.default.svc.cluster.local 443
nc: kubernetes.default.svc.cluster.local (10.96.0.1:443): Connection timed out
command terminated with exit code 1
Не пускает. Меняем в политике cidr на 192.168.10.17/32 и port на 6443 — адрес и порт API-сервера, в которые kube-proxy превращает 10.96.0.1:443:
$ kubectl apply -f probe-to-api.yaml
networkpolicy.networking.k8s.io/probe-to-api configured
$ kubectl -n demo exec probe -- nc -zv -w 2 kubernetes.default.svc.cluster.local 443
kubernetes.default.svc.cluster.local (10.96.0.1:443) open
$ kubectl -n demo delete -f probe-to-api.yaml
networkpolicy.networking.k8s.io "probe-to-api" deleted from demo namespace
Под по-прежнему подключается к 10.96.0.1:443, а политика сравнивает уже 192.168.10.17:6443.
Что на узле. Под hello-6d5c6db6b4-nt56v работает на k8s-node-2 с адресом 172.20.172.44. Его интерфейс найдём по маршруту и сверим с хешем имени:
user@k8s-node-2:~$ ip route | grep 172.20.172.44
172.20.172.44 dev calid81a2fd6ead scope link metric 1024
user@k8s-node-2:~$ printf demo.hello-6d5c6db6b4-nt56v | sha1sum
d81a2fd6ead57c74c790ee4269d803e58bb91e4f -
Пакет, который ядро узла отправляет в интерфейс пода, проходит хук forward, базовую цепочку Calico filter-FORWARD, цепочку filter-cali-FORWARD и попадает в диспетчер — карту вердиктов, где по имени выходного интерфейса сразу находится цепочка пода. На узле четыре пода — hello, probe из demo, Traefik и служебный под Calico:
user@k8s-node-2:~$ sudo nft list map ip calico filter-cali-to-wl-dispatch
table ip calico {
map filter-cali-to-wl-dispatch {
type ifname : verdict
elements = { "cali5ed29fc4313" : goto filter-cali-tw-cali5ed29fc4313,
"calia463c8b45d5" : goto filter-cali-tw-calia463c8b45d5,
"cali1f20149a837" : goto filter-cali-tw-cali1f20149a837,
"calid81a2fd6ead" : goto filter-cali-tw-calid81a2fd6ead }
}
}
Сама цепочка пода (комментарии с хешами свои на каждом узле):
user@k8s-node-2:~$ sudo nft list chain ip calico filter-cali-tw-calid81a2fd6ead
table ip calico {
chain filter-cali-tw-calid81a2fd6ead {
ct state established,related counter packets 48 bytes 4780 accept comment "cali:J8USgVKtKmeIe9eC;"
ct state invalid counter packets 0 bytes 0 drop comment "cali:tpzDHJWaPoRJ9c-x;"
counter packets 5 bytes 300 meta mark set meta mark & 0xfffcffff comment "cali:mOiJXmMqCk_6D0uA;"
counter packets 3 bytes 180 meta mark set meta mark & 0xfffdffff comment "cali:-BrknvkZT89OBg5i; Start of tier default"
meta mark & 0x00020000 == 0x00000000 counter packets 3 bytes 180 jump filter-cali-pi-knp/demo/default-deny comment "cali:wS87dAYgY8Qv1scI;"
meta mark & 0x00010000 == 0x00010000 counter packets 0 bytes 0 return comment "cali:rJjBzICTlwANm-rK; Return if policy accepted"
meta mark & 0x00020000 == 0x00000000 counter packets 3 bytes 180 jump filter-cali-pi-knp/demo/hello-from-traefik comment "cali:Ob1dmafZJNlYN6IN;"
meta mark & 0x00010000 == 0x00010000 counter packets 3 bytes 180 return comment "cali:XT75spmzaSoDqrIn; Return if policy accepted"
meta mark & 0x00020000 == 0x00000000 counter packets 0 bytes 0 drop comment "cali:QhISMjA7w6n0c7jC; End of tier default. Drop if no policies passed packet"
counter packets 0 bytes 0 jump filter-cali-pri-kns.demo comment "cali:t5R3VD0h1d7cyDqK;"
meta mark & 0x00010000 == 0x00010000 counter packets 0 bytes 0 return comment "cali:xOh-51vkZftxinOv; Return if profile accepted"
counter packets 0 bytes 0 jump filter-cali-pri-ksa.demo.default comment "cali:-WMoRlMBSgE6l-hC;"
meta mark & 0x00010000 == 0x00010000 counter packets 0 bytes 0 return comment "cali:tYnNxJTkcWQ0zGdu; Return if profile accepted"
counter packets 0 bytes 0 drop comment "cali:xPRB56C7OTlIahZq; Drop if no profiles matched"
}
}
Здесь вся модель в полутора десятках строк. Сначала пропускаются пакеты уже установленных соединений (conntrack) — так работает «ответ разрешён» и так уживаются открытые соединения с новыми политиками — и отбрасываются пакеты, которые conntrack считает некорректными. Затем сбрасываются биты-метки результата: 0x10000 — «принято политикой», 0x20000 — «передано дальше» (Pass). По очереди вызываются цепочки политик, выбирающих этот под: default-deny и hello-from-traefik, по имени видно и вид (knp — Kubernetes NetworkPolicy), и неймспейс. Политика, разрешившая пакет, ставит бит 0x10000, и цепочка пода возвращает управление — дальше пакет принимает базовая цепочка filter-FORWARD (Policy explicitly accepted packet). Если ни одна не разрешила и никто не поставил Pass — End of tier default ... drop: тот самый неявный запрет в конце tier'а. Ниже — профили kns.demo и ksa.demo.default, которые приняли бы всё, если бы под не выбрала ни одна политика; у нашего пода до них не доходит ни один пакет. Счётчики counter показывают, сколько пакетов прошло через каждое правило: все новые соединения, дошедшие до пода, приняла hello-from-traefik. Попытки probe сюда не дошли вовсе — их отбросила его собственная цепочка filter-cali-fw-… на выходе.
Цепочка политики hello-from-traefik — одно правило: источник из набора адресов, порт 8080, поставить бит «принято»:
user@k8s-node-2:~$ sudo nft list chain ip calico filter-cali-pi-knp/demo/hello-from-traefik
table ip calico {
chain filter-cali-pi-knp/demo/hello-from-traefik {
ip saddr @cali40s-rob4WbEGIc5jlJUd4ap62IG tcp dport 8080 counter packets 1 bytes 60 meta mark set meta mark | 0x00010000 comment "cali:xmq1Y6-XZkIVNcn7; KubernetesNetworkPolicy demo/hello-from-traefik ingress"
}
}
user@k8s-node-2:~$ sudo nft list set ip calico cali40s-rob4WbEGIc5jlJUd4ap62IG
table ip calico {
set cali40s-rob4WbEGIc5jlJUd4ap62IG {
type ipv4_addr
flags interval
elements = { 172.20.172.47, 172.20.211.35 }
}
}
В наборе — адреса двух подов Traefik, по одному на каждом рабочем узле. Масштабируете Traefik до трёх — Felix добавит третий адрес в набор, цепочки останутся прежними. Цепочка default-deny для входа пуста по смыслу — в ней одно правило continue: политика выбирает под, но ничего не разрешает.
Теперь выход пода probe (интерфейс cali5ed29fc4313 на том же узле):
user@k8s-node-2:~$ sudo nft list chain ip calico filter-cali-fw-cali5ed29fc4313
table ip calico {
chain filter-cali-fw-cali5ed29fc4313 {
ct state established,related counter packets 41 bytes 2278 accept comment "cali:AeGE1uIZEs6IHBCc;"
ct state invalid counter packets 0 bytes 0 drop comment "cali:ul9cvPWeiE6970Y4;"
counter packets 84 bytes 6128 meta mark set meta mark & 0xfffcffff comment "cali:-epCTaGIhf4ACahG;"
udp dport 4789 counter packets 0 bytes 0 drop comment "cali:2zjF5A7i-ubwEJFH; Drop VXLAN encapped packets originating in workloads"
meta l4proto ipencap counter packets 0 bytes 0 drop comment "cali:o6BUzHCylnAha-3F; Drop IPinIP encapped packets originating in workloads"
counter packets 49 bytes 3732 meta mark set meta mark & 0xfffdffff comment "cali:5ZJaeiYdH_1iS0vr; Start of tier default"
meta mark & 0x00020000 == 0x00000000 counter packets 33 bytes 2672 jump filter-cali-go-iBOnLIGKDZQ62MbnzC50 comment "cali:QPSvYligqE5NnYoT;"
meta mark & 0x00010000 == 0x00010000 counter packets 26 bytes 2252 return comment "cali:k937F-wsU9jm-T4y; Return if policy accepted"
meta mark & 0x00020000 == 0x00000000 counter packets 0 bytes 0 drop comment "cali:Y9PXwujeD4Iy_Mqj; End of tier default. Drop if no policies passed packet"
counter packets 0 bytes 0 jump filter-cali-pro-kns.demo comment "cali:KUve0gdJD1lTvy1H;"
meta mark & 0x00010000 == 0x00010000 counter packets 0 bytes 0 return comment "cali:8XgaEsHkxyfJbRYq; Return if profile accepted"
counter packets 0 bytes 0 jump filter-cali-pro-ksa.demo.default comment "cali:fzqYFm0Vr-1cSXG0;"
meta mark & 0x00010000 == 0x00010000 counter packets 0 bytes 0 return comment "cali:nbx0mCNhhs4-y0pv; Return if profile accepted"
counter packets 0 bytes 0 drop comment "cali:GSMTU3rHcQPwWEoo; Drop if no profiles matched"
}
}
user@k8s-node-2:~$ sudo nft list chain ip calico filter-cali-go-iBOnLIGKDZQ62MbnzC50
table ip calico {
chain filter-cali-go-iBOnLIGKDZQ62MbnzC50 {
counter packets 61 bytes 4590 jump filter-cali-po-knp/demo/allow-dns comment "cali:4kvO45nccx68evnZ;"
meta mark & 0x00030000 == 0x00000000 counter packets 21 bytes 1260 jump filter-cali-po-knp/demo/default-deny comment "cali:GE12kpl8uZ68stOw;"
}
}
У выхода две особенности. Два правила drop перед политиками отбрасывают пакеты, которые под сам завернул в VXLAN (UDP 4789) или IP-in-IP — туннели, в которых Calico возит пакеты подов между узлами (часть 14): так под не может выдать свой пакет за туннельный трафик между узлами. А политики allow-dns и default-deny для выхода выбирают поды одним селектором {}, поэтому Felix собрал их в группу filter-cali-go-…: allow-dns ставит «принято» пакетам к подам CoreDNS (их адреса — в своём наборе cali40s-…), остальные проходят через пустую default-deny, не получают ни «принято», ни Pass и отбрасываются правилом End of tier default. Счётчики соседних правил здесь не сходятся: когда меняются политики, Felix переписывает правила цепочек, и у нового правила счёт начинается с нуля.
GlobalNetworkPolicy. Применяем tier infra и политику и повторяем проверку из sandbox, где своих политик нет:
$ kubectl get tiers.projectcalico.org -o custom-columns=NAME:.metadata.name,ORDER:.spec.order,DEFAULT:.spec.defaultAction
NAME ORDER DEFAULT
calico-system 100 Deny
default 1000000 Deny
infra 200 Pass
kube-admin 1000 Pass
kube-baseline 10000000 Pass
$ kubectl -n sandbox exec probe -- nc -zv -w 2 192.168.10.31 445
nc: 192.168.10.31 (192.168.10.31:445): Connection timed out
command terminated with exit code 1
$ kubectl -n sandbox exec probe -- nc -zv -w 2 192.168.10.32 443
192.168.10.32 (192.168.10.32:443) open
К файловому серверу из sandbox не пройти, к остальной сети — пожалуйста: Pass отдал решение дальше, а в sandbox политик нет. Под probe в sandbox работает на k8s-node-1 с адресом 172.20.211.36, и в журнале ядра этого узла видна каждая попытка — первый SYN и его повтор через секунду:
user@k8s-node-1:~$ sudo journalctl -k --since -3min | grep calico-packet
Oct 29 17:14:27 k8s-node-1 kernel: calico-packet: IN=cali798b0e33dc9 OUT=ens18 MAC=ee:ee:ee:ee:ee:ee:e6:ba:dd:df:35:bf:08:00 SRC=172.20.211.36 DST=192.168.10.31 LEN=60 TOS=0x00 PREC=0x00 TTL=63 ID=40761 DF PROTO=TCP SPT=33081 DPT=445 WINDOW=64860 RES=0x00 SYN URGP=0 MARK=0x80000
Oct 29 17:14:28 k8s-node-1 kernel: calico-packet: IN=cali798b0e33dc9 OUT=ens18 MAC=ee:ee:ee:ee:ee:ee:e6:ba:dd:df:35:bf:08:00 SRC=172.20.211.36 DST=192.168.10.31 LEN=60 TOS=0x00 PREC=0x00 TTL=63 ID=40762 DF PROTO=TCP SPT=33081 DPT=445 WINDOW=64860 RES=0x00 SYN URGP=0 MARK=0x80000
SRC=172.20.211.36 — адрес пода, ещё до NAT: политика срабатывает раньше, чем natOutgoing подменит его на адрес узла. В цепочке выхода пода теперь первым идёт tier infra:
user@k8s-node-1:~$ sudo nft list chain ip calico filter-cali-fw-cali798b0e33dc9
table ip calico {
chain filter-cali-fw-cali798b0e33dc9 {
ct state established,related counter packets 6 bytes 312 accept comment "cali:J_LfkfwvC54l83v2;"
ct state invalid counter packets 0 bytes 0 drop comment "cali:MfiUREkd1FQP_HzX;"
counter packets 4 bytes 240 meta mark set meta mark & 0xfffcffff comment "cali:tz4G1cxLiOD-ZEMK;"
udp dport 4789 counter packets 0 bytes 0 drop comment "cali:kavdU9UVVc0pYmKY; Drop VXLAN encapped packets originating in workloads"
meta l4proto ipencap counter packets 0 bytes 0 drop comment "cali:-6_2lSk8w-Om-V8p; Drop IPinIP encapped packets originating in workloads"
counter packets 3 bytes 180 meta mark set meta mark & 0xfffdffff comment "cali:CLeo9XtaTkaB1LqV; Start of tier infra"
meta mark & 0x00020000 == 0x00000000 counter packets 3 bytes 180 jump filter-cali-po-gnp/infra.deny-pods-to-service-hosts comment "cali:0jLFQEYCXuKDMW2-;"
meta mark & 0x00010000 == 0x00010000 counter packets 0 bytes 0 return comment "cali:THVQXgMg_INj4vLP; Return if policy accepted"
counter packets 1 bytes 60 jump filter-cali-pro-kns.sandbox comment "cali:w9NEpwcoZm1K-J81;"
meta mark & 0x00010000 == 0x00010000 counter packets 1 bytes 60 return comment "cali:uw0CZIYtsHoUOiew; Return if profile accepted"
counter packets 0 bytes 0 jump filter-cali-pro-ksa.sandbox.default comment "cali:RmTNeXNdE1VRzbIu;"
meta mark & 0x00010000 == 0x00010000 counter packets 0 bytes 0 return comment "cali:mGWnLQLcplCSpp5i; Return if profile accepted"
counter packets 0 bytes 0 drop comment "cali:CFU8WikkcUpyKa4J; Drop if no profiles matched"
}
}
user@k8s-node-1:~$ sudo nft list chain ip calico filter-cali-po-gnp/infra.deny-pods-to-service-hosts
table ip calico {
chain filter-cali-po-gnp/infra.deny-pods-to-service-hosts {
counter packets 3 bytes 180 meta mark set meta mark & 0xffe7ffff comment "cali:tPsxEs9yNevw57sV; GlobalNetworkPolicy infra.deny-pods-to-service-hosts egress"
ip daddr 192.168.10.30 counter packets 0 bytes 0 meta mark set meta mark | 0x00080000 comment "cali:BevwyTLT-AsDpjvJ;"
ip daddr 192.168.10.31 counter packets 2 bytes 120 meta mark set meta mark | 0x00080000 comment "cali:8c-HdlA1pEPopaz0;"
meta mark & 0x00080000 == 0x00080000 counter packets 2 bytes 120 log prefix "calico-packet: " level info comment "cali:TS4pb3hyChf-kKaL;"
counter packets 3 bytes 180 meta mark set meta mark & 0xffe7ffff comment "cali:IbLmPGYDsdd-1P9P;"
ip daddr 192.168.10.30 counter packets 0 bytes 0 meta mark set meta mark | 0x00080000 comment "cali:r3dpDZl_FK_647kN;"
ip daddr 192.168.10.31 counter packets 2 bytes 120 meta mark set meta mark | 0x00080000 comment "cali:4IemNuPhSwSiXvWz;"
meta mark & 0x00080000 == 0x00080000 counter packets 2 bytes 120 meta mark set meta mark | 0x00040000 comment "cali:9cqIl-YWHB6_XRPC;"
meta mark & 0x00040000 == 0x00040000 counter packets 2 bytes 120 drop comment "cali:KbiVF72qasWlTRW8;"
counter packets 1 bytes 60 meta mark set meta mark | 0x00020000 comment "cali:BV9Sd-zYRt1FO0Bv;"
}
}
Каждое правило политики Felix разворачивает в несколько строк: адреса из nets по одному отмечают пакет служебным битом 0x80000 («совпало»), а затем действие выполняется по этому биту — log для правила Log (отсюда MARK=0x80000 в журнале), бит 0x40000 и drop для Deny. Последняя строка — наш Pass: бит 0x20000. После него в цепочке пода нет End of tier infra ... drop (у tier'а defaultAction: Pass), а поскольку в sandbox пакет не выбирает больше ни одна политика, он доходит до профиля kns.sandbox и принимается. И проверка с самого узла — служебный сервер доступен ему как прежде:
user@k8s-node-1:~$ nc -zv -w 2 192.168.10.31 445
Connection to 192.168.10.31 445 port [tcp/microsoft-ds] succeeded!
Источники
- Kubernetes: Network Policies — модель изоляции,
policyTypes, исключения для узла и самого пода,hostNetwork, существующие соединения, переписывание адресов,endPort, чего политики не умеют. - Kubernetes: Declare Network Policy — пошаговый пример с политикой и пробным подом.
- Kubernetes: Ephemeral Containers и Debug Running Pods — эфемерные контейнеры,
kubectl debug, профили. - Network Policy API, выпуск v0.2.0 и реализации — ClusterNetworkPolicy, объединение AdminNetworkPolicy и BaselineAdminNetworkPolicy, поддержка в Calico 3.32.
- Calico: Component architecture — Felix, Typha, Calico API server.
- Calico: Enable kubectl to manage Calico APIs (3.32) — Calico API server по умолчанию,
tigerastatus apiserver. - Calico: Get started with Kubernetes network policy и Enable a default deny policy for Kubernetes pods — запрет по умолчанию для неймспейса и для кластера с разрешением DNS.
- Calico: Get started with Calico network policy —
order, запрещающие правила,Log, глобальные политики. - Calico: NetworkPolicy и GlobalNetworkPolicy — поля, синтаксис селекторов, действия,
namespaceSelectorиglobal(). - Calico: Tier и Get started with policy tiers —
order,defaultAction, неявный запрет в конце tier'а, встроенные уровни. - Calico: Global network set и Host endpoint — наборы сетей, конечные точки узлов и их поведение по умолчанию.
- Calico: Felix configuration —
logPrefix,nftablesMode; Data plane guide: nftables — датаплейн nftables и kube-proxy в режиме nftables.
Теперь внутри кластера есть стены: неймспейс demo закрыт по умолчанию и открыт ровно настолько, насколько нужно, а служебные серверы сети узлов закрыты от всех подов политикой, которую команды не могут обойти.
Дальше — тома и CSI: где под хранит данные, которые должны пережить перезапуск, как устроены PersistentVolume, PersistentVolumeClaim и StorageClass, и что делает драйвер CSI, чтобы на узле появился смонтированный каталог.
Оставить комментарий могут только зарегистрированные пользователи.
Войдите на сайт или зарегистрируйтесь, чтобы оставить комментарий.