Files
hpnn-proto/docs/STEP4_PUBLIC_STATELESS_ASSESSMENT.md
ayurishchevandClaude Opus 5 fa389f119b Шаг 5: динамическое изучение MAC/ARP для бэкендов
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>
2026-08-19 12:33:48 +03:00

20 KiB
Raw Permalink Blame History

Шаг 4 — оценка: публичная балансировка без conntrack

Дата: 2026-08-18 Статус: оценка осуществимости (не план внедрения и не реализация) Область: только публичный листенер 192.168.5.21:80 Источник: PUBLIC_LB_DATAFLOW.md, lb/pipeline.sh (таблицы 12, 15, 16) Смежные документы: PRIVATE_LB_DATAFLOW.md, MAGLEV_SLOT_TABLE.md

Задача

Оценить переход публичного пути с текущей stateful-схемы (ct(commit,nat) на прямом пути, ct(nat) на обратном) на схему без conntrack. Главное условие, заданное на входе: бэкенд обязан видеть реальный IP клиента — адрес, с которым клиент обратился на балансировщик, до всех преобразований.

Уточнение по ходу обсуждения: клиент всегда обращается на адрес балансировщика (VIP), не на бэкенд напрямую. Он должен получить ответ, который его TCP-стек и приложение примут как продолжение своего же соединения — то есть src ответа обязан остаться VIP:80.

Вывод

Технически осуществимо для нового трафика через публичный листенер, при двух явных условиях периметра и одном принимаемом изменении поведения:

  1. бэкенды не должны быть достижимы клиентами напрямую, в обход VIP (условие периметра, не техническое ограничение датапаса);
  2. правило обратной трансляции обязано быть сужено по адресу назначения, иначе оно ломает трафик между бэкендами;
  3. установленные сессии перестают быть защищены от смены состава пула — это принимаемый компромисс, а не дефект.

1. Что меняется в датапасе

Таблица Сейчас Предлагается
12 (прямой DNAT) ct(commit, zone=1, nat(dst=member:8080)) mod_nw_dst:<member_ip>, mod_tp_dst:<member_port> — без ct вообще
15 (обратный путь) одно правило tcp actions=ct(table=16,zone=1,nat) на весь трафик по одному правилу на каждого члена: tcp,nw_src=<member_ip>,tp_src=<member_port>,nw_dst=$PUB_NET actions=mod_nw_src:$VIP,mod_tp_src:$VIP_PORT,goto_table:16
16 пост-ct — счётчики не нужна для этого пути; сохраняется для транзита, где ct ещё используется

src клиента в т.12 не трогается ни сейчас, ни в новой схеме — сохранение IP клиента никогда не зависело от ct, поэтому по этой части поведение не меняется вообще.

Обоснование замены в т.15 — §2.2 дизайна v1: DNAT переписывает адрес назначения в фиксированный member_ip:member_port, а не в случайный порт, как SNAT. Поэтому пара (member_ip, member_port, proto) однозначно определяет (VIP, listener_port), и обратную трансляцию можно сделать статическим правилом без какого-либо состояния.

2. Условие периметра: прямой доступ к бэкенду должен быть закрыт

Таблица 1 отправляет в т.15 весь трафик с dl_dst = MAC шлюза сегмента, не различая, был ли пакет частью балансируемой сессии. Сегодняшний стенд умеет и поддерживает прямой доступ клиента к бэкенду в обход VIP (table=10, priority=100,ip actions=goto_table:20, проверяется в verify.sh секция 5 и MANUAL_TEST_PLAN блок C.2) — ответ на такое обращение и ответ на сессию через VIP дают на выходе бэкенда идентичные заголовки (src=member_ip:8080, dst=client_ip:client_port), различить их без состояния невозможно.

При заданном требовании — клиент всегда обращается на VIP — эта коллизия не относится к реальному клиентскому трафику. Она становится значимой только если прямой доступ к порту бэкенда остаётся разрешён одновременно со stateless-схемой. Вывод: предпосылка корректности схемы — бэкенды недостижимы клиентами напрямую, что и так является типовой практикой для облачных LB (изоляция периметра, security groups), а не новым ограничением, придуманным ради этого перехода.

3. Связность между бэкендами

Уточняющий вопрос по ходу оценки: не потеряют ли бэкенды способность общаться друг с другом. Ответ разделяется по топологии.

