Про управление входящим в OpenShift трафиком (оно же Ingress) написано много в документации и различных статьях по его настройке. Но, кроме контроля входящего в кластер трафика, в работе зачастую требуется контроль исходящего трафика (Egress). А на эту тему информации, систематизирующей подходы и технические решения, значительно меньше. Постараемся заполнить эту нишу серией постов.
Итак, в каких ситуациях нужен контроль исходящего из кластера трафика?
Можно выделить три типовых сценария использования.
Доступ к внешнему ресурсу контролируется извне кластера
Например, есть внешняя база данных, доступ к которой контролируется межсетевым экраном, и требуется предоставить доступ к этой БД только для определенных POD кластера. По умолчанию POD при взаимодействии с внешними ресурсами используют IP-адрес узла кластера, поэтому задача не решается «в лоб».
Можно выделить несколько узлов исключительно под эти POD, но это зачастую не выход. Узлов понадобится несколько (нам же нужна отказоустойчивость), а требуемых POD может быть совсем немного. Узлы могут получать IP адреса по DHCP, что также затрудняет решение проблемы. Ну и, наконец, таких внешних «зон безопасности» может быть много, и выделять под каждую по два узла совсем не хочется.
Для определенных POD в кластере требуется доступ в изолированные сегменты сети
Типичным случаем здесь является организация доступа определенных POD к сегментам сети, расположенным в другом VLAN (которого нет на узлах кластера) или вообще на другом оборудовании (например, по требованиям PCI DSS).
Создавать по кластеру в каждом таком сегменте — дорого и к тому же тяжело поддерживать. А заводить требуемый VLAN на узлы кластера не будут: его для этого и сделали, чтобы доступ получило только то ПО и оборудование, которое участвует в этом критичном с точки зрения ИБ бизнес-процессе.
К пропускной способности или задержкам сети определенных POD предъявляются особые требования
Например, приложение требует для нормальной работы выделенного соединения 10G Ethernet и полностью его утилизирует. В этом случае также непонятно, как обеспечить эти требования в условиях одновременной работы десятков или сотен POD на одном рабочем узле. Не выделять же полностью узел кластера под один подобный POD.
На все вышеописанные сценарии есть решения, которые связаны с управлением исходящего из кластера OpenShift трафика. Попробуем разобраться, в каком случае какое решение по Egress применять, и как оно работает.
Egress IP и Egress Router
Static IP Egress
В OpenShift, начиная с версии 3.7, появилась возможность выделить фиксированные адреса на namespace, и именно с этих адресов все POD в этом namespace будут обращаться к внешним ресурсам кластера. Реализовано это средствами OpenShift SDN.
Как это работает:
- Создадим проект «egress» и запустим в нем тестовый POD «tool»:
[ocp@shift-is01 ~]$ oc new-project egress Now using project «egress» on server «https://api.ocp4.demo.local:6443». [ocp@shift-is01 ~]$ kubectl run tool -it --image=rhel7/rhel-tools --restart=Never -- bash If you don't see a command prompt, try pressing enter.
- Выполним запрос к внешнему серверу с POD и посмотрим, с какого адреса мы пришли:
[root@tool /]# curl www.ocp4.demo.local:8080/test Hello ! [root@shift-is01 httpd]# tail -f ./access_log 192.168.111.204 — - [30/Oct/2020:14:00:32 +0300] «GET /test HTTP/1.1» 200 8 «-» «curl/7.29.0»
- Видно, что «наружу» из POD tool мы попадаем с узла кластера shift-worker1:
[ocp@shift-is01 ~]$ oc get pod tool -o jsonpath='{ .status.hostIP }{»\n»}' 192.168.111.204 [ocp@shift-is01 ~]$ oc get hostsubnets.network.openshift.io shift-worker0 shift-worker1 NAME HOST HOST IP SUBNET EGRESS CIDRS EGRESS IPS shift-worker0 shift-worker0 192.168.111.203 10.254.3.0/24 shift-worker1 shift-worker1 192.168.111.204 10.254.4.0/24
- Присвоим по одному статическому IP-адресу для egress на два узла кластера и укажем, что эти адреса должны использоваться для egress трафика в namespace «egress»:
[ocp@shift-is01 egress]$ oc patch hostsubnet shift-worker0 --type=merge -p '{«egressIPs»: [«192.168.111.225»]}' hostsubnet.network.openshift.io/shift-worker0 patched [ocp@shift-is01 egress]$ oc patch hostsubnet shift-worker1 --type=merge -p '{«egressIPs»: [«192.168.111.226»]}' hostsubnet.network.openshift.io/shift-worker1 patched [ocp@shift-is01 egress]$ oc patch netnamespace egress --type=merge -p '{«egressIPs»: [«192.168.111.225», «192.168.111.226»]}' netnamespace.network.openshift.io/egress patched
- После этого OpenShift создаст network alias на основном интерфейсе узла (интерфейс с суффиксом :eip, то есть интерфейс для egress IP):
[ocp@shift-is01 egress]$ ssh core@shift-worker0 ip a show dev ens192 2: ens192: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000 link/ether 00:50:56:96:48:19 brd ff:ff:ff:ff:ff:ff inet 192.168.111.203/24 brd 192.168.111.255 scope global noprefixroute ens192 valid_lft forever preferred_lft forever inet 192.168.111.225/24 brd 192.168.111.255 scope global secondary ens192:eip valid_lft forever preferred_lft forever inet6 fe80::250:56ff:fe96:4819/64 scope link valid_lft forever preferred_lft forever [ocp@shift-is01 egress]$ ssh core@shift-worker1 ip a show dev ens192 2: ens192: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000 link/ether 00:50:56:96:0c:ef brd ff:ff:ff:ff:ff:ff inet 192.168.111.204/24 brd 192.168.111.255 scope global noprefixroute ens192 valid_lft forever preferred_lft forever inet 192.168.111.226/24 brd 192.168.111.255 scope global secondary ens192:eip valid_lft forever preferred_lft forever inet6 fe80::250:56ff:fe96:cef/64 scope link valid_lft forever preferred_lft forever
- Теперь при запуске обращения из нашего контейнера видно, что мы приходим к WEB-серверу с назначенного Egress адреса:
[root@shift-is01 httpd]# tail -f ./access_log 192.168.111.225 — - [30/Oct/2020:15:17:05 +0300] «GET /test HTTP/1.1» 200 8 «-» «curl/7.29.0»
Всё работает, но непонятно, как именно:) Например, контейнер, как мы видели ранее, работает на узле shift-worker1. На этот узел был назначен адрес Egress IP = 192.168.111.226. Так почему же мы выходим из кластера с Egress IP = 192.168.111.225?
Ответ на этот вопрос дает статья How to Enable Static Egress IP in OCP. Давайте с ее помощью выясним, что же происходит «под капотом» OpenShift при использовании Egress IP.
- В таблице NAT в iptables на shift-worker1 есть такие строчки:
---- skipped ----- Chain OPENSHIFT-MASQUERADE (1 references) target prot opt source destination SNAT all -- 10.254.0.0/16 anywhere mark match 0xe28820 to:192.168.111.226 RETURN all -- anywhere anywhere mark match 0x1/0x1 OPENSHIFT-MASQUERADE-2 all -- 10.254.0.0/16 anywhere /* masquerade pod-to-external traffic */
То есть iptables на этом узле изменит source address на 192.168.111.226 для всех пакетов c меткой 0xe28820. Аналогичная картина будет и на shift-worker0, только адрес для трансляции будет 192.168.111.225. - Осталось выяснить, откуда берется эта метка. А метку ставит OpenShift SDN на все пакеты, которые выходят из namespace «egress».
Принадлежность к проекту определяется его virtual network ID (VNID), в нашем случае 0xe28820 — это шестнадцатеричное значение VNID для проекта egress.
OpenShift SDN заворачивает все подобные пакеты в tunnel interface vxlan0 с указанием, что доставить пакет надо на хост shift-worker0:
[root@shift-worker1 ~]# ovs-ofctl -O OpenFlow13 dump-flows br0 table=100 ---- skipped ----- cookie=0x0, duration=4106.403s, table=100, n_packets=6, n_bytes=496, priority=100,ip,reg0=0xe28820 actions=ct(commit),move:NXM_NX_REG0[]->NXM_NX_TUN_ID[0..31],set_field:192.168.111.203->tun_dst,output:vxlan0
Таким образом, OpenShift SDN выбирает «основной» Egress IP-адрес, и все пакеты с проекта помечает меткой и отправляет на хост, содержащий «основной» Egress IP. На этом хосте iptables выполняет Source NAT, и задача решена. Если хост с «основным» Egress IP становится недоступен, то пакеты перенаправляются на следующий в списке Egress IP-адресов хост (в нашем случае это будет shift-worker1).
Если мы выключим хост shift-worker0, то наш тестовый POD «наружу» будет выходить уже с адресом 192.168.111.226, который мы присвоили узлу shift-worker1:
192.168.111.226 — — [30/Oct/2020:15:28:38 +0300] «GET /test HTTP/1.1» 200 8 «-» «curl/7.29.0»
Именно поэтому нам нужно как минимум два IP-адреса при использовании Egress IP. В случае отказа одного узла кластера POD мы сможем получить доступ к внешним ресурсам с заданных IP.
Описанная здесь схема организации Egress хорошо подходит для первого сценария использования, когда доступ к внешним по отношению к кластеру ресурсам определяется межсетевым экраном или самим ресурсом. Применительно к нашему стенду все POD в проекте «egress» (POD2 – POD4) при обращении «наружу» получат заранее определенные адреса (192.168.111.225 или 192.168.111.225) вне зависимости от того, на каком узле кластера они находятся. Остальные POD кластера при обращении к внешним ресурсам получат IP-адрес узла кластера и будут отвергнуты либо МЭ, либо приложением.
Subnet IP Egress
Если нужно сделать фиксированные Egress-адреса для нескольких проектов, то использовать Egress IP неудобно. В этом случае узлам кластера можно указать, какую подсеть использовать, а проекту — какой IP адрес из этой подсети взять.
[ocp@shift-is01 egress]$ oc patch hostsubnet shift-worker0 --type=merge -p '{«egressCIDRs»: [«192.168.111.224/29»]}'
hostsubnet.network.openshift.io/shift-worker0 patched
[ocp@shift-is01 egress]$ oc patch hostsubnet shift-worker1 --type=merge -p '{«egressCIDRs»: [«192.168.111.224/29»]}'
hostsubnet.network.openshift.io/shift-worker1 patched
[ocp@shift-is01 egress]$ oc get hostsubnets.network.openshift.io shift-worker0 shift-worker1
NAME HOST HOST IP SUBNET EGRESS CIDRS EGRESS IPS
shift-worker0 shift-worker0 192.168.111.203 10.254.3.0/24 [«192.168.111.224/29»]
shift-worker1 shift-worker1 192.168.111.204 10.254.4.0/24 [«192.168.111.224/29»]
После этого указываем проекту, какой IP-адрес использовать для исходящего трафика:
[ocp@shift-is01 egress]$ oc patch netnamespace egress --type=merge -p '{«egressIPs»: [«192.168.111.225»]}'
netnamespace.network.openshift.io/egress patched
[ocp@shift-is01 egress]$ oc get netnamespaces egress
NAME NETID EGRESS IPS
egress 14807624 [«192.168.111.225»]
Разумеется, egressCIDR должен быть в той же подсети, что и основной IP-адрес узла. И выделять два адреса на проект в данном случае не требуется — при отказе узла используемый адрес «переедет» на другой доступный узел с установленным egressCIDR (при условии, что этот адрес в него входит).
Egress Router
Рассказывая про Egress в OpenShift, важно также упомянуть про Egress router. Он применялся еще до того, как появилась возможность контролировать исходящий трафик средствами Openshift SDN. В качестве решения использовался специальный POD, у которого один адрес находился в кластерной сети OpenShift, а другой получал внешний IP-адрес и доступ к физическому интерфейсу узла через macvlan CNI plugin.
Этот POD выступал в роли маршрутизатора между внутренней сетью кластера и внешними сетями за пределами OpenShift. Пример управления исходящим трафиком через Egress router можно посмотреть на странице Red Hat OpenShift Blog. Но так как конфигурация на базе OpenShfit SDN проще в настройке и поддержке, описание решения на базе Egress router в OpenShift версии 4 удалено из документации.
Vanilla Kubernetes
А что с управлением Egress трафиком в «ванильном» Kubernetes? К сожалению, единого решения здесь нет. Все зависит от возможностей SDN, который используется в работе кластера. Например, Calico позволяет отключить Source NAT для определенного пула IP-адресов. Но в этом случае вопросы маршрутизации трафика между POD кластера и внешним миром придется решать самостоятельно.
Резюме
В OpenShift можно штатными средствами SDN организовать контроль IP-адресации исходящего трафика, чтобы обеспечить требования внешних систем контроля доступа. При этом необходимо понимать возникающие в работе кластера технические нюансы:
- Весь исходящий трафик от проекта с фиксированным Egress IP будет направлен через один узел кластера вне зависимости от того, где находятся POD этого проекта. Если у вас на кластере большая нагрузка на сетевую подсистему, то это может увеличить задержки в передаче данных;
- Network alias для Egress IP будет создан на первом с точки зрения OC интерфейсе. Поэтому, если на узлах несколько интерфейсов, необходимо, чтобы «основной» из них был первым;
- Вам придется заняться IP address management для Egress IP. Это очевидно: раз вы сами занимаетесь выделением IP, то и контроль за корректностью тоже будет за вами.
Описанные выше способы организации Egress прекрасно помогают решить проблемы, когда нам нужно для определенных проектов получить фиксированные и отдельные от кластера IP адреса для исходящего трафика. Но что делать, если нам необходимо попасть в другой VLAN?
Подмена IP-адресов здесь никак не поможет. Решением является использование CNI «meta-plugin» Multus. Multus CNI plugin стал доступен в качестве поддерживаемого компонента в OpenShift 4.1 и фактически позволяет организовать подключение POD к нескольким сетям одновременно.
Настройке и использованию Multus CNI Plugin в OpenShift посвятим вторую часть этого поста.
До встречи!
Список литературы
https://docs.openshift.com/container-platform/4.6/networking/openshift_sdn/assigning-egress-ips.html
https://examples.openshift.pub/networking/egress-ip/
https://www.openshift.com/blog/how-to-enable-static-egress-ip-in-ocp
www.openshift.com/blog/accessing-external-services-using-egress-router
Автор: Сергей Артемов, руководитель отдела DevOps-решений «Инфосистемы Джет»
andreivpetrov
А что скажите по поводу скорости переключения в случае отказа ноды?
Проводили замеры ?
На мой взгляд эту технологию лучше обходить стороной по следующим причинам:
JetHabr Автор
Специально замеров не делали, навскидку это секунды. То есть после выключения узла немедленно запускали тест, все работало.
Если говорить про обрыв соединений, то они всегда будут при отказе узла, и приложение в идеале должно это нормально переживать.
В случае фиксированного Egress IP масштаб «потерь» будет больше, согласны с вами.
По поводу нужно это или нет — это вопрос требований от ИБ. Если можно обойтись Network Policy внутри кластера, то, конечно, лучше не затевать привязку к адресам, это вообще противоречит «облачной» природе Kubernetes. Но если по другому не выходит, то потеря сессий при отказе узла — типичное «наименьшее зло».
andreivpetrov
Тут я с вами согласен что ИБ проще работать с ИП адресами… и кого заботит что правила ни кто не подчищает после удаления проекта =)