Про управление входящим в 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-решений «Инфосистемы Джет»