Files
ayurishchevandClaude Opus 5 be34fb0492 Документ по поддержке мультитенантности на ноде HPNN
Анализ текущего состояния и перечень изменений для размещения 100+
тенантов на одной ноде с изоляцией уровня VRF.

Ключевая проблема — пересекающиеся адресные пространства тенантов:
ни один матч в пайплайне не может опираться на IP или MAC без
идентификатора тенанта. Предложена схема с tenant_id в reg3 и
ct-зоной из регистра (ct(zone=NXM_NX_REG3[0..15]) — синтаксис
проверен на стенде).

Отдельно оценён масштаб: таблица слотов доминирует (100 пулов по 1024
слота дают 102400 правил и ~8.8 с полной перезаливки при замеренных
88 мс на 1024 правила), поэтому SLOTS должен стать параметром пула,
а apply.sh — перейти на инкрементальные бандлы.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 21:21:29 +03:00

23 KiB
Raw Permalink Blame History

Мультитенантность на ноде HPNN: перечень необходимых изменений

Дата: 2026-08-17 Зачем: в ПРОД на одну ноду HPNN (маршрутизация + балансировка) размещается 100 и более тенантов. Тенант — изолированная сущность, аналог VRF: своё адресное пространство, свои сегменты, свои листенеры и пулы. Адреса разных тенантов могут совпадать. Основание: фактическое состояние стенда на 2026-08-17 (шаг 3). Смежные документы: OVS_DPDK_MIGRATION.md, PRIVATE_LB_DATAFLOW.md, MAGLEV_SLOT_TABLE.md.

1. Исходное состояние: что жёстко привязано к одному тенанту

Место Как сейчас Почему не масштабируется
lb/topology.env плоский список адресов, MAC и ofport нет понятия тенанта; на 100 тенантов файл нечитаем и неуправляем
Таблица 20 (маршрутизация) три префикса 10.20/24, 10.30/24, 192.168.5/24 без признака владельца при совпадении адресов у двух тенантов пакет уйдёт «не туда»
Таблица 21 (adjacency) матч только по nw_dst то же
Таблица 25 (FDB) матч reg0 + eth_dst, где reg0 — номер сегмента (1/2/3) всего три значения; сегменты разных тенантов не различаются
ct единственная зона CT_ZONE=1 (13 правил с zone=1) записи тенантов с одинаковыми адресами столкнутся в одной таблице соединений
Таблица 11 (слоты) один пул, POOL_ID=1, 1024 правила один пул на ноду
hc/ (демон) конфиг с одним pool_id, одним slots, одним списком members нет модели пулов и тенантов
Health-пробы два адреса в ядре узла (10.20.0.253, 10.30.0.253) при совпадающих подсетях тенантов один стек ядра не может держать одинаковые адреса
lb/apply.sh replace-flows — перезаливка всего пайплайна правка одного тенанта затронет всех
ARP/ICMP-респондеры правило на адрес без признака тенанта VIP разных тенантов могут совпадать

Всего в мосту сейчас 1141 правило (105 пайплайна + 1024 слота + записи learn).

2. Ключевая проблема — пересечение адресных пространств

Два тенанта с 10.20.0.0/24 должны работать независимо. Отсюда правило, которому обязан удовлетворять каждый матч в пайплайне:

Ни одно правило не имеет права матчить IP или MAC без идентификатора тенанта.

Это касается таблиц 5, 6, 10, 12, 20, 21, 24, 25 и всех записей, создаваемых действием learn.

3. Целевая модель датапаса

3.1. Идентификатор тенанта

reg3[0..15] — tenant_id (16 бит, до 65535 тенантов). Проставляется в таблице 0 по входному порту вместе с номером сегмента:

table=0,priority=200,in_port=<порт ВМ>
    actions=load:<tenant_id>->NXM_NX_REG3[0..15],
            load:<segment_id>->NXM_NX_REG0[],
            <learn FDB>, goto_table:1

Проверено на стенде: metadata (64 бита) и write_metadata тоже принимаются — их стоит оставить под транспортный уровень (VNI/VLAN), а tenant_id держать именно в регистре, потому что из регистра его берёт ct (см. 3.3).

Распределение регистров после изменения:

Регистр Назначение
reg0 сегмент внутри тенанта
reg1 номер слота (из multipath)
reg2 идентификатор члена пула
reg3[0..15] tenant_id
reg3[16..31] / reg4 pool_id
metadata резерв под VNI/VLAN и транспорт

3.2. Что меняется в каждой таблице

Таблица Изменение
0 порт → tenant_id + segment_id; learn FDB получает reg3 в матч
1 классификация по MAC шлюза дополняется reg3: у разных тенантов MAC шлюзов могут совпадать
5 ARP-респондер: матч reg3 + arp_tpa; рассылка — в порты сегмента этого тенанта
6 ICMP-респондер: матч reg3 + nw_dst
10 листенер: матч reg3 + nw_dst + tp_dst; multipath получает basis = pool_id (уже так)
11 одна таблица на все пулы: матч reg4=pool_id + reg1=slot
12 матч reg3 + reg2; DNAT в адрес члена этого тенанта
15/16, 20/21 матч reg3 + nw_dst — LPM внутри VRF тенанта
24 матч reg3 + nw_src + tp_src
25 FDB: матч reg3 + reg0 + eth_dst; рассылка — группой type=all на сегмент
все priority=0 actions=drop сохраняется как есть — это и есть изоляция по умолчанию

3.3. Conntrack: зона на тенанта

Единственная зона CT_ZONE=1 не годится: при совпадающих адресах записи столкнутся. Решение — зона из регистра, синтаксис проверен на стенде (OVS 3.1 принимает):

actions=ct(commit,zone=NXM_NX_REG3[0..15],nat(dst=<member>:<port>),table=25)
actions=ct(zone=NXM_NX_REG3[0..15],nat,table=25)

Зона — 16 бит, то есть tenant_id ложится в неё один к одному, и 100+ тенантов помещаются с запасом.

Дополнительно необходимы лимиты соединений на зону, чтобы один тенант не исчерпал таблицу соединений всей ноды:

ovs-appctl dpctl/ct-set-limits zone=<tenant_id>,limit=<N>
ovs-appctl dpctl/ct-get-limits

3.4. Рассылка и группы

Список портов сегмента сейчас зашит в правило рассылки. При 100 тенантах и динамическом составе ВМ его придётся перегенерировать на каждое подключение. Замена — OpenFlow-группа type=all на сегмент тенанта: правило таблицы 25 ссылается на группу, а состав портов меняется независимо от правил.

4. Балансировка: пулы и таблица слотов

Сейчас пул один: POOL_ID=1, SLOTS=1024, таблица 11 целиком принадлежит ему.

Целевая схема — одна таблица слотов на все пулы, матч по паре (pool_id, slot):

table=11,priority=100,ip,reg4=<pool_id>,reg1=<slot>
    actions=load:<member_id>->NXM_NX_REG2[],goto_table:12

Отдельная таблица на пул не годится: в OpenFlow всего 255 таблиц.

Оценка числа правил. Замер на стенде: 1024 правила заливаются бандлом за 88 мс.

Пулов на ноде Слотов на пул Правил в таблице 11 Время полной перезаливки
1 (сейчас) 1024 1 024 0,09 с
100 1024 102 400 ≈ 8,8 с
100 256 25 600 ≈ 2,2 с
200 256 51 200 ≈ 4,4 с

Отсюда два обязательных вывода:

  1. SLOTS должен стать параметром пула, а не ноды. Для пула из 4–8 членов 1024 слота избыточны: 256 дают гранулярность веса 0,4 % и вчетверо меньше правил.
  2. Полная перезаливка недопустима. apply.sh сейчас делает replace-flows всего пайплайна, а демон следом перезаливает слоты. При 100 тенантах это моргание всей ноды на секунды. Нужны инкрементальные бандлы: изменение состава пула затрагивает только правила своего pool_id (88 мс), изменение тенанта — только его правила.

5. Health-check: самое сложное место

Демон hcd рассчитан на один пул и отправляет пробы из сетевого стека ядра (net.Dialer.LocalAddr на адресе internal-порта). При мультитенантности возникают три независимые задачи.

5.1. Модель. Конфиг демона переходит от одного пула к списку: tenant → pools[] → members[], у каждого пула — свои pool_id, slots, health_monitor, свой дайджест раскладки и свой источник проб. API /status, /metrics, /drain, /enable получают фильтр по тенанту и пулу.

