Node-Local Virtual IP в OpenStack
Нашей команде по исследованию облачных технологий (Cloud Tech Research Team) из Лаборатории передовых программных технологий (Advanced Software Technology Lab) Huawei достаточно часто приходится оптимизировать работу тех или иных компонентов облачной инфраструктуры. При решении одной из таких задач по обновлению 5G Core приложений на очень больших кластерах нами была предложена и разработана технология Local IP в OpenStack Neutron.
Local IP – это виртуальный IP, который работает в рамках физического сервера и служит для перенаправления запросов в локальный порт/виртуальную машину.
Технология Local IP будет полезна:
(в общем случае) при реализации side-car прокси, балансировщиков нагрузки или service mesh;
(в large-scale и/или high-performance) при построении сервисов распределенного кеширования данных и CDN. Так же технология позволит упростить использование распределенных кэшей в облаках построенных на основе OpenStack. Например, запустив виртуальные машины с Docker Registry (в режиме passthrough) на каждой физическом сервере, можно добиться значительного сокращения времени скачивания образов и времени старта контейнеров, запускаемых в виртуальных машинах;
(в large-scale) при реализации паттерна load-balance-at-source.
Доступно два режима работы Local IP:
translate (по умолчанию) – поведение Local IP будет аналогично DNAT;
passthrough – пакеты будут перенаправляться без изменения IP заголовков. В этом случае ожидается, что гостевая OS виртуальных машин отвечает за конфигурирование Local IP адреса на интерфейсе. Данный режим будет полезен для реализации Fault-tolerance или похожих сценариев.
Как это работает?
Neutron – это компонент управляющий сетью в OpenStack (будем рассматривать стандартную инсталляцию с ML2 плагином и OpenvSwitch). Для тех читателей, которые не знаком с OpenStack Networking рекомендую обратить внимание на эту статью в которой достаточно подробно описаны все аспекты.
Neutron при помощи агентов на каждом физическом сервере конфигурирует OpenvSwitch (OVS), который в свою очередь создает несколько бриджей:
бридж «br-int» (так называемый «integration bridge») служит для работы с локальными портами/виртуальными машинами;
бридж «br-ex» (или «provider bridge») используется для взаимодействия с внешними сетями;
бридж «br-tun» (или «tunnel bridge») предназначен для взаимодействия между хостами под управлением OpenStack.
Каждый бридж в OVS описан при помощи нескольких таблиц, которые содержат наборы правил соответствия пакетов тем или иным критериям и список действий над пакетами удовлетворяющими этим критериям.
Вся логика работы Local IP реализована внутри «br-int», поэтому трансляция адресов происходит локально, не выходя за границы физического сервера. Это позволяет использовать один и тот же адрес для Local IP на разных физических серверах.
На рисунке, в упрощенном виде, изображен OpenFlow pipeline для «br-int» с правилами и набором действий для функционирования Local IP. Правила расположены в порядке приоритета выполнения от высшего к низшему. Более подробнее о том какие таблицы в себе содержит «br-int» и какие правила в этих таблицах содержатся можно узнать при помощи следующей команды «ovs-ofctl dump-flows br-int», выполнив её на физическом сервере под управлением OpenStack с OVS. Тем читателям, кто хочет разобраться как конфигурируется OpenvSwitch рекомендую к ознакомлению эту статью.
Логика работы Local IP, на первый взгляд, очень простая: используя DNAT нужно обеспечить трансляцию (подмену) dst MAC и IP адресов с Local IP на Fixed IP. Это описано правилами Static Local IP Translation в таблице 31 «Local IP». NAT реализован двумя способами и может быть настроен используя опцию static_nat:
(static_nat = False, значение по умолчанию) через contrack с использованием ct nat action;
(static_nat = True) с использованием static NAT правил, добавляемых с помощью LEARN action в обратном flow используя следующие данные из заголовка пакета: MAC адреса отправления/назначения, IP адреса отправления/назначения, протокол и порты отправления/назначения. Поддерживается VLAN изоляция, возможность использовать Local IP вместе с OpenvSwitch Firewall и позволяет использовать SmartNIC для OpenvSwitch offloading и функционала DPDK. Сразу отмечу, что при этом Local IP использует большее количество flows в таблицах, что может влиять на производительность.
Помимо этого, необходимо учесть следующие граничные сценарии:
Локальный хост с Local IP должен иметь возможность обращаться к внешнему хосту с таким же IP адресом;
Локальный хост может сформировать ARP запрос, чтобы узнать MAC адрес хоста с Local IP;
На одном физическом сервере для разных сетей может быть установлен один и тот же Local IP на разные локальные хосты;
Внешний хост может прислать «Gratuitous ARP» или «ARP Announcement» пакет с тем же IP адресом, что уже используется для Local IP.
Для того чтобы локальный хост с Local IP мог обращаться к внешнему хосту с таким же IP адресом, нужно для таких пакетов игнорировать все правила трансляции «Static Local IP Translation» и не создавать запись в DNAT. Это реализовано при помощи правила «Self NAT Avoiding» в таблице 31 «Local IP».
Из-за того, что локальные порты «br-int» не тегированные и не содержат информации о сети, то с помощью правила, описанного в таблице 30 «Local Egress», в reg6 каждого пакета устанавливается идентификатор сети VLAN и далее используется для идентификации сети.
Чтобы локальному хосту можно было определить MAC адрес хоста с Local IP, необходимо реализовать локальный ARP Responder, который будет перехватывать все ARP запросы со стороны локальных портов и генерировать ответы с MAC адресом локального хоста на такие запросы. Это реализовано правилом «ARP Responder» в таблице 31 «Local IP».
Для того чтобы блокировать рассылаемые внешними хостами gARP анонсы, нужно реализовать так называемый gARP Blocker, который будет блокировать только ARP пакеты, содержащие в себе Local IP чтобы не допускать обновлений ARP адресов в кэшах локальных хостов.
Ну и конечно же, нужно учесть, что Neutron в «br-int» создает правила для предотвращения ARP/MAC Spoofing (таблицы 24 и 25). Причем эта функциональность опциональна и может быть отключена на этапе установки OpenStack. Для того, чтобы Local IP был совместим ней, перенаправление в Local Egress было добавлено в том числе и в таблицу 25.
Как использовать?
Давайте рассмотрим пример, где мы перенаправим трафик с помощью Local IP в локальный прокси. Представим, что на физическом сервере есть две виртуальные машины: хост «A» (MAC: aa:aa:aa:aa:aa:aa, IP: 10.0.0.101), на котором размещен клиент и хост «B» (MAC: bb:bb:bb:bb:bb:bb, IP: 10.0.0.102), на котором размещен прокси, а так же существует внешний хост «E» (MAC: ee:ee:ee:ee:ee:ee, IP: 10.0.0.10) на котором размещен сервис к которому будет обращаться клиент. Без Local IP клиент на хосте «A» обращается к сервису, находящемуся на хосте «E».
Назначив Local IP с адресом 10.0.0.10 на 10.0.0.102 (хост «B») мы направим трафик нашего клиента через прокси сервер, не изменяя конфигурации нашего клиента. Это можно сделать, создав объект Local IP и ассоциацию, выполнив следующие команды:
$ openstack local ip create --network public --name local_proxy --local-ip-address 10.0.0.10
+------------------+--------------------------------------+
| Field | Value |
+------------------+--------------------------------------+
| created_at | 2022-02-07T11:39:55Z |
| description | |
| id | e1918998-84c1-40d3-abd3-950e5bcb4cad |
| ip_mode | translate |
| local_ip_address | 10.0.0.10 |
| local_port_id | 49207b7d-52d5-4b18-b0bc-3c38bb2d08bb |
| name | local_proxy |
| network_id | 8bf58077-2a80-4f23-84b4-80ea1f40835e |
| project_id | 183b5dc87b774aac937f431f4e161e4e |
| revision_number | 0 |
| updated_at | 2022-02-07T11:39:55Z |
+------------------+--------------------------------------+
$ openstack local ip association create e1918998-84c1-40d3-abd3-950e5bcb4cad f8c935ce-24fc-4d55-b537-7f7bec6c2c79
+------------------+--------------------------------------+
| Field | Value |
+------------------+--------------------------------------+
| fixed_ip | 10.0.0.102 |
| fixed_port_id | f8c935ce-24fc-4d55-b537-7f7bec6c2c79 |
| host | uscpepper00644 |
| local_ip_address | 10.0.0.10 |
+------------------+--------------------------------------+
Благодаря NAT, для каждого исходящего пакета с хоста «A» на MAC bb:bb:bb:bb:bb:bb и IP адрес: 10.0.0.10, адрес назначения будет транслироваться в 10.0.0.102 и пакеты будут перенаправлены на локальный порт хоста «B». В пакетах, посланных в ответ с хоста «B» так же благодаря NAT будет транслироваться адрес отправления с 10.0.0.102 на 10.0.0.10 и таким образом пакеты будут доставлены на хост «A».
Если же в локальном ARP кэше хоста «A» не оказалось MAC адреса для 10.0.0.10, то хостом «A» будет отправлен широковещательный ARP запрос. В ответ на этот запрос хост «А» получит MAC адрес: «bb:bb:bb:bb:bb:bb», который соответствует хосту «B». В случае, если бы у нас все работало в обычном режиме, в ответе содержался бы MAC ee:ee:ee:ee:ee:ee хоста «E».
Данный пример получился совсем уж синтетическим, но если представить, что у нас несколько виртуальных машин и несколько гипервизоров, а прокси умеет кешировать запросы, то мы получим снижение нагрузки на сеть и хост «E» в разы.
Степень готовности технологии Local IP
Данная технология будет доступна начиная с релиза Yoga, который запланирован на 30 марта 2022 года. На настоящий момент реализовано:
Neutron API – готово;
OpenStack CLI – готово;
Поддержка VLAN (ML2 драйвер) – готово;
Поддержка VxLAN и GRE (Native драйвер) – готово;
Поддержка в DevStack – готово;
Поддержка режима «passthrough» – не планируется в релизе Yoga (может быть реализован через allowed address pair);
Поддержка IPv6 – в ближайшее время не планируется;
Поддержка в HeatStack – в ближайшее время не планируется.