Управление трафиком в Kubernetes-кластере с Calico
Практически каждый инженер, практикующий DevOps, в какой-то момент сталкивается с задачей настройки правил доступа для своих проектов. В данной статье мы рассмотрим примеры настройки сетевых политик Kubernetes-кластера, в котором используется плагин Calico и осветим некоторые интересные моменты. Предполагаем, что у вас уже имеется кластер k8s, где в качестве сетевого плагина используется Calico.
Одно из ключевых и очевидных преимуществ Calico перед другими плагинами — это значительное расширение функционала для настройки сетевых политик — возможность в качестве источников и целей при создании правил задавать диапазоны портов и IP-адресов, протоколы, атрибуты HTTP-запросов, и даже возможность задавать логирование определенного трафика.
Помимо этого Calico добавляет свою политику GlobalNetworkPolicy
. Она позволяет применять набор правил доступа по селектору не только для pod«ов, но и для групп хостов/контейнеров/pod«ов, и контролировать применение мер безопасности для различных цепочек путей трафика с помощью таких параметров, как preDNAT
, doNotTrack
и applyOnForward
.
Задачу по обеспечению базовых принципов безопасности мы условно разделим на две: на подзадачу по настройке взаимодействия между pod«ами и на подзадачу по настройке доступа к узлу в целом. Решить первую подзадачу можно при помощи такой сущности Kubernetes как NetworkPolicies
и при этом можно расширить функционал данной сущности, если использовать её с api, предоставляемого Calico. Для решения второй подзадачи мы будем использовать сущность GlobalNetworkPolicy
. По ходу дела так же будем анализировать детали и начнем с Zero Trust Networking.
Zero Trust Networking
Первое, что стоит учесть при написании сетевых политик, это то, что и Kubernetes, и Calico считают наилучшим подход Zero Trust Networking, то есть запретить все, что явно не разрешено.
Согласно данной концепции существуют следующие требования к контролю доступа:
- Правила применяются ко всем сетевым подключениям (не только к тем, которые пересекают границы защищаемой зоны).
- Идентификация удаленного endpoint всегда основана на нескольких критериях, включая надежные криптографические доказательства идентичности. В частности, идентификаторы сетевого уровня, такие как IP-адрес и порт, сами по себе недостаточны, поскольку могут быть подделаны враждебной сетью.
- Все ожидаемые и разрешенные сетевые подключения явно указаны в белом списке. Любое соединение, явно не занесенное в белый список, отклоняется.
- Трафик от скомпрометированных workload (pod/VM/container) не должен иметь возможности обойти применение сетевых политик.
- Во многих Zero Trust Networks также реализуется шифрование всего сетевого трафика. Это не является обязательным требованием (если речь идет не о передаче приватных данных), но для соответствия критериям Zero Trust Networks шифрование должно использоваться для каждого сетевого соединения.
Эти принципы влияют, к примеру, на следующее: если есть какая-либо сущность, имеющая метку, то по умолчанию весь трафик для неё разрешён. Но если будет создана хоть одна Policy, в которой будет фигурировать метка конкретной сущности, то для этой сущности будет запрещён весь трафик, кроме того, который был явно разрешён в только что созданной или уже имеющихся политиках.
Аналогично с HostEndpoint
, как только он будет создан, весь трафик к хосту будет запрещен, кроме определенного перечня портов, который останется открытым. Причем, даже если вы создадите политики, которые явно запрещают доступ к этим портам, они останутся открытыми:
Для того, чтобы запретить доступ к этим портам нужно будет изменить конфигурацию Felix. Felix — это компонент Calico, демон, запущенный на всех машинах, которые обслуживает Calico.
Из чего состоит Calico
Кратко рассмотрим основные компоненты Calico. Как вы уже, наверное, поняли, Calico — это не один демон, который запущен на каждом хосте, где он управляет сетью, Calico состоит из нескольких элементов, так же как и Kubernetes. В целом он состоит из трех (иногда четырех) компонентов.
Felix
Основной компонент — это демон Felix, он отвечает за построение логики взаимодействия между сетевыми интерфейсами на хосте, и, как уже было сказано выше, за создание и функционирование endpoint на хостах или ВМ. В целом он выполняет следующие функции:
- Управление сетевыми интерфейсами, их создание, проверка их работоспособности и настройка пересылки трафика для управляемых интерфейсов.
- Программирование маршрутизации трафика через FIB (Forwarding Information Base) ядра Linux.
- Программирование ACL на уровне ядра Linux.
- Уведомления о статусе сети, Felix записывает данные о проблемах и ошибках в etcd.
Плагин для оркестратора
В зависимости от того, какой оркестратор вы используйте (например, OpenStack, Kubernetes) Calico предоставляет определенный плагин для наиболее эффективного взаимодействия с оркестратором. В случае Kubernetes — это CNI plugin.
etcd
Так же Calico использует etcd. etcd — это распределенное хранилище ключ-значение, которое выполняет функцию базы данных и служит шиной для коммуникации между компонентами Calico. В случае, если вы используйте Kubernetes, они с Calico будут использовать одно и то же etcd.
BGP клиент (BIRD)
Calico разворачивает клиент BGP на каждой ноде, на которой размещен Felix. Роль клиента BGP заключается в том, чтобы считывать настройки маршрутизации, которые Felix создает на уровне ядра, и распространять на другие ноды обслуживаемой сети, то есть рассылать на другие клиенты.
BGP повторитель для правил маршрутов (BIRD)
В случае, когда обслуживаемая Calico область достаточно велика при наличии только BGP клиента могут возникнуть проблемы, так как он требует чтобы каждый клиент мог соединиться с каждым клиентом, что может порождать слишком большое количество соединений (N ^ 2). В таких случаях для решения проблемы можно использовать повторитель маршрутов, то есть соответственно настроенный BIRD. Он занимается тем, что, если клиент BGP сообщает ему какую-либо информацию об изменении в маршрутах, он распространяет эту информацию для других BGP-клиентов.
Network Policy
NetworkPolicy
, который предоставляет Calico (через api projectcalico.org) имеют более читаемый и развернутый синтаксис, чем тот, который предлагает сам Kubernetes (через api networking.k8s.io).
Ресурс сетевой политики NetworkPolicy
представляет собой упорядоченный набор правил, которые применяются к группе endpoints, соответствующих селектору меток (labels). Простой пример из официальной документации:
apiVersion: projectcalico.org/v3
kind: NetworkPolicy
metadata:
name: allow-tcp-6379
namespace: production
spec:
selector: role == 'database'
types:
- Ingress
- Egress
ingress:
- action: Allow
protocol: TCP
source:
selector: role == 'frontend'
destination:
ports:
- 6379
egress:
- action: Allow
Здесь мы разрешаем трафик с подов/контейнеров, у которых есть селектор role == 'frontend'
на порт 6379 для доступа к БД.
Host Endpoint
У каждого хоста (ноды) есть несколько реальных или виртуальных сетевых интерфейсов, с которыми взаимодействует Calico. Чтобы получить возможность применять политики конкретно к определенному интерфейсу необходимо создать для него сущность HostEndpoint
. Они могут иметь метки (labels) и эти метки аналогичны меткам для pod«ов, поэтому сетевые политики в равной степени могут применяться и к HostEndpoint
, и к endpoints pod«ов.
Предположим, что нам нужно запретить весь входящий трафик из внешней сети, кроме портов 22, 80, 443 на уровне хоста и при этом разрешить весь трафик от других нод кластера. Для этого, для начала, создадим для каждой ноды свой HostEndpoint
. К примеру:
apiVersion: projectcalico.org/v3
kind: HostEndpoint
metadata:
name: node4-ens160
labels:
type: production
role: worker
node: 4
spec:
interfaceName: ens160
node: k8s-s4
expectedIPs:
- 10.213.0.11
ports:
- name: http
port: 80
protocol: TCP
- name: https
port: 443
protocol: TCP
В данном случае мы добавили также раздел ports, где описали определенные порты и протоколы для взаимодействия с ними. Мы сделали это для того, чтобы обозначить их и дать им названия (http, https), которые в дальнейшем можно использовать в сетевых политиках. interfaceName
— название внешнего интерфейса сервера которому соответствует IP-адрес expectedIPs
. Указывать порт 22 нет необходимости, так как он разрешен на уровне конфигурации Felix.
Global Network Policy
Теперь, когда почти весь трафик к ноде заблокирован, создадим GlobalNetworkPolicy
, которая разрешит весь трафик с других нод кластера, входящий трафик на порты 80/443 и весь исходящий трафик для этого HostEndpoint
:
kind: GlobalNetworkPolicy
apiVersion: projectcalico.org/v3
metadata:
name: allow-s4
spec:
selector: role==worker
order: 10
applyOnForward: true
types:
- Egress
- Ingress
ingress:
- action: Allow
protocol: TCP
source:
nets:
- 10.213.0.0/24
- action: Allow
protocol: TCP
destination:
ports: [http,https]
- action: Allow
protocol: ICMP
egress:
- action: Allow
В целом, все параметры GlobalNetworkPolicy
аналогичны NetworkPolicy
и интуитивно понятны. order
— очередность применения политики, чем она меньше, тем раньше будет применено правило.
Однако, для GlobalNetworkPolicy
есть несколько интересных параметров, которые предоставляют дополнительные возможности для настройки политик, а именно: preDNAT, doNotTrack и applyOnForward
, рассматриваемые ниже.
Опции applyOnForward, preDNAT и doNotTrack
Чтобы понять в каких случаях вам следует применять данные параметры, мы кратко рассмотрим как изменяется обработка трафика, если их включить. Более подробно и развернуто об это можно почитать в статье (ссылка на перевод) от Bikram Gupta.
applyOnForward
Параметр applyOnForward
отвечает за то, применяется ли данная политика к трафику, который проходит по цепочке iptabels FORWARD
. Например, трафик, направленный к pod«у. Так как pod является внешним ресурсом с точки зрения сети хоста (не считается локальным процессом), следовательно, трафик до него проходит по маршруту цепочек PREROUTING — FORWARD — POSTROUTING.
Если applyOnForward
имеет значение false, GlobalNetworkPolicy
к трафику для локальных workload (контейнер/pod/ВМ, которые имеют свои виртуальные интерфейсы) не применится. Она будет применяться только к трафику для локальных процессов и к трафику, исходящему от них. Пример приведу далее.
Если applyOnForward
имеет значение true, GlobalNetworkPolicy
также применяется к перенаправленному (forwarded) трафику, такому как:
- Трафик, который поступает через
HostEndpoint
и перенаправляется в локальные workload. - Трафик из локальных workload, который перенаправляется через
HostEndpoint
. - Трафик, который поступает через
HostEndpoint
и пересылается на другойHostEndpoint
.
По умолчанию applyOnForward
имеет значение false. Для политик, использующих опции doNotTrack и preDNAT
, для свойства applyOnForward
должно быть установлено значение true, поскольку эти политики применяются ко всему трафику, проходящему через FORWARD
.
По умолчанию пересылаемый трафик (FORWARD
)разрешен, если к пункту его назначения или направлению не применены какие-либо политики. Иначе говоря: если у вас настроен HostEndpoint
, но нет политик с параметром applyOnForward: true
для этого HostEndpoint или направления трафика, то пересылаемый трафик будет разрешен. Если существуют политики applyOnForward:true
, в которых упоминаются селекторы HostEndpoint
или это направление, но никакие правила в политиках не разрешают трафик, трафик отклоняется.
Пример: вы создаете GlobalNetworkPolicy
, разрешающую исходящий трафик по протоколу ICMP для данного HostEndpoint с applyOnForward:false
. В этом случае, если вы попытаетесь сделать ping 8.8.8.8 из pod«а на данном хосте, вам это удастся, так как forwarded трафик по умолчанию разрешен, хотя GNP применилась только к локальному трафику. Но. Если вы создадите ещё одну политику для этого HostEndpoint
, но уже с applyOnForward:true
, например, вы разрешаете весь TCP-трафик, то ping 8.8.8.8 изнутри пода не пройдет. Потому что уже существует политика с applyOnForward:true
для этого типа трафика (этого HostEndpoint
) и этот тип трафика явно не разрешен.
preDNAT
Данная опция отвечает за то, применяется ли политика до прохождения через DNAT (Destination Network Address Translation) или нет.
Это может пригодится, например, если вы используйте NodePorts
и хотите контролировать трафик, который приходит извне на эти порты. После того, как трафик извне приходит на NodePort
его распределение по факту происходит при помощи DNAT (kube-proxy). Следовательно, для того, чтобы применить политику к трафику, приходящему на NodePort, preDNAT
должен быть установлен в true.
Ключевые особенности для таких политик следующие:
- Политики с опцией
preDNAT
могут иметь правила только для входящего трафика, но не исходящего. - Политика применяется для всего трафика, проходящего через
host endpoint
, не только к процессам на хосте, но и к локальным workload (pod/VM/container). - По умолчанию весь
preDNAT
трафик разрешен. То есть если существует HostEndpoint, но для него нетpreDNAT
политик, для него не действует правило «отбрасывать все по умолчанию».
doNotTrack
Опция doNotTrack
позволяет отключить отслеживание (conntrack) трафика, который попадает под данную политику, причем именно для локальных процессов хоста (не pod / VM / container).
conntrack — это опция ядра из сетевого стека Linux, которая помогает ему отслеживать сетевые потоки, чтобы была возможность обрабатывать пакеты из одного потока одинаково, на основании правила обработки выбранного для первого пакета из потока.
Но эта опция также накладывает ограничение на количество соединений, по достижению которого соединения отклоняются. Хотя в обычных условиях практически нереально достигнуть его исчерпания, есть несколько сценариев при которых возникнет необходимость в отключении этой опции:
- Если вам необходимо одновременно обрабатывать активных соединений больше, чем установленное значение в таблице conntrack (больше 128k по умолчанию).
- Если вам необходимо обрабатывать большое количество коротких соединений в секунду. Так как даже после завершения соединения conntrack продолжает отслеживать соединение в течении некоторого времени (по умолчанию 120с). Например, если в таблице conntrack установлено значение на 128к доступных подключений, и вы пытаетесь обрабатывать 1100 соединений в секунду, лимит будет исчерпан, даже если эти подключения очень кратковременные (128k / 120s = 1092 connections/s).
Такие цифры вам могут пригодиться, например, если Вы используйте memcached, который функционирует непосредственно на хосте и при этом принимает большое количество частых и кратковременных соединений. Если вы используете Calico для защиты хоста этого сервера, вы можете избежать этой проблемы, определив политику, которая разрешает доступ к портам сервера и помеченную как doNotTrack. Подробнее про то, какое влияние оказывает включение этой опции на обработку объемного трафика можно почитать [здесь] (тут ссылка на наш перевод).
Так как для активации doNotTrack политика должна примениться в самом начале цепочек OUTPUT и PREROUTING, она действует раньше других (тех, для которых doNotTrack:false
), вне зависимости от её order
. То есть одна политика без doNotTrack
может иметьorder:1
, а другая с doNotTrack
может иметь order:1000
, но раньше подействует политика с doNotTrack
. Порядок оrder соблюдается только между политиками данного типа.
Заключение
Таким образом, мы с вам получили представление о том, как реализовать при помощи сетевых политик Calico нужные вам разграничения трафика для Kubernetes-кластера и какие опции политик при этом использовать.
Материалы используемые для публикации: