Документ по поддержке мультитенантности на ноде 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>
This commit is contained in:
1 parent
5e9a035f6e
commit
be34fb0492
2 files changed
+324
-1
No files matched your search
@@ -24,7 +24,9 @@
|
||||
|
||||
Как устроена раскладка слотов — [docs/MAGLEV_SLOT_TABLE.md](docs/MAGLEV_SLOT_TABLE.md)
|
||||
(справка по `hc/maglev.go` для разработки). Что нужно изменить для перевода ноды
|
||||
на OVS-DPDK в ПРОД — [docs/OVS_DPDK_MIGRATION.md](docs/OVS_DPDK_MIGRATION.md).
|
||||
на OVS-DPDK в ПРОД — [docs/OVS_DPDK_MIGRATION.md](docs/OVS_DPDK_MIGRATION.md),
|
||||
на мультитенантную схему (100+ тенантов, изоляция уровня VRF) —
|
||||
[docs/MULTITENANCY.md](docs/MULTITENANCY.md).
|
||||
|
||||
Потоки данных описаны отдельно для каждого вида балансировки:
|
||||
[публичный трафик](docs/PUBLIC_LB_DATAFLOW.md) — клиент из клиентской сети на
|
||||
|
||||
@@ -0,0 +1,321 @@
|
||||
# Мультитенантность на ноде HPNN: перечень необходимых изменений
|
||||
|
||||
**Дата:** 2026-08-17
|
||||
**Зачем:** в ПРОД на одну ноду HPNN (маршрутизация + балансировка) размещается
|
||||
**100 и более тенантов**. Тенант — изолированная сущность, аналог VRF: своё
|
||||
адресное пространство, свои сегменты, свои листенеры и пулы. Адреса разных
|
||||
тенантов **могут совпадать**.
|
||||
**Основание:** фактическое состояние стенда на 2026-08-17 (шаг 3).
|
||||
**Смежные документы:** [OVS_DPDK_MIGRATION.md](OVS_DPDK_MIGRATION.md),
|
||||
[PRIVATE_LB_DATAFLOW.md](PRIVATE_LB_DATAFLOW.md),
|
||||
[MAGLEV_SLOT_TABLE.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+ тенантов
|
||||
помещаются с запасом.
|
||||
|
||||
Дополнительно необходимы **лимиты соединений на зону**, чтобы один тенант не
|
||||
исчерпал таблицу соединений всей ноды:
|
||||
|
||||
```bash
|
||||
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 тенантов — это предел или проектная точка, от
|
||||
которой считать запас.
|
||||
Reference in new issue
Block a user