137 lines
12 KiB
Markdown
137 lines
12 KiB
Markdown
# Шаг 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: кворум наблюдений, генерации пулов, сравнение дайджестов.
|