Files
hpnn-proto/docs/STEP1_IMPLEMENTATION_PLAN.md

127 lines
13 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.
# Шаг 1 — прототип окружения hpnn_v2 (OVS: маршрутизация + балансировка)
## Контекст
`hpnn_v1` содержит только дизайн-концепцию (`/opt/lvraid/claude/hpnn_v1/docs/design.md`, v0.3) OVS-native балансировщика: весь датапас — OpenFlow, DNAT без SNAT (бэкенд видит реальный IP клиента), возврат трафика маршрутизацией, control plane пишется свой. Кода нет.
`hpnn_v2` начинается с шага 1: собрать минимально достаточное контейнерное окружение, на котором можно развивать две подсистемы — **маршрутизации** и **балансировки нагрузки**. Результат шага: клиент из 192.168.5.0/24 обращается на VIP контейнера-1 и видит поочерёдно два разных бэкенда, а бэкенды видят реальный IP клиента; одновременно работает транзитная маршрутизация между приватными сегментами. Всё форвардинг-решение принимает OVS, ядро контейнера-1 транзитный трафик не обрабатывает.
Утверждённые решения:
- публичный сегмент — Docker **macvlan** поверх `enp3s0` (схема уже проверена на этом хосте: `router-functest_lan`, 192.168.5.11);
- **kernel datapath** OVS (модуль `openvswitch.ko` на хосте есть, не загружен);
- балансировка — **`group type=select`** + `ct(nat)`;
- маршрутизация — **целиком в OpenFlow** (свой ARP-респондер, статические next-hop-привязки, ICMP-ошибки на шаге 1 не генерируются);
- адреса: узел `192.168.5.20`, VIP `192.168.5.21`.
## Топология
```
клиент 192.168.5.0/24
│
[ enp3s0, macvlan ] сеть 1 «публичная»: 192.168.5.0/24
│ pub0 (02:42:c0:a8:05:14)
┌─────┴──────────────────────────────┐
│ lb-router (privileged, OVS) │ узел .20, VIP .21
│ br-lb: pub0, p2, p3 │
└──┬──────────────────────────┬──────┘
p2 │ 10.20.0.1 │ 10.30.0.1 p3
сеть 2 │ 10.20.0.0/24 сеть 3 │ 10.30.0.0/24
be1│ 10.20.0.2 be2│ 10.30.0.2
```
- Docker-шлюзы приватных сетей смещены на `.254` (`ipam.config.gateway`), чтобы `.1` занял OVS-роутер и не конфликтовал с host-бриджем.
- Дефолт бэкендов остаётся на `.254` (выход в интернет мимо LB — профиль A из §3.2 дизайна v1). Через LB бэкенды маршрутизируют только клиентские префиксы:
`ip route add 192.168.5.0/24 via 10.20.0.1` и `ip route add 10.30.0.0/24 via 10.20.0.1` (симметрично на be2).
- MAC бэкендов и `pub0` фиксируются в compose (`mac_address:`) — на них строятся статические adjacency-правила.
## Структура репозитория
```
/opt/lvraid/claude/hpnn_v2/
├── README.md # обзор стенда, запуск, проверка
├── docs/
│ ├── STEP1_IMPLEMENTATION_PLAN.md # копия этого плана (артефакт «план внедрения»)
│ └── STEP1_SUMMARY.md # итоги по факту выполнения
├── docker-compose.yml
├── Makefile # up / down / flows / verify / logs
├── lb/
│ ├── Dockerfile # debian:bookworm-slim + openvswitch-switch + iproute2 + tcpdump
│ ├── entrypoint.sh # запуск OVS, сборка br-lb, применение пайплайна
│ ├── topology.env # единственный источник правды: IP, MAC, порты, VIP
│ ├── pipeline.sh # генерация OpenFlow-правил из topology.env
│ └── lbctl.sh # обёртки: dump-flows / group-stats / ofproto-trace
├── backend/
│ ├── Dockerfile # multi-stage: golang:1.23-alpine → alpine (нужен iproute2)
│ ├── entrypoint.sh # установка маршрутов на клиентские префиксы, запуск app
│ ├── go.mod
│ └── main.go # hostname, дата/время, IP клиента, локальный адрес
└── scripts/
├── host-prereq.sh # modprobe openvswitch; опциональный macvlan-shim для проверки с хоста
└── verify.sh # e2e-проверки (см. раздел «Верификация»)
```
## Реализация
### 1. Контейнер-1: подъём OVS и портов (`lb/entrypoint.sh`)
1. `modprobe openvswitch` (хост-модуль виден через `/lib/modules:ro`), запуск `ovsdb-server` + `ovs-vswitchd`, создание `br-lb` с `fail_mode=secure`, `protocols=OpenFlow13,OpenFlow15` и без контроллера.
2. Для каждого из трёх Docker-интерфейсов: снять IP (`ip addr flush`), внести в мост (`ovs-vsctl add-port br-lb <if>`), закрепить `ofport_request` (pub0=1, p2=2, p3=3), поднять.
IP-адреса на интерфейсах не нужны — L3 живёт в OpenFlow, IPAM-резервация Docker остаётся за контейнером и предотвращает выдачу этих адресов кому-то ещё.
3. Применение пайплайна атомарным бандлом: `ovs-ofctl --bundle -O OpenFlow15 replace-flows br-lb <(pipeline.sh)` — обновление без blackhole-окна (принцип §4 дизайна v1).
**Риск и запасной вариант.** Подключение macvlan-устройства портом OVS — рабочая, но нечастая схема. Если `ovs-vsctl add-port` не заведётся, откат: внутри контейнера поднять linux-bridge `brpub`, включить в него macvlan и один конец veth-пары, второй конец veth отдать в `br-lb`. Логика OpenFlow при этом не меняется — правится только шаг 2 entrypoint.
### 2. OpenFlow-пайплайн (`lb/pipeline.sh`)
| Таблица | Назначение |
|---|---|
| **0** | Классификация: ARP → t5; ICMP echo на собственные IP → t6; IP из `pub0` → learn-адъяценции + t10; IP из `p2/p3` → t30; иначе drop |
| **3** (inline `learn` в t0) | Обучение MAC клиентов: `learn(table=21, NXM_OF_IP_DST[]=NXM_OF_IP_SRC[], load:NXM_OF_ETH_SRC[]->NXM_OF_ETH_DST[], load:<pub0 mac>->NXM_OF_ETH_SRC[], output:NXM_OF_IN_PORT[])`, `hard_timeout=300` — заменяет отсутствующий ARP-резолвер для клиентской стороны |
| **5** | ARP-респондер для `192.168.5.20`, `192.168.5.21`, `10.20.0.1`, `10.30.0.1` (op=2, swap SHA/THA, `output:IN_PORT`) |
| **6** | ICMP echo-reply для тех же адресов (swap eth/ip, `icmp_type=0`) — даёт `ping` до узла и VIP |
| **10** | Листенеры: `tcp,nw_dst=192.168.5.21,tp_dst=80 → group:100`; остальной IP → t20 (обычная маршрутизация) |
| **group 100** | `type=select, selection_method=hash, fields(ip_src,tcp_src)`; 2 бакета, в каждом `ct(commit,zone=1,nat(dst=10.X0.0.2:8080),table=20)`. `tcp_src` в ключе обязателен — иначе все запросы одного клиента лягут на один бэкенд |
| **20** | Маршрутизация: LPM через приоритет = длина префикса. `10.20.0.0/24`→p2, `10.30.0.0/24`→p3, `192.168.5.0/24`→pub0; действия `dec_ttl`, `mod_dl_src=<MAC порта>`, `resubmit(,21)`. `priority=0` → drop (дефолта нет) |
| **21** | Adjacency: статически `nw_dst=10.20.0.2 → mod_dl_dst=<be1 mac>, output:p2` (и симметрично be2); клиентские адреса приходят сюда из `learn` |
| **30** | Возврат из приватных сетей: TCP → `ct(table=31, zone=1, nat)` (снимает DNAT: `src` снова VIP, `dst` — клиент); прочий IP (be↔be, транзит) → t20 |
| **31** | После ct → t20 |
Un-DNAT выполняет `ct(nat)`, отдельных правил обратной трансляции не требуется. Stateless-вариант из §2 дизайна v1 остаётся задачей следующего шага — здесь сознательно взят stateful, чтобы окружение поднялось минимальными средствами.
### 3. Бэкенды (`backend/`)
`main.go` — HTTP-сервер на `:8080`, отдаёт `text/plain`: hostname, дата/время с таймзоной, `RemoteAddr` (реальный IP клиента — ключевое доказательство DNAT-only), локальный адрес соединения и счётчик запросов. Плюс `/healthz` для будущих проб. Стандартная библиотека, без зависимостей.
`entrypoint.sh` (нужен `NET_ADMIN`): ставит маршруты на клиентский префикс и на соседний приватный сегмент через `.1`, затем `exec` приложения.
### 4. Compose
Три сети: `pub` (macvlan, `parent: enp3s0`, `192.168.5.0/24`), `priv2` (bridge, `10.20.0.0/24`, gateway `.254`), `priv3` (bridge, `10.30.0.0/24`, gateway `.254`).
`lb-router`: `privileged: true`, `/lib/modules:ro`, все три сети, фиксированные IP и MAC.
`be1`/`be2`: по одной приватной сети каждый, `cap_add: [NET_ADMIN]`, фиксированные IP и MAC.
## Верификация
**Подготовка хоста:** `scripts/host-prereq.sh` — `modprobe openvswitch`; при желании проверять с самого хоста (192.168.5.9) он же создаёт macvlan-shim, т.к. macvlan не пропускает трафик хост↔контейнер напрямую.
С клиента из 192.168.5.0/24:
| Проверка | Команда | Ожидание |
|---|---|---|
| Доступность узла и VIP | `ping 192.168.5.20`, `ping 192.168.5.21` | отвечает OpenFlow-респондер (t6) |
| Балансировка | `for i in $(seq 20); do curl -s http://192.168.5.21/; done` | оба hostname встречаются, распределение близко к 50/50 |
| Сохранение IP клиента | тот же вывод | в поле «клиент» — реальный адрес 192.168.5.x, не адрес LB |
| Распределение по бакетам | `docker compose exec lb-router ovs-ofctl -O OpenFlow15 dump-group-stats br-lb` | счётчики обоих бакетов растут |
| Детерминизм выбора | `ovs-appctl ofproto/trace br-lb <5-tuple>` | одинаковый бакет для одного и того же кортежа |
| Маршрутизация между сегментами | `docker compose exec be1 curl -s http://10.30.0.2:8080/` | ответ be2, трафик прошёл через t20/t21 |
| Профиль A (egress мимо LB) | `docker compose exec be1 ping -c1 8.8.8.8` при `tcpdump -ni p2` на LB | пакеты на LB не появляются |
| Целостность пайплайна | `ovs-ofctl -O OpenFlow15 dump-flows br-lb`, `ovs-vsctl show` | три порта в мосту, все таблицы на месте, счётчик drop-правил t20 не растёт при нормальном трафике |
`scripts/verify.sh` прогоняет проверки, выполнимые с хоста, и печатает сводку; клиентские шаги с curl/ping вынесены в README как ручные.
## Артефакты (по требованию CLAUDE.md)
1. `docs/STEP1_IMPLEMENTATION_PLAN.md` — план внедрения (копия этого документа) — **до** начала работ.
2. `README.md` — назначение стенда, схема, адресный план, запуск, проверка, устранение неполадок.
3. `docs/STEP1_SUMMARY.md` — по завершении: что сделано, отличия от плана, известные ограничения (stateful `ct` вместо stateless un-DNAT, отсутствие ICMP-ошибок и health-check, один узел LB вместо ECMP-набора) и заготовка задач шага 2.