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



    Про управление входящим в 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: <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
    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-адрес узла кластера и будут отвергнуты либо МЭ, либо приложением.


    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-решений «Инфосистемы Джет»
    Инфосистемы Джет
    Системный интегратор

    Комментарии 5

      0

      А что скажите по поводу скорости переключения в случае отказа ноды?
      Проводили замеры ?


      На мой взгляд эту технологию лучше обходить стороной по следующим причинам:


      1. Весь траффик идет через одну ноду.
      2. В случае отказа ноды с egress ip у Вас гарантированно будет разрыв всех сетевых соединений не зависимо от того сколько реплик приложения вы запустили.
        0
        Специально замеров не делали, навскидку это секунды. То есть после выключения узла немедленно запускали тест, все работало.

        Если говорить про обрыв соединений, то они всегда будут при отказе узла, и приложение в идеале должно это нормально переживать.
        В случае фиксированного Egress IP масштаб «потерь» будет больше, согласны с вами.

        По поводу нужно это или нет — это вопрос требований от ИБ. Если можно обойтись Network Policy внутри кластера, то, конечно, лучше не затевать привязку к адресам, это вообще противоречит «облачной» природе Kubernetes. Но если по другому не выходит, то потеря сессий при отказе узла — типичное «наименьшее зло».
          0
          Тут я с вами согласен что ИБ проще работать с ИП адресами… и кого заботит что правила ни кто не подчищает после удаления проекта =)
        0
        Странная особенность, что присваивается два разных адреса двум узлам, но второй адрес используется только при недоступности первого.
        Почему бы не использоваться для ограничения исходящего траффика EgressNetworkPolicy?
          0
          Конечно, можно использовать Egress Policy в кластере. Но представьте ситуацию:
          1. У вас коммунальный кластер, в котором «живут» две независимые организации (A и B). Они получают namespace в кластере, но расположены удаленно за своими корпоративными МЭ.
          2. Они присылают запрос: сообщить все адреса, с которых будут выходить их POD, чтобы создать правила доступа на их МЭ.

          В этом случае вы можете:
          1. Убедить всех, что вы правильно настроили Network Policy и POD организации A никогда не смогут получить доступ в сети организации B и наоборот. Также от вас потребуется клятва, что вы будете поддерживать Network Policy в актуальном состоянии, потому что требования к доступам будут меняться.
          2. Зафиксировать за A и B набор IP-адресов (через Egress IP) и сообщить их А и В

          Первый вариант мало реалистичен, так как в этом случае контроль доступа организации А и В полностью передают вам. А они могут и будут вам не доверять. И предвосхищая комментарии — да, выделенный кластер под каждую организацию лучше. Но и дороже)

        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

        Самое читаемое