5.2. Источник проб при совпадающих адресах. Если у двух тенантов подсеть 10.20.0.0/24, узел не может держать два одинаковых адреса 10.20.0.253 в одном сетевом стеке. Варианты, которые надо сравнить спайком:

Вариант Суть Цена
Linux VRF (l3mdev) internal-порт тенанта помещается в vrf-<tenant>, пробер привязывает сокет к VRF (SO_BINDTODEVICE) 100+ VRF-устройств и таблиц маршрутизации на ноде; штатный механизм ядра
netns на тенанта internal-порт в отдельном namespace, пробер — отдельный процесс или горутина с setns полная изоляция, но 100+ namespace и сложнее эксплуатация
Пробер с собственным стеком проба формируется и разбирается самим демоном (packet-out / AF_XDP / DPDK) снимает зависимость от ядра и от tap на DPDK-ноде, но это отдельная разработка

На ноде с OVS-DPDK internal-порт — это tap, то есть медленный путь; 100+ tap плюс 100+ VRF требуют отдельной оценки (см. OVS_DPDK_MIGRATION.md, раздел 3.5).

5.3. Масштаб проб. 100 тенантов × 10 членов при интервале 2 с = 500 проб/с установленных TCP-соединений. Требуется: пул горутин с ограничением параллелизма, ограничение частоты, устойчивость к «шторму» переходов состояния (сейчас при любом изменении вызывается перезаливка — при 100 пулах нужен батчинг и дебаунс).

6. Control plane

Статические topology.env и hc-config.sh на 100 тенантов неприменимы. Нужна декларативная модель — та самая, что заложена в дизайне v1 (§6), расширенная сущностью тенанта:

Tenant (VRF)
 ├── Segment      (сегменты тенанта, порты ВМ, адрес шлюза, VIP-ы)
 ├── Route        (префиксы VRF: LPM таблицы 20)
 └── Load_Balancer → Listener → Pool → Member
                                  └── Health_Monitor

Требования к агенту-рендереру:

  • инкрементальность: изменение одного тенанта не перегенерирует пайплайн целиком (см. 4);
  • идемпотентность: повторное применение не меняет датапас;
  • атомарность на тенанта: бандл на тенанта, а не на ноду;
  • валидации §8.4 дизайна v1 в границах тенанта: уникальность (vip, protocol, port) — теперь внутри тенанта, а не глобально; уникальность обратного пути (member_address, member_port, proto) — тоже внутри тенанта;
  • распределение идентификаторов: tenant_id, pool_id, segment_id, ofport и номера ct-зон выдаёт control plane, а не человек.

7. Оценка масштаба на 100 тенантов

Ориентир при 10 ВМ и 2 листенерах на тенанта, 8 членов в пуле, 256 слотов:

Таблица Правил
0 — сегментация и обучение ~2 000 (по два на порт ВМ)
5/6 — ARP и ICMP-респондеры ~800
10 — листенеры ~200
11 — слоты 25 600
12 — DNAT ~800
20/21 — маршрутизация и adjacency ~1 500 + записи learn
24 — обратная трансляция ~800
25 — FDB ~1 000 (learn)
Итого ≈ 33 000

При 1024 слотах на пул — около 110 000, то есть таблица слотов доминирует и определяет и память, и время применения.

Что ещё растёт линейно по числу тенантов: число портов моста (100 × 10 = 1000 vhost-user), число ct-зон, число VRF/netns для проб, число целей мониторинга.

8. Изоляция: что обязательно проверять

Изоляция — не побочный эффект, а требование, которое нужно доказывать тестом на каждой сборке:

  1. Два тенанта с одинаковой подсетью 10.20.0.0/24 и одинаковыми адресами ВМ обслуживаются независимо; трафик одного не появляется на портах другого.
  2. Совпадающие VIP у разных тенантов работают одновременно.
  3. ct-записи лежат в разных зонах: dpctl/dump-conntrack с фильтром по зоне.
  4. Записи learn (FDB и adjacency) одного тенанта не применяются к трафику другого.
  5. При отказе всех членов пула тенанта A трафик тенанта B не затронут (fail-close локален).
  6. Исчерпание лимита соединений тенантом A не влияет на B (ct-set-limits).
  7. Счётчики правил тенанта B не растут во время нагрузки на A.

9. Ресурсные лимиты и справедливость

