Files

138 lines
12 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.
# Шаг 3 — итоги: листенер в приватном сегменте
**Дата:** 2026-08-17
**Статус:** выполнено, проверено после холодного перезапуска (86 из 86 проверок)
**План:** [STEP3_IMPLEMENTATION_PLAN.md](STEP3_IMPLEMENTATION_PLAN.md)
**Предыдущий шаг:** [STEP2_SUMMARY.md](STEP2_SUMMARY.md)
## Что сделано
Балансировщик получил листенеры в приватных сегментах — `10.20.0.100:80` и
`10.30.0.100:80` — и научился обслуживать клиента, который стоит **в одном
сегменте с бэкендами**. Исходный IP клиента сохраняется, порт транслируется
80 → 8080, в ОС виртуальных машин не настраивается ничего.
- **`br-lb` стал коммутатором приватного сегмента.** Каждая ВМ подключена
отдельным портом (`scripts/attach-segment.sh`), порты `p2`/`p3` остались
аплинками сегмента к шлюзу Docker. Появились таблицы 0 (сегментация и
обучение), 25 (FDB, broadcast и unknown flood) и 1 (классификация).
- **Обратная трансляция коммутируемого трафика — таблица 24.** Ответ бэкенда
клиенту-соседу уходит по L2 напрямую, но проходит через мост, где `ct(nat)`
возвращает источник к `VIP:80`.
- **Выдача на члена своего сегмента — без маршрутизации.** Таблица 12 при
совпадении сегмента клиента и члена меняет только MAC назначения и отдаёт кадр
в коммутацию: `dec_ttl` не выполняется, клиент и бэкенд остаются L2-соседями.
- **Пул общий с публичным листенером** — та же таблица слотов, тот же `pool_id`,
тот же дайджест `0a16713c5a9eb94d`. Демон `hcd` не изменён ни строкой.
- **Переключатель `PRIV_LISTENERS`** в `topology.env`: пусто — приватных
листенеров нет, `"p2"` — один сегмент, `"p2 p3"` — оба.
- **Клиенты сегментов** `cli2` (`10.20.0.10`) и `cli3` (`10.30.0.10`) — ВМ без
единого маршрута в таблице маршрутизации.
## Почему именно так
Клиент и бэкенд — L2-соседи, поэтому ответ бэкенда уходит клиенту напрямую.
Снять DNAT можно только там, где этот кадр виден. Остальные пути отпадали по
условиям задачи:
| Вариант | Почему отвергнут |
|---|---|
| SNAT | уничтожает сохранение IP клиента |
| DSR, VIP на `lo` бэкенда | требует настройки ОС ВМ, не транслирует порт |
| Изоляция портов сегмента | блокирует внутрисегментный трафик, неприменима к ВМ |
| Дробление подсети маршрутами `/25` | вмешательство в ОС ВМ и в адресацию заказчика |
Осталась единственная предпосылка, совпадающая с зоной влияния SDN: **порты ВМ
— порты OVS**. В целевой среде это выполняется по построению; стенд приведён к
той же модели.
## Результаты проверок
| Проверка | Результат |
|---|---|
| ВМ сегментов подключены портами OVS | 6 портов: be1, be3, cli2, be2, be4, cli3 |
| `ping 10.20.0.100` из `cli2` | отвечают ARP- и ICMP-респондеры OVS |
| Балансировка из приватного клиента | 24 запроса, задействованы все четыре члена |
| **IP клиента на бэкенде** | `client=10.20.0.10` во всех ответах |
| **Трансляция порта** | `served=10.20.0.2:8080` при листенере на 80 |
| Член в сегменте клиента | обслуживает запросы, ответ транслируется таблицей 24 |
| **TTL ответа от VIP** | 64 для членов своего сегмента, 63 — для членов другого |
| В ОС клиента нет маршрутов | `ip route` содержит только connected-запись |
| Внутрисегментная связность | `cli2 → be1` напрямую, `be1 → be3`, шлюз Docker — всё работает |
| Отказ члена под нагрузкой из `cli2` | слоты разошлись 341/342/341, 2 ошибки в окне обнаружения |
| Профиль A | egress бэкенда адресован шлюзу Docker, не маршрутизатору сегмента |
| `PRIV_LISTENERS="p2"` | листенер в priv3 выключен (счётчик drop растёт), связность цела |
| Регрессия шагов 1–2 | публичный листенер, транзит, health-check, fail-close, дренаж — без изменений |
## Главное измерение: TTL как признак пути
Ответы от `be1`/`be3` (сегмент клиента) приходят с **TTL 64**, от `be2`/`be4`
(другой сегмент) — с **TTL 63**. Это прямое доказательство того, что для соседей
по сегменту балансировщик не стал L3-хопом: он подменил MAC назначения,
оттранслировал адрес и отдал кадр коммутацией. С точки зрения ВМ ничего не
изменилось — она общается с соседом по подсети, как и раньше.
Для членов из другого сегмента работает прежний маршрутизируемый путь с
`dec_ttl`. Оба пути живут на одном пуле и одной таблице слотов, и клиент их не
различает.
## Отклонения от плана
1. **`load:0->NXM_OF_IN_PORT[]` не понадобился.** План предполагал hairpin —
выдачу в порт входа — и сброс `in_port`, чтобы OVS не подавил вывод. После
перевода каждой ВМ на собственный порт hairpin исчез: клиент и бэкенд входят
и выходят через разные порты. Риск снят самой архитектурой.
2. **Отдельная сеть для egress не потребовалась.** План предусматривал служебную
docker-сеть под default-маршрут. Оказалось достаточно оставить `p2`/`p3`
аплинками сегмента: бэкенд адресует кадр MAC-у шлюза Docker, мост его
коммутирует, и профиль A сохраняется в прежнем виде.
3. **Появилась таблица 1.** В плане классификация оставалась в таблице 0. Но
таблица 0 занята определением сегмента и обучением (два действия `learn` на
каждый порт), поэтому классификация вынесена отдельно — иначе правила
пришлось бы дублировать на каждый порт сегмента.
4. **`make` больше не требует `sudo`.** Под root его в системе может не быть;
цели подготовки хоста теперь подставляют `sudo` только при запуске не от root.
5. **Проверки 7 и 9 в `verify.sh` переформулированы.** Обе слушали аплинк `p2`,
через который health-пробы и egress больше не проходят: пробы идут
коммутацией на порт ВМ, а признаком профиля A стал MAC назначения (шлюз
Docker, а не маршрутизатор сегмента), а не отсутствие трафика на `p2`.
## Известные ограничения
- **Предпосылка архитектуры:** порты ВМ должны быть портами OVS. Если между
клиентом и бэкендом окажется коммутатор, который SDN не контролирует, кейс не
решается ничем, кроме SNAT.
- **Хрупкость стенда:** veth, созданные `attach-segment.sh`, исчезают при
пересоздании контейнера — нужен `make attach`. В целевой среде порты создаёт
гипервизор.
- **Весь внутрисегментный трафик проходит через `br-lb`** — пропускная
способность узла становится пропускной способностью сегмента. Замер k6 не
выполнен: в системе нет клиента k6; цель для сравнения —
`http://10.20.0.100/` против `http://192.168.5.21/`.
- **Обратная трансляция опирается на `ct`** — как и прежде. Stateless-вариант
(`nw_src=member, tp_src=8080 → VIP:80`) остаётся заделом: в сегменте он
потребует аккуратного исключения трафика, который бэкенд сам инициирует с
порта 8080.
- **Unicast ARP внутри сегмента рассылается флудом**, если MAC ещё не выучен —
обычное поведение обучающегося коммутатора, но без таймера старения записей
короче 300 с.
- **Приватный листенер только TCP и только IPv4**, как и публичный. UDP- и
L3-листенеров нет.
- Кворум, control plane в OVSDB, ICMP-ошибки и фрагменты — по-прежнему вне рамок.
## Задел на шаг 4
1. **Stateless-датапас** для обоих путей: явный un-DNAT вместо `ct(nat)`.
На коммутируемом пути это снимет последнюю зависимость от состояния.
2. **Control plane:** модель `Load_Balancer → Listener → Pool → Member` в OVSDB
вместо `topology.env`. Сейчас листенер, сегмент и состав пула описаны в трёх
местах, а добавление ВМ требует правки `topology.env`, `hc-config.sh` и
запуска `attach-segment.sh`.
3. **Старение FDB и защита от переполнения** — сейчас записи живут 300 с без
ограничения на число MAC в сегменте.
4. Второй узел LB: кворум наблюдений, генерации пулов, сравнение дайджестов.