Files
hpnn-proto/docs/STEP5_SUMMARY.md
T

127 lines
11 KiB
Markdown
Raw Normal View History

# Шаг 5 — итоги: динамическое изучение MAC/ARP для бэкендов
**Дата:** 2026-08-18
**Статус:** выполнено, проверено после холодного перезапуска (94 из 94 проверок)
**План:** [STEP5_IMPLEMENTATION_PLAN.md](STEP5_IMPLEMENTATION_PLAN.md)
**Предыдущий шаг:** [STEP3_SUMMARY.md](STEP3_SUMMARY.md)
## Что сделано
MAC каждого члена пула больше не статическая константа из `topology.env` — он
резолвится динамически демоном `hcd` через обычный ARP ядра.
- **`lb/entrypoint.sh`** — убраны permanent-записи `ip neigh` для адресов
членов пула. `hcif-p2`/`hcif-p3` — настоящие L3-интерфейсы ядра в тех же
сегментах, что и бэкенды, поэтому ядро само резолвит их MAC обычным ARP:
первая health-проба порождает ARP-запрос через `br-lb`, реальный бэкенд
отвечает, ядро запоминает.
- **`hc/neigh.go`** (новый) — на том же тикере, что и health-пробы, читает
`ip -json neigh show` для каждого члена по его интерфейсу (`hcif-p2`/
`hcif-p3`, новое поле `iface` в конфиге) и при изменении MAC атомарно
заливает точечный бандл `ovs-ofctl bundle` в таблицу 21 — не трогая записи
остальных членов.
- **Таблица 21 стала единственным источником MAC** для обоих путей —
маршрутизируемого и коммутируемого (шаг 3). Таблица 12 (same-segment DNAT)
больше не пишет MAC инлайново: `ct(commit,...)` теперь ведёт напрямую в
таблицу 21 вместо таблицы 25, минуя таблицу 20 (TTL по-прежнему не
уменьшается для соседей по сегменту).
- **`lbctl neigh` / `make neigh`** — таблица «член / адрес / интерфейс / MAC /
резолвлен / с момента», плюс сырые правила таблицы 21.
- **`BE1_MAC`…`BE4_MAC` в `topology.env`** остались только как «заводской» MAC,
который `attach-segment.sh` назначает интерфейсу ВМ (эмуляция того, что
реально прошито в NIC) — в OpenFlow эти переменные больше не попадают.
## Результаты проверок
| Проверка | Результат |
|---|---|
| `lbctl neigh` показывает реальный MAC каждого члена | совпадает с `ip link show eth0` внутри контейнера |
| Таблица 21, приоритет 100 | правило на каждого члена, `actions` содержат резолвленный MAC |
| Регрессия (все шаги 1–3) | 86 существующих проверок без изменений в поведении |
| **Смена MAC «железа»** | `ip link set eth0 address ...` у be1 → обнаружено `hcd`, таблица 21 обновлена, доставка восстановлена без вмешательства |
| Идемпотентность `apply.sh` | таблица 21 полностью восстанавливается после `replace-flows` (см. отклонения) |
## Ключевая находка (уже была на момент планирования)
`segment_ingress()`/`l3_learn()` и раньше заливала действием `learn` записи
приоритета 90 в таблицу 21 для любого IP-трафика с портов сегмента, включая
ответы бэкендов на health-пробы — но они были перекрыты статическими
правилами приоритета 100. Шаг 5 не строил резолвер с нуля: снял то, что
блокировало естественный ARP ядра (`nud permanent`), и сделал `hcd` явным,
наблюдаемым источником приоритета 100 вместо неявного поведения `learn`.
Разделение осталось ровно таким, как задумано: `learn` (приоритет 90)
по-прежнему единственный механизм для клиентов — их адрес заранее не известен
и не enumerable; `hcd` (приоритет 100) — для членов пула, чей адрес известен
из конфигурации.
## Отклонение от плана: баг с `force`
Первая реализация не заливала таблицу 21 заново после `apply.sh`
(`replace-flows` стирает весь пайплайн). Причина: `syncNeighbors()` сравнивал
резолвленный MAC с MAC **в памяти демона** — после `replace-flows` датапас
пуст, но в памяти MAC не изменился, поэтому условие «MAC изменился» не
срабатывало и точечный бандл не переливался. Обнаружено тестом идемпотентности
(`verify.sh`, блок 14 → новая проверка блока 17) — именно то, для чего этот
тест и существует.
Исправление зеркально `force` у `Agent.apply()`: `syncNeighbors(force bool)`,
`/reapply` теперь вызывает `a.syncNeighbors(true)` вслед за `a.apply(true)`.
Это тот же класс проблемы, что уже был явно предусмотрен для таблицы слотов
(`force заставляет залить даже неизменившуюся`, `hc/main.go`), просто
не был перенесён на новый подкомпонент при первой реализации.
## Наблюдение: реальная скорость обнаружения смены MAC
Проверка на живом стенде: смена MAC интерфейса `be1` обнаружена ядром и
отражена `hcd` в датапасе примерно через **2 минуты**, а не за один-два цикла
тикера (2 с), как можно было бы предположить из интервала опроса `hcd`.
Причина — `hcd` опрашивает **быстро** (каждые 2 с), но может отразить только
то, что уже узнало **ядро**, а стандартное старение Linux ARP (`REACHABLE` →
`STALE` → `DELAY` → `PROBE` → `FAILED`/переразрешение) по умолчанию рассчитано
на десятки секунд на первом же переходе (`base_reachable_time` ≈ 30 с) и суммарно
может занимать порядка минуты и больше. Частота опроса `hcd` не сокращает
это время — она лишь гарантирует, что новый MAC попадёт в датапас на первом же
тике **после** того, как его узнало ядро. Это честная характеристика решения,
а не дефект: тот же порядок величины, что и в реальных сетях с обычным ARP.
## Отклонения от плана
1. **`nud permanent` для members убраны полностью**, без опционального
ручного `mac`-переопределения в JSON — как и намечалось («не делать этого
на первой итерации»). Подтверждено достаточным: cold-start окно закрывается
первой же успешной пробой.
2. **Обнаружен и исправлен баг с `force`**, не предусмотренный явно в плане
(план предполагал его как риск в общих чертах — «зависимость от `ip -json
neigh`», но не called out force-реапплай конкретно). Добавлена
регрессионная проверка (`verify.sh`, блок 17) именно на этот случай.
3. **Проверка TTL в блоке 15 (`verify.sh`) сделана устойчивее**: захват `-c 4`
на 12 запросах оказался статистически хрупким — при 4 членах пула 12
запросов иногда не попадали ни разу на соседа по сегменту, тест ложно
падал. Увеличено до `-c 16` на 32 запросах; на двух прогонах подряд
стабильно зелёный. Не связано с шагом 5 по существу (тест существовал с
шага 3), но исправлено в этой же сессии, раз затронут `verify.sh`.
## Известные ограничения
- **Обнаружение новых, необъявленных хостов вне объёма** — резолвится MAC
только для адресов, уже присутствующих в `hc-config.sh`. Обнаружение самих
адресов — отдельная, более крупная задача control plane (`MULTITENANCY.md`).
- **Скорость обнаружения смены MAC ограничена стандартным старением ARP ядра**
(см. наблюдение выше) — величина порядка минуты, не секунд.
- **Окно на холодном старте**: пока `hcd` не сделал первый резолв, трафик к
члену зависит от того, успел ли сработать `learn` (приоритет 90); в худшем
случае — `priority=0 drop`. По порядку величины совпадает с fail-close
таблицы слотов, новой категории риска не вводит.
- **`ip -json neigh`** — версия `iproute2` в образе (`6.1.0`) поддерживает флаг;
при смене базового образа стоит переподтвердить.
## Задел на следующие шаги
1. Тот же приём (демон резолвит адрес через ядро и заливает точечным бандлом)
применим к обнаружению самих хостов, если понадобится снять ограничение
«только уже известные адреса» — потребует ARP-сканирования сегмента и
решения, как новый хост попадает в конфигурацию пула.
2. Control plane (`Load_Balancer → Listener → Pool → Member` в OVSDB,
`MULTITENANCY.md`) естественно принимает поле `iface` как часть модели
`Member`, а не специфику `hc-config.sh`.