При 100 тенантах на одной ноде нужен механизм, не позволяющий одному тенанту занять всю ноду:

  • новые соединения: OpenFlow-метр на листенер (new_conn_rate из §4.6 дизайна v1) — особенно важно, потому что каждое новое соединение даёт upcall (см. OVS_DPDK_MIGRATION.md, раздел 6);
  • соединения в ct: dpctl/ct-set-limits на зону тенанта;
  • полоса: QoS на порт ВМ или на группу портов тенанта;
  • правила: верхняя граница числа членов и слотов на тенанта — иначе один тенант раздует таблицу 11.

10. Наблюдаемость

  • Метрики hpnn_* получают лейблы tenant и pool; сейчас есть только member.
  • lbctl health|slots|fdb|conns получают фильтр по тенанту — иначе вывод на 100 тенантов нечитаем.
  • Дайджест раскладки считается на пул, а не на ноду.
  • Отдельная метрика на тенанта: число ct-записей, upcall rate, отказы проб, срабатывания лимитов.

11. Изменения по файлам

Файл Действие
lb/topology.env заменяется декларативной моделью тенантов (OVSDB или конфиг-файл на тенанта)
lb/pipeline.sh генератор перестраивается: цикл по тенантам и их сегментам, reg3 во всех матчах, ct(zone=NXM_NX_REG3[0..15]), группы type=all для рассылки
lb/apply.sh инкрементальные бандлы на тенанта и на пул вместо replace-flows всей ноды
hc/main.go модель tenant → pools → members, дайджест на пул, батчинг перезаливки, ограничение параллелизма проб, API с фильтрами
hc/maglev.go без изменений — алгоритм уже параметризован poolID и slots
lb/hc-config.sh заменяется генерацией из модели control plane
lb/lbctl.sh фильтр по тенанту во всех командах
scripts/verify.sh добавляются проверки изоляции (раздел 8) на паре тенантов с пересекающимися адресами

12. План внедрения

Этап Содержание Критерий
1 Ввести tenant_id в reg3 и ct(zone=NXM_NX_REG3[0..15]) на текущем единственном тенанте все 86 проверок проходят, поведение не изменилось
2 Генератор пайплайна по списку тенантов; два тенанта с пересекающимися адресами проверки изоляции из раздела 8 зелёные
3 SLOTS как параметр пула; таблица 11 с матчем (pool_id, slot) раскладки пулов независимы, дайджест на пул
4 Инкрементальные бандлы на тенанта и пул изменение одного тенанта не трогает счётчики других
5 Многопуловый hcd и источник проб на тенанта (VRF либо netns) пробы идут в контексте своего VRF при совпадающих адресах
6 Лимиты (ct-set-limits, метры) и наблюдаемость с лейблами тенант не может исчерпать ресурсы ноды
7 Масштабные испытания: 100 тенантов время применения, память, PPS и CPS в целевых границах

Порядок важен: этап 1 — чистый рефакторинг без изменения поведения, он страхует всё остальное существующим набором проверок.

13. Спайки до начала работ

  • S-MT-1 — изоляция при пересечении адресов. Два тенанта с одинаковой подсетью и одинаковыми адресами ВМ. Блокирующий: на нём стоит вся модель.
  • S-MT-2 — масштаб таблицы слотов. 100 пулов: время применения, память ovs-vswitchd, влияние на PPS.
  • S-MT-3 — источник health-проб. VRF против netns при совпадающих адресах, в том числе на netdev-датапасе.
  • S-MT-4 — ct-зоны под нагрузкой. Поведение при исчерпании лимита зоны, влияние на соседние зоны.

14. Открытые вопросы

  1. Сколько ВМ и сегментов на тенанта в среднем и в пределе — от этого зависят число портов моста и размер групп рассылки.
  2. Могут ли совпадать публичные VIP разных тенантов, или публичный пул адресов глобально уникален.
  3. Как сегменты тенантов приходят с фабрики: отдельными портами, VLAN-транком или туннелями (VXLAN/Geneve) — от этого зависит, чем проставляется tenant_id в таблице 0.
  4. Нужен ли межтенантный трафик (route leaking между VRF) и по каким правилам.
  5. Кто владеет IPAM и распределением tenant_id, pool_id, ct-зон.
  6. Требуется ли гарантия полосы на тенанта (QoS) или достаточно защиты от исчерпания ресурсов.
  7. Какова целевая плотность: 100 тенантов — это предел или проектная точка, от которой считать запас.