MAC каждого члена пула больше не статическая константа в topology.env, а резолвится демоном hcd через обычный ARP ядра — в реальном окружении MAC бэкенда заранее не известен (сервер ещё не подключён, NIC может замениться), топология не описывается статически, в отличие от стенда. hcif-порты (единственные адреса узла в ядре) уже были настоящими L3-интерфейсами в тех же сегментах, что и бэкенды — единственное, что мешало обычному ARP, это permanent-записи ip neigh в entrypoint.sh. Убрав их и добавив hc/neigh.go (читает ip -json neigh show, точечно заливает бандл в таблицу 21 на том же тикере, что и health-пробы), получили резолвер без нового OpenFlow-контроллера. Таблица 21 стала единственным источником MAC для обоих путей — маршрутизируемого и коммутируемого (шаг 3): таблица 12 больше не дублирует MAC инлайново, ct(commit) ведёт сразу в таблицу 21. Исправлен попутно найденный баг: после apply.sh (replace-flows) таблица 21 не восстанавливалась, поскольку syncNeighbors сравнивал MAC с памятью демона, а не с датапасом. Добавлен force-режим по аналогии с Agent.apply(), плюс регрессионная проверка в verify.sh. Проверено на живом стенде: MAC всех четырёх членов резолвлен и совпадает с реальными интерфейсами; смена MAC "железа" обнаружена и применена без вмешательства за счёт штатного старения ARP ядра. Регрессия: 94 из 94 проверок. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
105 lines
8.0 KiB
Markdown
105 lines
8.0 KiB
Markdown
# Шаг 1 — итоги: прототип окружения hpnn_v2
|
||
|
||
**Дата:** 2026-08-16
|
||
**Статус:** выполнено, стенд поднят и проверен (30 из 30 проверок)
|
||
**План:** [STEP1_IMPLEMENTATION_PLAN.md](STEP1_IMPLEMENTATION_PLAN.md)
|
||
|
||
## Что сделано
|
||
|
||
Собрано контейнерное окружение из трёх сетей и трёх контейнеров, на котором
|
||
работают обе подсистемы — маршрутизация и балансировка нагрузки. Всё
|
||
форвардинг-решение принимается в OpenFlow: сетевой стек ядра контейнера
|
||
транзитный трафик не обрабатывает.
|
||
|
||
- **Контейнер 1 (`hpnn-lb`)** — Open vSwitch с kernel datapath в собственном
|
||
netns, мост `br-lb` с тремя портами. Пайплайн из 42 правил и группы
|
||
балансировки рендерится скриптом из `topology.env` и заливается атомарным
|
||
бандлом.
|
||
- **Контейнеры 2 и 3 (`hpnn-be1`, `hpnn-be2`)** — идентичный web-сервис на Go,
|
||
каждый в своём приватном сегменте; отдаёт имя хоста, дату, время и адрес
|
||
клиента.
|
||
- **Сети** — публичный сегмент через macvlan поверх `enp3s0` (настоящий L2
|
||
клиентской сети) и два приватных bridge-сегмента.
|
||
- **Инструментарий** — `make up/down/flows/verify`, `lbctl` для осмотра
|
||
датапаса, `scripts/host-prereq.sh` для подготовки хоста.
|
||
|
||
## Результаты проверок
|
||
|
||
| Проверка | Результат |
|
||
|---|---|
|
||
| Kernel datapath OVS в netns контейнера | `system@ovs-system`, поддержка recirculation, ct_state_nat, ct_zone |
|
||
| macvlan-интерфейс как порт OVS | работает, `pub0` — ofport 1 |
|
||
| `selection_method=hash, fields(ip_src,tcp_src)` | принят OVS 3.1 |
|
||
| Балансировка 20 запросов с клиента | be1/be2 ≈ 10/10, счётчики обоих бакетов растут |
|
||
| Сохранение IP клиента | бэкенд видит `192.168.5.13` — DNAT без SNAT подтверждён |
|
||
| ARP- и ICMP-респондеры OVS | `ping` до узла и до VIP проходит |
|
||
| Маршрутизация между приватными сегментами | `be1 → be2` через таблицы 20/21 |
|
||
| Профиль A (§3.2 дизайна v1) | egress бэкенда наружу работает и на порту `p2` не появляется |
|
||
| `ofproto/trace` пути клиент → VIP | DNAT, `dec_ttl`, перепись MAC, выход в порт 2 |
|
||
|
||
## Отличия от плана
|
||
|
||
1. **Публичная сеть объявлена с подсетью `192.168.55.0/24`, а не
|
||
`192.168.5.0/24`.** Docker отказывается регистрировать пул, пересекающийся
|
||
с уже существующей macvlan-сетью соседнего стенда `router-functest`
|
||
(`Pool overlaps with other one on this address space`). Объявленная подсеть
|
||
нужна только Docker IPAM: адреса узла и VIP живут исключительно в правилах
|
||
OpenFlow, а macvlan включён в `enp3s0` и работает на реальном L2.
|
||
|
||
2. **Обратный путь занял таблицы 15 и 16 вместо 30 и 31.** `goto_table` в
|
||
OpenFlow разрешает переход только вперёд, поэтому таблицы обратного пути
|
||
обязаны стоять до общей маршрутизации (20).
|
||
|
||
3. **DNAT вынесен из бакетов группы в отдельную таблицу 11.** Бакет только
|
||
кладёт номер члена пула в `reg2` и делает `resubmit`. Так вся логика
|
||
трансляции лежит в одном месте и не зависит от того, какие действия
|
||
допустимы внутри бакета.
|
||
|
||
4. **Обучение MAC клиентов сужено** до трафика, адресованного стенду (VIP,
|
||
адрес узла, приватные сегменты). В первой редакции `learn` срабатывал на
|
||
любой IP-пакет с macvlan-порта и заносил в таблицу 21 весь
|
||
широковещательный шум сегмента — DHCP, mDNS, SSDP всех соседей по LAN.
|
||
|
||
5. **Проверка профиля A добавлена в `verify.sh`** — в плане она была только
|
||
ручной.
|
||
|
||
## Известные ограничения
|
||
|
||
Осознанные упрощения шага 1, каждое — задача следующих шагов:
|
||
|
||
- **Датапас stateful.** Обратную трансляцию выполняет `ct(nat)`, а не
|
||
stateless un-DNAT из §2 дизайна v1. Следствие: прямой и обратный трафик
|
||
сессии обязаны проходить через один узел, то есть Active/Active ECMP в такой
|
||
схеме работать не будет.
|
||
- **ICMP-ошибки не генерируются.** У чисто-OpenFlow узла нет стека, который бы
|
||
формировал `TTL exceeded` и `fragmentation needed`; PMTUD через стенд не
|
||
работает, `traceroute` не показывает узел.
|
||
- **Фрагменты IP не обрабатываются** — правила матчат L4-заголовок, которого
|
||
во втором и последующих фрагментах нет.
|
||
- **Один узел LB.** Ни BGP, ни BFD, ни ECMP-набора: FRR в стенде нет, место
|
||
под него (internal-порты с IP) на шаге 1 не занято, поскольку маршрутизация
|
||
выполняется целиком в OpenFlow.
|
||
- **Нет health-check.** Бэкенды считаются живыми всегда; отказ бэкенда не
|
||
выводит его из группы. У приложения есть `/healthz` как задел.
|
||
- **Пул и листенер статические** — заданы в `topology.env`, control plane и
|
||
OVSDB-схемы из §6 дизайна v1 нет.
|
||
- ~~MAC бэкендов прописаны статически. ARP-резолвер next-hop отсутствует~~ —
|
||
снято на шаге 5: `hcd` резолвит MAC членов пула через обычный ARP ядра, см.
|
||
[STEP5_SUMMARY.md](STEP5_SUMMARY.md).
|
||
- **Только TCP и только IPv4.** UDP- и L3-листенеров нет.
|
||
|
||
## Задел на шаг 2
|
||
|
||
1. Заменить `ct(nat)` на stateless DNAT/un-DNAT с таблицей слотов и
|
||
`multipath(symmetric_l4)` — это спайк S0 из §12 дизайна v1 и предпосылка
|
||
для Active/Active.
|
||
2. Проверить детерминизм выбора бэкенда: один и тот же 5-tuple должен давать
|
||
один и тот же слот на разных узлах и между перезапусками
|
||
(`ofproto/trace` по корпусу синтетических кортежей).
|
||
3. Подсистема health-check: пробы с уникального адреса узла, вывод члена из
|
||
пула, перерасчёт таблицы слотов.
|
||
4. Control plane: модель `Load_Balancer → Listener → Pool → Member` в OVSDB
|
||
вместо `topology.env`, агент-рендерер пайплайна.
|
||
5. ICMP-транслятор в userspace (packet-in) — для PMTUD и корректных
|
||
ICMP-ошибок от имени VIP.
|