Бэкенды в разных сегментах. Ответ be1 → be2:8080 (транзит между сегментами) идёт через ту же таблицу 15, что и обратный трафик клиентских сессий, потому что оба используют один и тот же MAC шлюза сегмента как классификатор. Правило nw_dst=$PUB_NET в предложенном un-DNAT решает это: адресные пространства клиентов (192.168.5.0/24) и бэкендов (приватные сегменты) не пересекаются, поэтому транзитный трафик просто не попадает под условие правила и проходит немодифицированным. Связность между сегментами сохраняется, если это условие включено в правило.

Бэкенды в одном сегменте. Это территория приватного пути (таблица 24, шаг 3), не публичного, и в этой оценке не менялась — она по-прежнему на ct. Если в будущем этот путь тоже переводить на stateless-схему, префиксный критерий не сработает: в шаге 3 клиент и бэкенды намеренно живут в одной подсети, поэтому ответ be3 → be1 и ответ be3 → реальный клиент cli2 неотличимы по заголовкам. Решение для этого случая другое — не диапазон адресов, а явный список исключений (для каждого члена сегмента: «если назначение — адрес другого известного члена этого же сегмента, не транслировать»), которые должны иметь приоритет выше общего правила un-DNAT. Список членов конечен и известен заранее, в отличие от адресов произвольных клиентов, поэтому это решаемо, но требует отдельного набора правил и не входит в объём этой оценки (она ограничена публичным путём).

4. Изменение поведения: установленные сессии больше не защищены

Сейчас ct(commit) закрепляет привязку за соединением на весь срок его жизни независимо от таблицы слотов — это подтверждено в STEP2_SUMMARY.md: сессия, обслуживаемая выведенным из пула членом, не рвётся (verify.sh секция 13, MANUAL_TEST_PLAN блок I).

Без ct трансляция каждого пакета определяется исключительно текущим состоянием таблицы слотов. При выводе члена из пула следующий пакет уже установленной к нему сессии уйдёт на другого члена — без контекста TCP-соединения, и сессия оборвётся. Минимальное возмущение Maglev ограничивает долю пострадавших сессий (переезжают только слоты выбывшего члена, у остальных ничего не меняется), но не защищает сами эти сессии.

Это ровно тот компромисс, который был предсказан заранее в STEP2_SUMMARY.md: «ценность таблицы слотов раскроется при переходе на stateless-датапас, где единственной защитой от переезда сессий останется минимальное возмущение раскладки». Переход на публичном пути — первое место, где это предсказание становится проверяемым поведением, а не наблюдением.

Практическое следствие: тест «сессия переживает дренаж» (verify.sh секция 13) для публичного пути перестаёт быть верным и требует пересмотра или явного исключения из проверок при внедрении.

5. Узкое остаточное ограничение (из дизайна v1)

Если сам бэкенд когда-либо инициирует исходящее TCP-соединение с src-портом, равным его собственному порту прослушивания (8080), и адресует его в клиентскую сеть — такой пакет попадёт под правило un-DNAT и будет неверно переписан. Маловероятный случай (обычно исходящие соединения используют эфемерный порт источника), уже документирован в дизайне v1 (§3.2, «остаточное ограничение», §15.4). Переход на stateless ничего не меняет в этом отношении — ограничение существовало бы в любой чисто заголовочной схеме un-DNAT.

6. Что выигрывается

  • ct и зависимость от CT_ZONE уходят из публичного пути целиком — оба направления становятся чистым match + rewrite.
  • Меньше recirculation в датапасе: на момент оценки в датапасе стенда зафиксировано 6 потоков с ct(...), каждый вызывает recirc; на публичном пути они исчезают.
  • Снимается ограничение, зафиксированное в PUBLIC_LB_DATAFLOW.md (раздел 9): «прямой и обратный трафик сессии обязаны проходить через один узел». Это предпосылка, без которой Active/Active ECMP между несколькими узлами LB не имеет смысла обсуждать — устраняется она именно этим шагом.

7. Что не меняется

Маршрутизация (таблицы 20/21), таблица слотов (таблица 11), алгоритм Maglev, детерминизм multipath — не затрагиваются вообще. Ограничения по ICMP-ошибкам и фрагментации сохраняются в прежнем виде — они не были связаны с ct и раньше.

8. Отдельный вопрос: доставка до физического сервера в EVPN-фабрике

Поднят по ходу обсуждения, зафиксирован здесь как связанный, но не пересекающийся с темой ct.

