Документ по нескольким листенерам с независимыми пулами бэкендов
Анализ текущего состояния и перечень изменений для классического режима облачного балансировщика: несколько листенеров (публичных и/или приватных), каждый со своим независимым пулом. Подтверждено на стенде: сейчас все листенеры делят один POOL_ID и одну таблицу слотов (reg1 -> reg2), member_id глобален. multipath для разных листенеров с одним POOL_ID даёт одинаковый номер слота для совпадающего 5-tuple — пулы физически не разделены. Предложена схема reg4 = pool_id: таблица слотов становится общей для всех пулов с матчем (reg4, reg1), FlowBundle.flow delete фильтруется по reg4 для инкрементального обновления одного пула. Замер: 1024 правила заливаются бандлом за 86 мс. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
be34fb0492
commit
69b9b56ccd
2 files changed
+321
-1
No files matched your search
@@ -26,7 +26,8 @@
|
||||
(справка по `hc/maglev.go` для разработки). Что нужно изменить для перевода ноды
|
||||
на OVS-DPDK в ПРОД — [docs/OVS_DPDK_MIGRATION.md](docs/OVS_DPDK_MIGRATION.md),
|
||||
на мультитенантную схему (100+ тенантов, изоляция уровня VRF) —
|
||||
[docs/MULTITENANCY.md](docs/MULTITENANCY.md).
|
||||
[docs/MULTITENANCY.md](docs/MULTITENANCY.md), на несколько листенеров с
|
||||
независимыми пулами бэкендов — [docs/MULTI_LISTENER_POOLS.md](docs/MULTI_LISTENER_POOLS.md).
|
||||
|
||||
Потоки данных описаны отдельно для каждого вида балансировки:
|
||||
[публичный трафик](docs/PUBLIC_LB_DATAFLOW.md) — клиент из клиентской сети на
|
||||
|
||||
@@ -0,0 +1,319 @@
|
||||
# Несколько листенеров с уникальными пулами: перечень необходимых изменений
|
||||
|
||||
**Дата:** 2026-08-17
|
||||
**Зачем:** классический режим облачного балансировщика — на ноде HPNN
|
||||
одновременно работают несколько листенеров (публичных и/или приватных), и
|
||||
**каждый обслуживает свой независимый пул бэкендов**. Сейчас это не так: все
|
||||
листенеры делят один общий пул.
|
||||
**Основание:** фактическое состояние стенда на 2026-08-17 (шаг 3).
|
||||
**Смежные документы:** [PUBLIC_LB_DATAFLOW.md](PUBLIC_LB_DATAFLOW.md),
|
||||
[PRIVATE_LB_DATAFLOW.md](PRIVATE_LB_DATAFLOW.md),
|
||||
[MAGLEV_SLOT_TABLE.md](MAGLEV_SLOT_TABLE.md),
|
||||
[MULTITENANCY.md](MULTITENANCY.md) — этот документ описывает многопуловость
|
||||
внутри одного тенанта; при мультитенантности оба изменения комбинируются.
|
||||
|
||||
## 1. Исходное состояние — проверено на стенде
|
||||
|
||||
| Место | Как сейчас | Подтверждение |
|
||||
|---|---|---|
|
||||
| `lb/topology.env` | один `POOL_ID=1`, один `SLOTS=1024`, один `VIP`, `PRIV_VIP_PORT` общий на оба приватных листенера | `grep POOL_ID` даёт одно значение |
|
||||
| `lb/hc-config.sh` | один блок `"members": [...]` на весь конфиг демона | нет ни `listener`, ни привязки члена к листенеру |
|
||||
| `hc/main.go`, `Config` | один `PoolID uint32`, один `[]*Member` | структура `Config` не содержит списка пулов |
|
||||
| `hc/maglev.go`, `BuildSlotTable` | принимает **один** `poolID` и **один** список членов | сигнатура `func BuildSlotTable(poolID uint32, members []*Member, slots int) []int` |
|
||||
| Таблица 10 (`lb/pipeline.sh`) | три листенера (`VIP:80`, `P2_VIP:80`, `P3_VIP:80`), у каждого свой `multipath(...,POOL_ID,...)` — но `POOL_ID` **один и тот же** | строки с `multipath(symmetric_l4,$POOL_ID,...)` повторяют одну переменную |
|
||||
| Таблица 11 (слоты) | один `reg1` (0–1023) → `reg2` (1–4); `reg1` не содержит признака пула | 1024 правила `table=11,priority=100,ip,reg1=<слот>` |
|
||||
| Таблица 12 (DNAT) | матч только по `reg2` (у публичного листенера) или `reg0+reg2` (у приватного); `reg2` — глобальный ID члена | `table=12,priority=100,ip,reg2=0x1 actions=...` |
|
||||
| `member_id` | 1–4, зашиты в `topology.env` (`BE1_ID=0x1` … `BE4_ID=0x4`) | одно адресное пространство ID на всю ноду |
|
||||
|
||||
**Вывод:** три листенера сейчас формально работают, но `multipath` считает слот
|
||||
от одного и того же `POOL_ID` для всех — то есть при одинаковом 5-tuple разные
|
||||
листенеры выбрали бы **один и тот же номер слота**, и любой из них ссылается на
|
||||
одну и ту же таблицу 11 с одним составом членов. Разделить пулы физически
|
||||
невозможно без изменения модели идентификаторов.
|
||||
|
||||
## 2. Целевая модель
|
||||
|
||||
```
|
||||
Listener (VIP, порт, протокол, сегмент)
|
||||
└── Pool (pool_id, slots, affinity, health_monitor)
|
||||
└── Member (id, address, port, weight)
|
||||
```
|
||||
|
||||
Один листенер → один пул. Пул может обслуживать несколько листенеров (типовой
|
||||
кейс — HTTP и HTTPS на общий бэкенд), но никогда наоборот. Правило соответствует
|
||||
§6 дизайна v1: `Listener.pool` — внешний ключ на `Pool`.
|
||||
|
||||
### 2.1. Идентификаторы
|
||||
|
||||
Два независимых пространства номеров, а не один плоский `member_id`:
|
||||
|
||||
| Что | Ширина | Где хранится | Обоснование |
|
||||
|---|---|---|---|
|
||||
| `pool_id` | не менее 16 бит | `reg4` (новый) | сейчас 32-битный, но используется только как seed хэша; для матчей в таблице 11 нужно явное поле |
|
||||
| `member_id` | 8–16 бит, **уникален внутри пула**, не глобально | `reg2` (как сейчас) | у разных пулов id членов могут и должны повторяться (1, 2, 3…) |
|
||||
|
||||
`reg2` без `reg4` в таблице 11 неоднозначен: `member_id=1` у пула A и
|
||||
`member_id=1` у пула B — разные бэкенды. Матч обязан включать оба поля.
|
||||
|
||||
### 2.2. Таблица 11 — главное изменение
|
||||
|
||||
Сейчас: `reg1 (слот) → reg2 (член)`, один пул на всю таблицу.
|
||||
|
||||
Целевое: **одна таблица слотов на все пулы**, отдельная таблица на пул не
|
||||
подходит — в OpenFlow не более 255 таблиц, а пулов на ноде десятки.
|
||||
|
||||
```
|
||||
table=11,priority=100,ip,reg4=<pool_id>,reg1=<slot>
|
||||
actions=load:<member_id>->NXM_NX_REG2[],goto_table:12
|
||||
```
|
||||
|
||||
Требование к слоту: значения `reg1` **переиспользуются между пулами** (у пула A
|
||||
слот 5 и у пула B слот 5 существуют одновременно, различает их `reg4`), поэтому
|
||||
после `multipath` перед `goto_table:11` обязательно `load:<pool_id>-
|
||||
>NXM_NX_REG4[]`.
|
||||
|
||||
### 2.3. Таблица 10 — листенер помечает свой пул
|
||||
|
||||
```
|
||||
table=10,priority=200,tcp,nw_dst=<VIP>,tp_dst=<порт>
|
||||
actions=load:<pool_id>->NXM_NX_REG4[],
|
||||
multipath(symmetric_l4,<pool_id>,modulo_n,<slots>,0,NXM_NX_REG1[]),
|
||||
goto_table:11
|
||||
```
|
||||
|
||||
`pool_id` продолжает быть `basis` хэша (как сейчас) — это даёт независимые
|
||||
раскладки для разных пулов на одном 5-tuple, что уже проверено в шаге 2.
|
||||
Дополнительно он же явно кладётся в `reg4` для матча таблицы 11.
|
||||
|
||||
### 2.4. Таблица 12 — DNAT с учётом пула
|
||||
|
||||
```
|
||||
table=12,priority=100,ip,reg4=<pool_id>,reg2=<member_id>
|
||||
actions=ct(commit,zone=<zone>,nat(dst=<addr>:<port>),table=20)
|
||||
```
|
||||
|
||||
Для приватных листенеров (коммутируемый путь, шаг 3) матч дополнительно
|
||||
включает `reg0` (сегмент), как сейчас — только добавляется `reg4`.
|
||||
|
||||
### 2.5. `SLOTS` и `affinity` — параметры пула, не ноды
|
||||
|
||||
Сейчас `SLOTS=1024` и алгоритм хэша (`multipath(symmetric_l4,...)`, единая
|
||||
строка в `lb/pipeline.sh`) — общие для всей ноды. При независимых пулах это
|
||||
обязано стать полем `Pool`:
|
||||
|
||||
- `slots` — гранулярность весов; для пула из 3–5 членов достаточно 256 (см.
|
||||
расчёт стоимости в разделе 4);
|
||||
`affinity` (`none`/`source_ip`) — выбор хэш-полей `multipath`, сейчас общий
|
||||
`symmetric_l4` для всех листенеров, а нужен независимый на пул (§6 дизайна
|
||||
v1 уже переносит `affinity` на пул).
|
||||
|
||||
## 3. Изменения в `hc/` (демон)
|
||||
|
||||
### 3.1. Модель конфигурации
|
||||
|
||||
```go
|
||||
type Config struct {
|
||||
Bridge, OFVersion, API, ApplyScript string
|
||||
SlotTable, DNATTable int
|
||||
Pools []*Pool // было: PoolID + Members
|
||||
}
|
||||
|
||||
type Pool struct {
|
||||
ID uint32
|
||||
Name string
|
||||
Slots int
|
||||
Affinity string // "none" | "source_ip"
|
||||
Probe, HTTPPath, Interval, Timeout string
|
||||
Rise, Fall int
|
||||
Members []*Member // как сейчас, но member.ID уникален внутри пула
|
||||
}
|
||||
```
|
||||
|
||||
`Member.ID` перестаёт быть глобальным — валидация уникальности переносится
|
||||
внутрь `Pool`.
|
||||
|
||||
### 3.2. `hc/maglev.go` — сигнатура меняется минимально
|
||||
|
||||
`BuildSlotTable` уже параметризован `poolID` и `slots` — переиспользуется как
|
||||
есть, вызывается **в цикле по пулам**:
|
||||
|
||||
```go
|
||||
for _, pool := range cfg.Pools {
|
||||
table := BuildSlotTable(pool.ID, pool.Members, pool.Slots)
|
||||
digest := Digest(table)
|
||||
// сравнение с pool.lastDigest, не с глобальным digest
|
||||
}
|
||||
```
|
||||
|
||||
`FlowBundle` дополняется полем `pool_id`:
|
||||
|
||||
```go
|
||||
func FlowBundle(poolID uint32, table []int, slotTable, nextTable int) string {
|
||||
// flow delete table=<slotTable>,reg4=<poolID>
|
||||
// flow add ...,reg4=<poolID>,reg1=<slot> actions=load:<id>->reg2,goto_table:<nextTable>
|
||||
}
|
||||
```
|
||||
|
||||
Ключевое: `flow delete` теперь фильтруется **по `reg4`**, а не сносит всю
|
||||
таблицу — иначе пересчёт одного пула стирает раскладку остальных на время
|
||||
применения бандла. Это уже требование инкрементальности, отдельно
|
||||
зафиксированное в `MULTITENANCY.md`.
|
||||
|
||||
### 3.3. Health-check и API
|
||||
|
||||
- Пробы каждого пула идут независимо — `hcd` уже пробует членов параллельно
|
||||
(`sync.WaitGroup` в `main.go`), достаточно развернуть цикл на пулы.
|
||||
- Дайджест раскладки, счётчики `probes`/`failures`, применение бандла —
|
||||
**на пул**, не на ноду. Изменение состояния в пуле A не должно вызывать
|
||||
перезаливку пула B.
|
||||
- HTTP API (`/status`, `/status.txt`, `/metrics`, `/drain`, `/enable`)
|
||||
получает параметр `pool` (или `pool+member`, если имена членов совпадают
|
||||
между пулами — см. 3.4).
|
||||
- `/drain?member=be1` неоднозначен при нескольких пулах с членом `be1` —
|
||||
обязателен `pool`: `/drain?pool=<name>&member=be1`.
|
||||
|
||||
### 3.4. Имена членов не обязаны быть уникальны глобально
|
||||
|
||||
Сейчас `lbctl slots`/`health` матчат член по имени (`be1`…`be4`) — при
|
||||
нескольких пулах с непересекающимся списком имён это временно работает, но
|
||||
это случайность конфигурации, а не гарантия. Уникальность имени должна
|
||||
проверяться **внутри пула**, вывод `lbctl`/`/status` — группироваться по пулу.
|
||||
|
||||
## 4. Стоимость: сколько правил и как быстро применяются
|
||||
|
||||
Замер на стенде: 1024 правила заливаются бандлом за **86 мс** (`ovs-ofctl
|
||||
bundle`).
|
||||
|
||||
| Листенеров | Членов на пул | Слотов на пул | Правил в таблице 11 | Оценка времени полной заливки |
|
||||
|---|---|---|---|---|
|
||||
| 1 (сейчас) | 4 | 1024 | 1 024 | 0,09 с |
|
||||
| 5 | 4 | 256 | 1 280 | ≈ 0,11 с |
|
||||
| 20 | 4 | 256 | 5 120 | ≈ 0,44 с |
|
||||
| 20 | 8 | 512 | 10 240 | ≈ 0,88 с |
|
||||
|
||||
При десятках листенеров на ноде — приемлемо **при условии**, что заливка
|
||||
инкрементальна (раздел 3.2, `flow delete` по `reg4`). Полная перезаливка
|
||||
(`replace-flows`), как делает сейчас `apply.sh` для всего пайплайна, при 20+
|
||||
пулах даёт заметную паузу и должна остаться только для применения самого
|
||||
пайплайна (`pipeline.sh`), а не для обновления состава пула.
|
||||
|
||||
## 5. `lb/apply.sh` и `lb/lbctl.sh`
|
||||
|
||||
- `apply.sh` без изменений в части пайплайна; вызов `/reapply` у демона должен
|
||||
адресоваться **всем пулам** после `replace-flows` (таблица 11 стирается
|
||||
целиком вместе с остальным пайплайном) — здесь полная перезаливка неизбежна,
|
||||
это разовая операция при смене конфигурации листенеров, а не при отказе
|
||||
бэкенда.
|
||||
- `lb/lbctl.sh`: команды `health`, `slots`, `drain`, `enable`, `trace`, `fdb`
|
||||
получают обязательный или контекстный параметр `pool`/`listener`. `trace`
|
||||
уже принимает `SEG` (шаг 3) — по аналогии добавляется выбор VIP и порта не
|
||||
по жёстко зашитым `$VIP`/`$P2_VIP`, а по имени листенера из конфигурации.
|
||||
|
||||
## 6. `lb/topology.env` → декларативная модель листенеров
|
||||
|
||||
Плоский файл с одним `VIP`/`POOL_ID` не описывает список листенеров. Минимальный
|
||||
шаг без полноценного OVSDB (§6 дизайна v1) — секция листенеров в том же файле
|
||||
или отдельном:
|
||||
|
||||
```bash
|
||||
# listeners.env — один блок на листенер
|
||||
LISTENER_1_NAME=public-http
|
||||
LISTENER_1_SEGMENT=pub
|
||||
LISTENER_1_VIP=192.168.5.21
|
||||
LISTENER_1_PORT=80
|
||||
LISTENER_1_POOL=pool-a
|
||||
|
||||
LISTENER_2_NAME=priv2-http
|
||||
LISTENER_2_SEGMENT=p2
|
||||
LISTENER_2_VIP=10.20.0.100
|
||||
LISTENER_2_PORT=80
|
||||
LISTENER_2_POOL=pool-b
|
||||
|
||||
POOL_A_ID=1
|
||||
POOL_A_SLOTS=1024
|
||||
POOL_A_MEMBERS="be1:10.20.0.2:8080:1 be2:10.30.0.2:8080:1"
|
||||
|
||||
POOL_B_ID=2
|
||||
POOL_B_SLOTS=256
|
||||
POOL_B_MEMBERS="be3:10.20.0.3:8080:1 be4:10.30.0.3:8080:1"
|
||||
```
|
||||
|
||||
`lb/pipeline.sh` перестаёт быть плоским генератором с фиксированным числом
|
||||
листенеров (сейчас — ровно три, с условным включением приватных через
|
||||
`PRIV_LISTENERS`) и переходит на **цикл по списку листенеров**, каждый со своим
|
||||
пулом. Это прямой шаг к модели `Load_Balancer → Listener → Pool → Member` из
|
||||
`MULTITENANCY.md` — там же описан переход к OVSDB и control plane, здесь
|
||||
достаточно локального конфига.
|
||||
|
||||
## 7. Валидации (переносятся из §8.4 дизайна v1, применяются к новой модели)
|
||||
|
||||
| Правило | Уровень | Комментарий |
|
||||
|---|---|---|
|
||||
| `(vip, protocol, port)` уникальна | среди листенеров ноды | сейчас неявно верно (три разных VIP), но не проверяется |
|
||||
| `(member_address, member_port, proto)` не повторяется в **разных** пулах | глобально на ноде | иначе `ct`-обратная трансляция неоднозначна — один и тот же адрес:порт не может принадлежать двум пулам |
|
||||
| `member_id` уникален внутри пула, не глобально | на пул | пересматривается относительно текущей схемы |
|
||||
| `pool_id` уникален на ноде | глобально | входит в `reg4`, basis хэша |
|
||||
| один и тот же член может входить в несколько пулов **разных** листенеров | разрешено | классический кейс — общий бэкенд для HTTP и HTTPS листенеров с разными портами приёма |
|
||||
|
||||
Второе правило — самое важное практическое ограничение: нельзя создать два
|
||||
пула, у которых есть член с одинаковым `address:port`. Сейчас проверки такой
|
||||
нет вовсе.
|
||||
|
||||
## 8. Изменения по файлам — сводка
|
||||
|
||||
| Файл | Действие |
|
||||
|---|---|
|
||||
| `lb/pipeline.sh` | генератор переходит на цикл по листенерам/пулам; `reg4` во всех матчах таблиц 10/11/12; `flow delete` в бандле слотов — по `reg4` |
|
||||
| `lb/topology.env` | секция листенеров и пулов вместо одного `VIP`/`POOL_ID`/`SLOTS` (раздел 6) |
|
||||
| `lb/hc-config.sh` | генерирует список `Pools[]` вместо одного `members` |
|
||||
| `hc/main.go` | `Config.Pools []*Pool`; API получает параметр `pool`; применение и дайджест — на пул |
|
||||
| `hc/maglev.go` | `FlowBundle` получает `poolID`, `flow delete` фильтруется по `reg4` |
|
||||
| `lb/lbctl.sh` | `health`/`slots`/`drain`/`enable`/`trace`/`fdb` — параметр `pool`/`listener` |
|
||||
| `scripts/verify.sh` | новая секция: два листенера с непересекающимися пулами, отказ члена одного не влияет на другой |
|
||||
| `docs/MANUAL_TEST_PLAN.md` | блок ручной проверки нескольких пулов |
|
||||
|
||||
## 9. План внедрения
|
||||
|
||||
| Этап | Содержание | Критерий |
|
||||
|---|---|---|
|
||||
| 1 | Ввести `reg4` (pool_id) во все матчи, оставаясь на **одном** пуле | все 86 текущих проверок проходят без изменения поведения |
|
||||
| 2 | Второй независимый пул на существующем публичном листенере (тестовый VIP) | раскладки двух пулов различны, `flow delete` в бандле не трогает чужой `reg4` |
|
||||
| 3 | `SLOTS`/`affinity` как поля пула, а не ноды | пул A на 1024 слота и пул B на 256 работают одновременно |
|
||||
| 4 | Многопуловый `hcd`: `Config.Pools`, API с фильтром | `/status?pool=b` не показывает членов пула A |
|
||||
| 5 | Декларативный список листенеров в `topology.env` | добавление четвёртого листенера — правка одного файла, без изменения `pipeline.sh` |
|
||||
| 6 | Валидации §7 в `apply.sh` или отдельном линтере конфигурации | конфликт `member_address:port` между пулами отклоняется до заливки |
|
||||
| 7 | Нагрузочная проверка: 10–20 листенеров, независимые пулы | время применения и корректность раскладок в пределах раздела 4 |
|
||||
|
||||
Порядок аналогичен `MULTITENANCY.md`: этап 1 — чистый рефакторинг,
|
||||
подтверждаемый существующим набором проверок, прежде чем менять модель данных.
|
||||
|
||||
## 10. Спайки до начала работ
|
||||
|
||||
- **S-ML-1 — `reg4` в `multipath` и матчах.** Подтвердить, что
|
||||
`load:<pool_id>->NXM_NX_REG4[]` перед `multipath` и последующий матч
|
||||
`reg4=...,reg1=...` работают детерминированно на OVS 3.1 (в стенде уже
|
||||
использовался похожий приём с `reg0`/`reg3` — риск низкий, но не проверен
|
||||
именно для `reg4` + `multipath`).
|
||||
- **S-ML-2 — `flow delete` с фильтром `reg4` в бандле.** Убедиться, что частичное
|
||||
удаление подмножества правил таблицы 11 по регистру не создаёт временного
|
||||
окна с неполной раскладкой для **не изменяемых** пулов.
|
||||
- **S-ML-3 — совместное использование члена двумя пулами.** Проверить обратную
|
||||
трансляцию (`ct(nat)` в таблице 15/24), когда один и тот же бэкенд обслуживает
|
||||
два листенера на разных портах приёма, но с одним `member_port` — граница
|
||||
правила §7.
|
||||
|
||||
## 11. Открытые вопросы
|
||||
|
||||
1. Нужен ли явный API создания листенера и пула (REST/CLI) на этом этапе, или
|
||||
пока достаточно декларативного `topology.env`/`listeners.env`.
|
||||
2. Разрешено ли одному бэкенду входить в пулы двух разных листенеров
|
||||
одновременно (раздел 7) — это меняет модель валидации.
|
||||
3. Нужны ли разные health-monitor'ы для разных пулов на одной ноде (например,
|
||||
TCP-проба для одного пула и HTTP — для другого) — конфиг демона уже это
|
||||
допускает per-pool, вопрос в приоритете реализации.
|
||||
4. Ожидаемое число листенеров и пулов на ноде вне мультитенантного сценария —
|
||||
от этого зависит, достаточно ли плоского конфиг-файла или сразу нужен OVSDB.
|
||||
5. Как это соотносится с `MULTITENANCY.md`: планируется ли многопуловость как
|
||||
промежуточный шаг перед тенантами, или тенант тоже может иметь несколько
|
||||
листенеров с разными пулами (тогда `reg3` и `reg4` комбинируются с самого
|
||||
начала).
|
||||
Reference in new issue
Block a user