Files
hpnn-proto/docs/STEP2_IMPLEMENTATION_PLAN.md

180 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Шаг 2 — подсистема health-check и таблица слотов
## Контекст
Стенд шага 1 работает: OVS маршрутизирует три сегмента и балансирует TCP на
два бэкенда группой `type=select`. Но состав пула статичен — отказ бэкенда
балансировщику неизвестен, и половина соединений уходит в никуда.
Шаг 2 добавляет подсистему проверки живости: узел сам определяет состояние
членов пула, выводит мёртвых из балансировки и возвращает восстановившихся.
Это §7 дизайн-концепции `../hpnn_v1/docs/design.md`, адаптированный к
одноузловому прототипу (кворум по кластеру и генерации пулов появятся вместе
с control plane).
Вместе с этим по решению заказчика группа `type=select` заменяется на
**таблицу слотов с Maglev-раскладкой** (§5 дизайна). Причина: при перезаписи
бакетов группы хэш пересчитывается целиком, и размещение новых соединений
переезжает даже у нетронутых членов. Таблица слотов даёт минимальное
возмущение — переезжают только слоты выбывшего члена — и является тем самым
механизмом, на котором позже строится stateless-датапас.
## Ключевые решения
**Пробы уходят с уникального адреса узла в сегменте бэкенда** (§7.1 дизайна),
не с VIP. У чисто-OpenFlow узла шага 1 адресов в ядре нет вовсе, поэтому на
мосту появляются два internal-порта: `hcif-p2` (`10.20.0.253`) и `hcif-p3`
(`10.30.0.253`). Это `lbif-bk` из §3.3 — на них позже поселится FRR.
**Пробер — демон на Go (`hcd`) внутри контейнера-балансировщика**, как
`lb-agent` на узле LB в дизайне. Становится основным процессом контейнера;
`ovsdb-server` и `ovs-vswitchd` работают демонами рядом.
**Fail-close при полном отказе пула:** таблица слотов пустеет, остаётся только
`priority=0 actions=drop` — трафик на VIP отбрасывается со счётчиком.
**Дренаж** (§7.3): член выводится из раскладки вручную, пробы при этом
продолжают идти и состояние остаётся `up`.
**Нюанс, который надо зафиксировать честно.** В пайплайне остаётся `ct(nat)`
для обратного пути, а conntrack закрепляет трансляцию за соединением при его
создании. Поэтому уже установленные сессии переживают смену раскладки и без
Maglev. Реальная ценность таблицы слотов здесь — стабильность размещения
**новых** соединений и подготовка к stateless-датапасу, где никакой ct не
подстрахует. Это проверяется отдельным тестом (см. верификацию).
## Реализация
### 1. Датапас: слоты вместо группы (`lb/pipeline.sh`)
Таблицы перенумеровываются так, чтобы все переходы шли вперёд (`goto_table`
назад не умеет):
| Таблица | Было | Стало |
|---|---|---|
| 10 | листенер → `group:100` | листенер → `multipath(...)` → таблица 11 |
| 11 | DNAT по `reg2` | **таблица слотов**: `reg1=<slot>` → `reg2=<member>` |
| 12 | — | DNAT по `reg2` (бывшая 11) |
```
table=10,priority=200,tcp,nw_dst=<VIP>,tp_dst=80 \
actions=multipath(symmetric_l4,<pool_id>,modulo_n,1024,0,NXM_NX_REG1[]),goto_table:11
# правила слотов заливает hcd; pipeline.sh отдаёт только fail-close основание
table=11,priority=0 actions=drop
```
Группа 100 удаляется (`ovs-ofctl del-groups`), `apply.sh` её больше не
создаёт. Счётчики правил таблицы 11 дают бесплатную per-member статистику.
### 2. Порты источника проб (`lb/entrypoint.sh`)
Для каждого приватного сегмента:
```
ovs-vsctl --may-exist add-port br-lb hcif-p2 \
-- set Interface hcif-p2 type=internal ofport_request=4 mac='"02:42:0a:14:00:fd"'
ip addr replace 10.20.0.253/24 dev hcif-p2
ip link set hcif-p2 up
ip neigh replace 10.20.0.2 lladdr 02:42:0a:14:00:02 dev hcif-p2 nud permanent
```
Статическая ARP-запись избавляет ядро от резолва MAC бэкенда — MAC членов и
так зафиксированы в `docker-compose.yml`. Обратное направление (бэкенд
резолвит `10.20.0.253`) закрывает ARP-респондер OVS.
Правила для hc-портов в `pipeline.sh`:
| Таблица | Правило |
|---|---|
| 0 | `in_port=hcif-*` → таблица 20 (без `learn`, без `ct`) |
| 5 | ARP-респондер для `10.20.0.253` и `10.30.0.253` |
| 20 | `/32` на адреса hc-портов, приоритет 32 → таблица 21, **без `dec_ttl`** |
| 21 | adjacency на hc-порты: `mod_dl_dst` + `output` |
Ответы бэкендов на пробы приходят на `p2`/`p3`, проходят таблицу 15 (записи в
`ct` для них нет, трансляция не применяется) и уходят транзитом.
### 3. Демон `hc/` (Go, ~350 строк)
`hc/maglev.go` — раскладка по §5.2 дизайна:
- ключ члена — `"address:port"`, не UUID: пересоздание члена с теми же
адресом и портом обязано давать ту же раскладку;
- вход — отсортированный по ключу список живых членов с весами + `pool_id`
как seed;
- 1024 слота; `offset = h1 % M`, `skip` приводится к **нечётному** — при
M = 1024 (степень двойки) чётный шаг не покрывает все слоты и заполнение
зациклилось бы;
- `slot_table_digest` — SHA-256 от сериализованной раскладки (§5.3), пишется
в лог при каждом применении.
`hc/main.go` — пробер и применение:
- конфиг — JSON, который `lb/hc-config.sh` генерирует из `topology.env`;
- тикер `interval` (2 с), пробы всех членов параллельно с таймаутом (1 с);
- тип пробы `http` (GET `/healthz`, ожидается 2xx) или `tcp` (connect);
источник фиксируется через `net.Dialer.LocalAddr` — это и есть требование
«пробы с уникального адреса узла»;
- переход в `down` после `fall` подряд неудач (3), в `up` — после `rise`
подряд успехов (2); в лог пишутся только переходы, как в §7.1;
- применение — атомарным бандлом `ovs-ofctl bundle` с директивами
`delete table=11` + `add ...`, то есть замена таблицы целиком без
промежуточного состояния; повторно та же раскладка не заливается;
- HTTP-API на `127.0.0.1:9111`: `/status` (JSON), `/metrics` (Prometheus),
`/drain?member=`, `/enable?member=`, `/reapply`.
`/reapply` дёргает `lb/apply.sh` после `replace-flows`: тот перезаливает весь
пайплайн и стёр бы слоты, поэтому в конце сообщает демону перезалить их.
### 4. Инструментарий и сборка
- `lbctl health | slots | drain <m> | enable <m>`; `make health`, `make slots`,
`make drain M=be1`, `make enable M=be1`.
- `lbctl slots` — сводка: сколько слотов у каждого члена, счётчики пакетов,
дайджест.
- Контекст сборки балансировщика расширяется до корня (`context: .`,
`dockerfile: lb/Dockerfile`), первая стадия — `golang:1.23-alpine`.
- В бэкенд добавляется `/slow` (ответ растягивается на N секунд) — нужен для
проверки выживания установленной сессии.
### 5. Файлы
```
hc/{go.mod,main.go,maglev.go} новый демон
lb/hc-config.sh генерация конфига из topology.env
lb/topology.env параметры проб, слотов, hc-портов
lb/pipeline.sh слоты, hc-порты, перенумерация таблиц
lb/entrypoint.sh internal-порты, статический neigh, запуск hcd
lb/apply.sh снятие группы, вызов /reapply
lb/lbctl.sh, Makefile health / slots / drain / enable
backend/main.go эндпоинт /slow
docker-compose.yml контекст сборки
scripts/verify.sh новые проверки
```
## Верификация
Добавляется в `scripts/verify.sh`:
| Проверка | Ожидание |
|---|---|
| Пробы доходят | `lbctl health` — оба члена `up`, счётчик `/healthz` на бэкендах растёт |
| Источник проб | на бэкенде видно обращение с `10.20.0.253`/`10.30.0.253`, не с VIP |
| Раскладка слотов | 1024 слота поделены поровну ±1, дайджест стабилен между перезапусками |
| **Минимальное возмущение** | снимок раскладки → `drain be1` → у be2 **ни один** слот не сменил владельца |
| Отказ члена | `docker compose pause be1` → `down` за `fall × interval`, его слоты перешли к be2 |
| Балансировка при отказе | все запросы на VIP обслуживает be2, ошибок нет |
| Восстановление | `unpause` → `up` за `rise × interval`, раскладка вернулась к исходному дайджесту |
| Выживание сессии | долгая сессия через `/slow` не рвётся при выводе второго члена |
| Полный отказ | оба члена недоступны → в таблице 11 только `drop`, трафик на VIP падает со счётчиком |
| Идемпотентность | `make flows` → слоты возвращаются в актуальном составе, а не в полном |
## Артефакты (по требованию CLAUDE.md)
1. `docs/STEP2_IMPLEMENTATION_PLAN.md` — план внедрения; черновик уже написан
под вариант с группой, привести к решению о таблице слотов.
2. `docs/STEP2_SUMMARY.md` — итоги, отклонения, ограничения.
3. `README.md` — раздел про health-check и таблицу слотов, схема с
hc-портами, новые команды; таблица пайплайна перенумерована.