Вопрос: если нода HPNN подключена к EVPN-фабрике, и MAC сетевого порта физического сервера-бэкенда доходит до HPNN через фабрику — будет ли трафик доставлен до сервера?

Ответ: это не зависит от наличия или отсутствия ct. Трансляция адреса (таблицы 12/15) и доставка кадра до правильного MAC (таблица 21, adjacency) — независимые механизмы. ct в доставке кадра не участвует ни сейчас, ни в предложенной схеме.

Реальная зависимость — в существующей модели adjacency: MAC членов пула сейчас статически прописаны в topology.env и заливаются в таблицу 21 константами (mod_dl_dst:$BE1_MAC). Это уже задокументированное ограничение — STEP1_SUMMARY.md: «MAC бэкендов прописаны статически. ARP-резолвер next-hop отсутствует». Дизайн v1 предполагал агента, резолвящего MAC next-hop через packet-in/packet-out по своей neigh-таблице (§4.3, §8.2), но это не реализовано ни в стенде, ни где-либо ещё.

То, что MAC физического порта доходит до HPNN по EVPN-фабрике, — свойство самой фабрики (leaf-коммутаторы разучивают MAC локально, публикуют по EVPN Type-2 остальным VTEP); HPNN в этом процессе не участвует, для него это просто кадры на порту аплинка. Вопрос не в том, дойдёт ли MAC до HPNN (дойдёт), а в том, знает ли пайплайн HPNN, какой MAC подставить в mod_dl_dst, когда решает отправить пакет этому серверу. При текущей статической модели — знает только то, что вручную прописано заранее. Если реальный MAC карты сервера не совпадает с прописанным (замена карты, переключение LACP/бондинга на другой физический линк, сервер ещё не был известен на момент конфигурации) — таблица 21 либо отправит кадр не туда, либо, если совпадения нет вовсе, попадёт под priority=0 actions=drop: трафик до сервера не дойдёт, несмотря на то что фабрика прекрасно знает его настоящий MAC.

Это существующий пробел архитектуры, не имеющий отношения к переходу на схему без conntrack — он не блокирует и не усложняет этот шаг. Требует отдельной оценки: динамический ARP-резолвер next-hop (как и предполагал дизайн v1) либо интеграция с тем, как EVPN-фабрика публикует MAC-привязки, чтобы control plane HPNN получал их не вручную, а из фабрики.

Обновление (шаг 5, STEP5_SUMMARY.md): первая часть этого пробела закрыта — hcd резолвит MAC членов пула динамически через обычный ARP ядра, статических BE*_MAC в таблице 21 больше нет. Это не решает вопрос EVPN-фабрики целиком: hcd резолвит MAC в пределах L2-домена, видимого узлу напрямую (те же hcif-порты, что и для health-проб), а не через интеграцию с EVPN Type-2. Если фабрика доставляет сегмент до узла как один L2-домен (что и предполагает текущая архитектура), тот же ARP-путь работает без изменений; отдельная интеграция с control plane EVPN остаётся вне рамок.

9. Итоговая сводка условий и рисков

# Пункт Тип Статус
1 Бэкенды недостижимы клиентами напрямую, только через VIP предпосылка периметра требует явного решения при внедрении
2 Правило un-DNAT сужено nw_dst=$PUB_NET техническое условие зафиксировано в предложенной схеме
3 Связность между сегментами (be↔be, разные сегменты) проверено сохраняется при условии (2)
4 Связность внутри сегмента (be↔be, один сегмент) вне объёма этой оценки не тронуто (путь на ct); при переносе на stateless требует отдельного набора исключений
5 Установленные сессии при смене состава пула изменение поведения принимаемый компромисс, требует пересмотра теста verify.sh §13
6 Исходящий трафик бэкенда с src-портом = 8080 в клиентскую сеть остаточное ограничение узкое, уже документировано в дизайне v1
7 Доставка до физического сервера по MAC (EVPN) отдельный вопрос не связан с ct; существующий пробел модели adjacency

10. Что дальше

Это оценка осуществимости, не план внедрения. Следующий шаг, если решение принято, — оформить отдельный план и итоги по конвенции репозитория (STEP4_IMPLEMENTATION_PLAN.md / STEP4_SUMMARY.md), с явной фиксацией решений по пунктам 1 и 5 таблицы выше как условий приёмки, и отдельной оценкой пункта 7 при планировании физического окружения.