Files
hpnn-proto/README.md
T

257 lines
16 KiB
Markdown
Raw Normal View History

# hpnn_v2 — стенд OVS-маршрутизатора и балансировщика нагрузки
Контейнерный прототип основы сервиса: один узел на Open vSwitch, который
одновременно выполняет две функции — **маршрутизацию** между сегментами и
**балансировку нагрузки** на четыре бэкенда. Всё форвардинг-решение принимается
в OpenFlow: сетевой стек ядра контейнера транзитный трафик не обрабатывает.
Состав пула ведёт подсистема health-check: мёртвые бэкенды выводятся из
балансировки, восстановившиеся возвращаются.
Развивает дизайн-концепцию `../hpnn_v1/docs/design.md`.
| Шаг | Содержание | План | Итоги |
|---|---|---|---|
| 1 | Окружение, маршрутизация, балансировка | [план](docs/STEP1_IMPLEMENTATION_PLAN.md) | [итоги](docs/STEP1_SUMMARY.md) |
| 2 | Health-check и таблица слотов | [план](docs/STEP2_IMPLEMENTATION_PLAN.md) | [итоги](docs/STEP2_SUMMARY.md) |
| — | Расширение пула до четырёх бэкендов | [план](docs/CHANGE_POOL_4_MEMBERS_PLAN.md) | [итоги](docs/CHANGE_POOL_4_MEMBERS_SUMMARY.md) |
## Топология
```
клиент из 192.168.5.0/24
│
[ enp3s0, macvlan ] сеть 1 — публичный сегмент
│ pub0 · ofport 1
┌─────────────────┴─────────────────────┐
│ hpnn-lb (Open vSwitch, br-lb) │ узел 192.168.5.20
│ маршрутизатор + балансировщик │ VIP 192.168.5.21
│ hcd: health-check + таблица слотов │
└──┬──────────────────────────────┬─────┘
p2 ·2 │ 10.20.0.1 10.30.0.1 │ p3 ·3
hcif-p2 ·4 10.20.0.253 10.30.0.253 │ hcif-p3 ·5 (источники проб)
сеть 2 │ 10.20.0.0/24 сеть 3 │ 10.30.0.0/24
hpnn-be1 │ 10.20.0.2 hpnn-be2 │ 10.30.0.2
hpnn-be3 │ 10.20.0.3 hpnn-be4 │ 10.30.0.3
```
| Контейнер | Роль |
|---|---|
| `hpnn-lb` | OVS, все три сети, маршрутизация и балансировка |
| `hpnn-be1`, `hpnn-be3` | web-сервис на Go в приватном сегменте 2 |
| `hpnn-be2`, `hpnn-be4` | тот же сервис в приватном сегменте 3 |
### Адресный план
| Назначение | Адрес |
|---|---|
| Клиентская сеть | `192.168.5.0/24` |
| Адрес узла в публичном сегменте | `192.168.5.20` |
| **VIP балансировщика** | **`192.168.5.21:80`** |
| Шлюз OVS в сети 2 / бэкенды be1, be3 | `10.20.0.1` / `10.20.0.2:8080`, `10.20.0.3:8080` |
| Шлюз OVS в сети 3 / бэкенды be2, be4 | `10.30.0.1` / `10.30.0.2:8080`, `10.30.0.3:8080` |
| Источники health-проб (internal-порты OVS) | `10.20.0.253`, `10.30.0.253` |
| Шлюзы Docker (собственный egress бэкендов) | `10.20.0.254`, `10.30.0.254` |
Адреса `192.168.5.20` и `192.168.5.21` существуют **только в правилах
OpenFlow** — на интерфейсах они не настроены. ARP-респондер OVS отвечает на
них MAC-адресом порта `pub0`. Исключение — адреса `.253`: это единственные
адреса, которые узел держит в ядре, потому что с них уходят health-пробы.
Единственный источник правды по адресам — [lb/topology.env](lb/topology.env).
## Запуск
```bash
make up # modprobe openvswitch + сборка образов + запуск
make verify # проверки
make down # остановка
```
Проверка с самого хоста требует macvlan-shim: интерфейс macvlan контейнера и
физический интерфейс хоста по устройству macvlan не видят друг друга. Клиенту
из 192.168.5.0/24 никакой shim не нужен.
```bash
make shim # поднять shim 192.168.5.13 с маршрутами на .20 и .21
make shim-down
```
Полный сценарий ручной проверки стенда — [docs/MANUAL_TEST_PLAN.md](docs/MANUAL_TEST_PLAN.md)
(что выполнять на клиенте, что на хосте, чего ожидать на каждом шаге и что
означает отклонение). Автоматический аналог — `make verify`.
## Проверка с клиента
```bash
ping 192.168.5.20 # адрес узла — отвечает ICMP-респондер OVS
ping 192.168.5.21 # VIP
for i in $(seq 20); do curl -s http://192.168.5.21/; done
```
```
backend=be1 time=2026-08-16 21:35:02 MSK client=192.168.5.13:47734 served=10.20.0.2:8080 req=24
backend=be3 time=2026-08-16 21:35:02 MSK client=192.168.5.13:47740 served=10.20.0.3:8080 req=11
backend=be2 time=2026-08-16 21:35:02 MSK client=192.168.5.13:47742 served=10.30.0.2:8080 req=30
backend=be4 time=2026-08-16 21:35:02 MSK client=192.168.5.13:47746 served=10.30.0.3:8080 req=17
```
Что здесь видно:
- **балансировка** — ответы приходят от всех четырёх бэкендов примерно поровну;
- **сохранение IP клиента** — в поле `client` стоит реальный адрес из
192.168.5.0/24, а не адрес балансировщика: выполняется только DNAT, SNAT
нигде не делается;
- ключ хэша — `(ip_src, tcp_src)`, поэтому разные соединения одного клиента
расходятся по бэкендам, а пакеты одного соединения — нет.
Подсистему маршрутизации видно отдельно от балансировки:
```bash
# на клиенте: маршруты в приватные сегменты через узел
ip route add 10.20.0.0/24 via 192.168.5.20
ip route add 10.30.0.0/24 via 192.168.5.20
curl http://10.20.0.2:8080/ # напрямую в бэкенд, мимо VIP
```
> Эти маршруты нужно прописывать на **клиентской машине**, а не на хосте
> стенда: на самом хосте они перекроют connected-маршруты docker-бриджей
> `hpnn-p2`/`hpnn-p3` и оборвут бэкендам выход наружу.
Транзит между приватными сегментами проверяется изнутри:
```bash
docker compose exec be1 curl -s http://10.30.0.2:8080/ # be1 -> be2 через OVS
```
## Подсистема health-check
Демон `hcd` внутри контейнера-балансировщика проверяет живость членов пула и
отражает их состав в датапасе. Пробы уходят **с уникального адреса узла** в
сегменте бэкенда (`10.20.0.253`, `10.30.0.253`), а не с VIP — иначе ответ
вернулся бы не тому, кто проверял.
```bash
make health # состояние членов пула
make slots # раскладка слотов и счётчики пакетов по членам
make drain M=be1 # вывести член из балансировки (пробы продолжаются)
make enable M=be1 # вернуть член в балансировку
make metrics # метрики Prometheus
```
```
пул 1: слотов 1024, проба http каждые 2s (rise=2 fall=3)
дайджест раскладки: 0a16713c5a9eb94d
ЧЛЕН АДРЕС СОСТОЯНИЕ ADMIN В ПУЛЕ СЛОТОВ ПРОБ НЕУДАЧ МС ИСТОЧНИК ПРОБ
be1 10.20.0.2:8080 up enabled да 256 120 0 1 10.20.0.253
be2 10.30.0.2:8080 up enabled да 256 120 0 2 10.30.0.253
be3 10.20.0.3:8080 up enabled да 256 120 0 2 10.20.0.253
be4 10.30.0.3:8080 up enabled да 256 120 0 2 10.30.0.253
```
Проверить руками:
```bash
docker compose pause be1 # член уходит в down за fall × interval = 6 с
curl http://192.168.5.21/ # трафик обслуживают три оставшихся члена
docker compose unpause be1 # возвращается за rise × interval = 4 с
```
Параметры проб (тип, интервал, таймаут, `rise`/`fall`) — в
[lb/topology.env](lb/topology.env), раздел `HC_*`.
### Таблица слотов
Балансировка выполняется не группой OVS, а таблицей из 1024 слотов
(таблица 11). Номер слота даёт `multipath(symmetric_l4)` от заголовков
пакета, слот указывает на члена пула. Раскладку слотов по членам считает
`hcd` по алгоритму Maglev (§5 дизайна v1) и заливает атомарным бандлом.
Свойства, которые это даёт:
- **детерминированность** — одинаковый состав пула даёт одинаковую раскладку
на любом узле и между перезапусками; контролируется дайджестом SHA-256;
- **минимальное возмущение** — при выбытии члена переезжают его слоты, а у
оставшихся меняются единицы: на этом стенде вывод одного из четырёх членов
сдвигает 256 его слотов и лишь 8 чужих (0,8 %); проверяется в `make verify`;
- **fail-close** — когда живых членов нет, таблица пустеет и трафик на VIP
отбрасывается со счётчиком, а не уходит на заведомо мёртвый бэкенд;
- бесплатная **per-member статистика** из счётчиков правил слотов.
## Осмотр датапаса
```bash
make ports # порты моста
make conns # таблица соединений ct
make trace SRC=192.168.5.13 # ofproto/trace сессии клиент -> VIP
make flows # перечитать pipeline.sh и перезалить правила
docker compose exec lb-router lbctl flows 21 # правила конкретной таблицы
docker compose exec lb-router lbctl status # состояние пула в JSON
```
## Как устроен пайплайн
Все правила рендерит [lb/pipeline.sh](lb/pipeline.sh) из `topology.env` и
применяет атомарным бандлом (`ovs-ofctl --bundle replace-flows`), поэтому
обновление не создаёт окна с полузалитым пайплайном.
| Таблица | Назначение |
|---|---|
| 0 | Классификация по входному порту; обучение MAC клиентов для обратного пути |
| 5 | ARP-респондер для адресов узла, VIP и источников проб |
| 6 | ICMP echo-респондер для адресов узла и VIP |
| 10 | Листенеры: `VIP:80` → `multipath` кладёт номер слота в `reg1`; прочий IP → маршрутизация |
| 11 | **Таблица слотов**: `reg1` → `reg2` (член пула). Ведёт демон `hcd` |
| 12 | DNAT выбранным членом пула (`ct(commit, nat)`) |
| 15, 16 | Обратный путь: `ct(nat)` снимает DNAT, источник снова становится VIP |
| 20 | Маршрутизация: приоритет = длина префикса, `dec_ttl`, MAC источника |
| 21 | Adjacency: MAC next-hop и выходной порт |
Регистры: `reg1` — номер слота, `reg2` — идентификатор члена пула.
Нумерация таблиц не произвольна: `goto_table` разрешает переход только вперёд,
поэтому обратный путь (15/16) стоит до общей маршрутизации (20).
### Чего нет у чисто-OpenFlow узла
Ядро контейнера в форвардинге не участвует, поэтому всё, что обычно делает
сетевой стек, реализовано правилами: ARP-ответы (таблица 5), ответы на ping
(таблица 6), поиск MAC клиента (действие `learn` в таблице 0 вместо
ARP-резолвера). ICMP-ошибки (`TTL exceeded`, `fragmentation needed`) на шаге 1
не генерируются — см. ограничения в [docs/STEP1_SUMMARY.md](docs/STEP1_SUMMARY.md).
## Особенности окружения
- **Kernel datapath OVS** namespace-aware, поэтому контейнер держит собственный
`ovs-system` — это настоящий датапас ядра, а не эмуляция. Нужны
`privileged: true`, проброс `/lib/modules` и загруженный на хосте модуль
`openvswitch` (`make prereq`, модуль прописывается в автозагрузку).
- **Публичная сеть объявлена с подсетью `192.168.55.0/24`** — это фиктивный
пул для Docker IPAM. Реальную `192.168.5.0/24` объявить нельзя: её уже
занимает macvlan-сеть соседнего стенда `router-functest`, и Docker
отказывается регистрировать пересекающийся пул. Для L2 это безразлично —
macvlan включён в `enp3s0`, а адреса стенда живут в OpenFlow.
- **Шлюзы приватных сетей смещены на `.254`**, чтобы адрес `.1` занял
OVS-роутер и не конфликтовал с host-бриджем Docker.
- **Профиль A** (§3.2 дизайна v1): у бэкендов через балансировщик
маршрутизируются только клиентские префиксы и соседний приватный сегмент,
а `default` остаётся на шлюзе Docker — собственный исходящий трафик
бэкендов идёт мимо балансировщика.
## Структура
```
docker-compose.yml три сети и три контейнера
Makefile up / down / flows / verify / health / диагностика
lb/topology.env адреса, MAC, номера портов, параметры проб и слотов
lb/pipeline.sh генератор OpenFlow-правил
lb/apply.sh атомарная заливка правил и запрос слотов у hcd
lb/entrypoint.sh запуск OVS, сборка моста, порты проб, запуск hcd
lb/hc-config.sh конфигурация health-check из topology.env
lb/lbctl.sh осмотр датапаса (ports / flows / health / slots / trace)
hc/maglev.go раскладка слотов, дайджест, рендер бандла
hc/main.go пробер, состояния членов пула, применение, API
backend/main.go web-сервис: имя хоста, дата и время, адрес клиента
scripts/host-prereq.sh модуль ядра и macvlan-shim
scripts/verify.sh проверки стенда
```