Все об OpenShift Egress. Часть 1

d0o_rs9fvpuo3h6jlx7iiexszng.jpeg

Про управление входящим в 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.

Как это работает:

  1. Создадим проект «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.
  2. Выполним запрос к внешнему серверу с 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»
  3. Видно, что «наружу» из 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
  4. Присвоим по одному статическому 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
  5. После этого OpenShift создаст network alias на основном интерфейсе узла (интерфейс с суффиксом : eip, то есть интерфейс для egress IP):
    [ocp@shift-is01 egress]$ ssh core@shift-worker0 ip a show dev ens192
    
    2: ens192:  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:  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
  6. Теперь при запуске обращения из нашего контейнера видно, что мы приходим к 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.

  1. В таблице 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.
  2. Осталось выяснить, откуда берется эта метка. А метку ставит 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-адрес узла кластера и будут отвергнуты либо МЭ, либо приложением.

360d0de7ec4b660ec5302dd5ee4342a0.png


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-адресации исходящего трафика, чтобы обеспечить требования внешних систем контроля доступа. При этом необходимо понимать возникающие в работе кластера технические нюансы:

  1. Весь исходящий трафик от проекта с фиксированным Egress IP будет направлен через один узел кластера вне зависимости от того, где находятся POD этого проекта. Если у вас на кластере большая нагрузка на сетевую подсистему, то это может увеличить задержки в передаче данных;
  2. Network alias для Egress IP будет создан на первом с точки зрения OC интерфейсе. Поэтому, если на узлах несколько интерфейсов, необходимо, чтобы «основной» из них был первым;
  3. Вам придется заняться 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-решений «Инфосистемы Джет»

© Habrahabr.ru