Files
hpnn-proto/docs/STEP3_IMPLEMENTATION_PLAN.md
T

150 lines
9.9 KiB
Markdown
Raw 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.
# Шаг 3 — план внедрения: листенер в приватном сегменте
**Дата:** 2026-08-17
**Предыдущий шаг:** [STEP2_SUMMARY.md](STEP2_SUMMARY.md)
**Итоги:** [STEP3_SUMMARY.md](STEP3_SUMMARY.md)
## Задача
Поддержать листенер балансировщика **в приватном сегменте** — в одном сегменте
или во всех сразу — при условиях:
1. листенер работает как публичный: принимает на порту 80 и транслирует на порт
бэкенда 8080;
2. бэкенд может стоять в одном сегменте с клиентом, отправившим запрос;
3. в ОС клиента и бэкенда не вносится ничего — ни адресов, ни маршрутов, ни
интерфейсов;
4. адресное пространство подсети задаёт заказчик, дробить её нельзя;
5. трафик внутри сегмента не блокируется;
6. единственный уровень влияния — OVS как data plane SDN и сама нода
балансировщика.
Ключевое требование: на бэкенд приходит пакет с исходным IP клиента.
## Почему прежняя схема не годится
Клиент и бэкенд — L2-соседи в одной подсети. Ответ бэкенда уходит клиенту
напрямую, минуя балансировщик: снять DNAT негде, клиент получает пакет от
`10.20.0.2:8080` вместо `10.20.0.100:80` и рвёт соединение.
Условия отсекают все обходные пути:
| Вариант | Почему не подходит |
|---|---|
| SNAT | уничтожает сохранение IP клиента |
| DSR (VIP на `lo` бэкенда) | нарушает условие 3, не транслирует порт (условие 1) |
| Изоляция портов сегмента (private VLAN) | нарушает условие 5, неприменимо к ВМ |
| Дробление подсети маршрутами `/25` на бэкенде | нарушает условия 3 и 4 |
Остаётся единственная предпосылка, совпадающая с условием 6: **порты клиента и
бэкенда — порты OVS**. Тогда кадр «бэкенд → клиент-сосед» проходит через
OpenFlow-таблицы даже при прямом L2-обмене, и обратную трансляцию есть где
выполнить.
В целевой среде это выполняется по построению: ВМ подключены к vSwitch. Текущий
стенд этому не соответствует — приватный сегмент коммутирует docker-бридж
`hpnn-p2`, а `br-lb` подключён к нему одним портом. Шаг 3 приводит стенд к
целевой модели.
## Решение
`br-lb` становится **коммутатором приватного сегмента** и выполняет трансляцию
на пути кадра. Для внутрисегментного трафика нет ни маршрутизации, ни
`dec_ttl`: с точки зрения ВМ клиент и бэкенд остаются L2-соседями.
**Прямой путь.** Клиент резолвит VIP через ARP-респондер OVS и шлёт кадр на MAC
шлюза сегмента. Таблица листенера кладёт сессию в слот, таблица DNAT
переписывает `VIP:80 → member:8080`, `src` не трогает, заменяет dst MAC на MAC
члена и отдаёт кадр в коммутацию.
**Обратный путь.** Бэкенд отвечает соседу напрямую: `src=member:8080`,
`dst=клиент`. Кадр приходит на порт OVS, где `ct(nat)` снимает трансляцию —
`src` возвращается к `VIP:80` — и кадр коммутируется в порт клиента.
**Прочий трафик сегмента** (`be↔be`, клиент к бэкенду напрямую, egress к
docker-шлюзу, ARP) коммутируется обычным образом, без трансляции.
## Топология после изменения
Каждая ВМ сегмента — отдельный порт `br-lb`. Порты `p2`/`p3` остаются как
аплинки сегмента к docker-шлюзу: через них идёт egress бэкендов (профиль A).
| Сегмент | Порт | ofport | Адрес |
|---|---|---|---|
| priv2 `10.20.0.0/24` | `p2` (аплинк) | 2 | — |
| | `hcif-p2` | 4 | `10.20.0.253` |
| | `be1` | 10 | `10.20.0.2` |
| | `be3` | 11 | `10.20.0.3` |
| | `cli2` | 12 | `10.20.0.10` |
| priv3 `10.30.0.0/24` | `p3` (аплинк) | 3 | — |
| | `hcif-p3` | 5 | `10.30.0.253` |
| | `be2` | 20 | `10.30.0.2` |
| | `be4` | 21 | `10.30.0.3` |
| | `cli3` | 22 | `10.30.0.10` |
| pub `192.168.5.0/24` | `pub0` | 1 | маршрутизируемый, без изменений |
Шлюз сегмента `10.20.0.1` и VIP `10.20.0.100` отвечают одним MAC — `$P2_MAC`.
Это и есть признак, по которому пайплайн отличает L3-трафик (адресован
маршрутизатору) от L2-трафика сегмента.
## Карта таблиц
Новые — 1, 24, 25; остальные сохраняют назначение шага 2.
| Таблица | Назначение |
|---|---|
| 0 | определение сегмента по `in_port` (`reg0`), обучение MAC и adjacency |
| 1 | классификация: ARP, ICMP, листенер, L3-трафик к шлюзу, коммутация |
| 5 | ARP-респондер (адреса узла и VIP), затем коммутация |
| 6 | ICMP echo-респондер |
| 10 | листенеры: публичный и приватные |
| 11 | таблица слотов (заливает `hcd`, не меняется) |
| 12 | применение члена пула: DNAT + L2-выдача либо DNAT + маршрутизация |
| 15/16 | обратный путь L3 (публичный и межсегментный) |
| 20/21 | маршрутизация и adjacency |
| **24** | обратная трансляция коммутируемого трафика сегмента |
| **25** | L2-коммутация сегмента: FDB, broadcast и unknown flood |
Регистры: `reg0` — номер сегмента, `reg1` — слот, `reg2` — член пула.
## Порядок работ
1. `lb/topology.env` — порты и адреса участников сегментов, VIP приватных
листенеров, переключатель `PRIV_LISTENERS`.
2. `scripts/attach-segment.sh` — veth из netns контейнера в `br-lb`, адрес, MAC
и маршруты назначаются снаружи (эмуляция гипервизора и DHCP).
`docker-compose.yml`: участники сегментов переводятся на `network_mode: none`;
добавляются клиенты `cli2` и `cli3`.
3. `lb/pipeline.sh` — таблицы 0/1/5/25: сегментация, обучение, коммутация.
Проверка связности сегмента до всякой балансировки.
4. Листенер: таблицы 10/12 и обратная трансляция в таблице 24.
5. `lb/lbctl.sh` — `trace` по приватному листенеру, `fdb`.
6. `scripts/verify.sh` — новая секция и регрессия существующих проверок.
7. Документация: `docs/STEP3_SUMMARY.md`, `README.md`,
`docs/PRIVATE_LB_DATAFLOW.md`, `docs/MANUAL_TEST_PLAN.md`.
## Критерии приёмки
- `curl http://10.20.0.100/` из `cli2` обслуживается всеми четырьмя членами;
- во всех ответах `client=10.20.0.10` — исходный IP клиента;
- `served=10.20.0.2:8080` — трансляция порта 80 → 8080 выполняется;
- TTL ответа от VIP равен TTL прямого ответа соседа по сегменту — путь остался
коммутируемым;
- внутрисегментная связность не пострадала: `cli2 → be1:8080` напрямую,
`be1 → be3:8080`, egress бэкенда мимо балансировщика;
- `PRIV_LISTENERS="p2"` выключает листенер в priv3, не затрагивая связность;
- все проверки шага 2 проходят.
## Риски
- **`br-lb` получает функцию L2-коммутатора** — обучение, flood, изоляция
сегментов. Новый класс правил и главный источник ошибок шага, поэтому этап 3
проверяется до подключения балансировки.
- **Хрупкость стенда:** veth, добавленные вручную, теряются при пересоздании
контейнера. `make attach` восстанавливает; в реальной среде порты создаёт
гипервизор.
- **Обратная трансляция опирается на `ct`**, как и прежде. Stateless-вариант —
задел следующего шага.
- **Нагрузка сегмента ложится на балансировщик**: весь внутрисегментный трафик
проходит через `br-lb`.