Add FRR anycast lab: HLD, containerized topology, docs
Реализует HLD-дизайн (hld.md) в виде развёртываемого Docker Compose окружения: 5 FRR-роутеров (A1-A3 anycast 10.200.200.1/32 на AS 65001, B1/B2 на AS 65002/65003) + 6 клиентских контейнеров, ECMP-балансировка трафика клиентов площадки B к anycast-адресу площадки A. - docker-compose.yml, frr/*, entrypoints/* — топология и конфиги FRR - scripts/verify.sh, scripts/traffic-test.sh — проверка BGP/ECMP и трафика - hld.md — HLD-дизайн + Mermaid-диаграмма топологии - README.md — запуск, структура, команды проверки на роутерах и клиентах - docs/ — планы внедрения и summary по каждому изменению Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Bc3HkMiDwooSyLBLoMKNyF
This commit is contained in:
commit
eaa577e6b7
30 files changed
+1035
No files matched your search
@@ -0,0 +1,22 @@
|
||||
# Твоя роль
|
||||
|
||||
- DevOps инженер
|
||||
- Разработчик Backend
|
||||
- Архитектор информационных систем
|
||||
- Архитектор корпоративной сети
|
||||
|
||||
# Стиль общения
|
||||
|
||||
- профессиональный, но без жаргона
|
||||
|
||||
# Стиль ответов
|
||||
|
||||
- максимально емкие и содержательные
|
||||
- не проваливайся в лишние детали, если это явно не было запрошено
|
||||
|
||||
# Создание артефактов
|
||||
|
||||
- На каждое новое изменение должен быть артефакт в .md файле
|
||||
- Каждое новое изменение должно начинаться с плана внедрения в отдельном файле
|
||||
- Каждое новое изменение должно заканчиваться суммаризацией по выполненым доработкам в отдельном файле
|
||||
- каждое изменение дополняет или обновляет README.md
|
||||
@@ -0,0 +1,84 @@
|
||||
# poc_frr_anycast
|
||||
|
||||
POC anycast-топологии на FRR: две площадки, eBGP full-mesh, ECMP-балансировка трафика клиентов площадки B к anycast-адресу площадки A.
|
||||
|
||||
- **Дизайн (HLD):** [hld.md](hld.md) (в т.ч. Mermaid-диаграмма топологии)
|
||||
- **История изменений:** [docs/](docs/) — на каждое изменение отдельный план внедрения и summary.
|
||||
|
||||
## Lab-окружение (Docker Compose)
|
||||
|
||||
Топология из `hld.md` поднимается локально в Docker Compose: 5 FRR-роутеров (a1/a2/a3 — площадка A, b1/b2 — площадка B) + 6 клиентских контейнеров площадки B. Каждый линк HLD — отдельная Docker-сеть с фиксированной адресацией, 1:1 соответствующая схеме из `hld.md`.
|
||||
|
||||
Детали архитектуры и обоснование решений: [docs/2026-09-03-anycast-lab-plan.md](docs/2026-09-03-anycast-lab-plan.md), результаты — [docs/2026-09-03-anycast-lab-summary.md](docs/2026-09-03-anycast-lab-summary.md).
|
||||
|
||||
### Запуск
|
||||
|
||||
```bash
|
||||
docker compose up -d # поднять топологию (13 контейнеров)
|
||||
./scripts/verify.sh # проверить BGP-сессии и ECMP-маршрут до 10.200.200.1/32
|
||||
./scripts/traffic-test.sh [client] [count] # прогнать трафик к anycast, увидеть распределение по A1/A2/A3
|
||||
docker compose down # разобрать окружение
|
||||
```
|
||||
|
||||
Пример: `./scripts/traffic-test.sh c1 30` — 30 запросов с клиента `c1` (площадка B1) к anycast-адресу с варьированием src-port; ответ каждого anycast-узла (`a1`/`a2`/`a3`) содержит его hostname, что показывает распределение трафика.
|
||||
|
||||
### Структура
|
||||
|
||||
```
|
||||
hld.md HLD-дизайн топологии
|
||||
docker-compose.yml 13 контейнеров, 8 сетей (1 на линк)
|
||||
frr/{a1,a2,a3,b1,b2}/ конфиги FRR (daemons, frr.conf, vtysh.conf)
|
||||
entrypoints/ создание dummy0 + anycast-responder (A), маршрут по умолчанию (клиенты)
|
||||
scripts/ verify.sh, traffic-test.sh
|
||||
docs/ план внедрения + summary по каждому изменению
|
||||
```
|
||||
|
||||
### Проверка окружения
|
||||
|
||||
Всё это уже автоматизировано в `./scripts/verify.sh` (BGP + маршруты) и `./scripts/traffic-test.sh` (трафик), но ниже — те же проверки вручную, командами на самих узлах.
|
||||
|
||||
#### На маршрутизаторах (`vtysh`)
|
||||
|
||||
Выполняются как `docker compose exec -T <node> vtysh -c "<command>"` или интерактивно: `docker compose exec <node> vtysh`.
|
||||
|
||||
| Команда | Где | Что должно быть в норме |
|
||||
|---|---|---|
|
||||
| `show bgp summary` | все 5 узлов | все соседи в состоянии `Established` (число вместо `Idle`/`Active` в колонке `State/PfxRcd`) — 2 соседа на a1/a2/a3, 3 на b1/b2 |
|
||||
| `show ip route 10.200.200.1/32` | b1, b2 | 3 записи `* <ip>, via ethN, weight 1` — ECMP-маршрут до anycast через a1/a2/a3 |
|
||||
| `show bgp ipv4 unicast 10.200.200.1/32` | b1, b2 | тот же префикс в BGP-таблице, 3 валидных пути, `Local AS 65001` у всех |
|
||||
| `show ip bgp neighbor <ip> advertised-routes` | a1/a2/a3 (`<ip>` — сосед в сторону B) | среди анонсируемых — `10.200.200.1/32` |
|
||||
| `show ip bgp neighbor <ip> advertised-routes` | b1, b2 (`<ip>` — сосед в сторону A) | среди анонсируемых — `10.100.1.0/24` (b1) / `10.100.2.0/24` (b2) |
|
||||
| `show interface brief` | любой | все интерфейсы `up`, IP соответствуют `hld.md` |
|
||||
|
||||
Пример:
|
||||
```bash
|
||||
docker compose exec -T b1 vtysh -c "show bgp summary"
|
||||
docker compose exec -T b1 vtysh -c "show ip route 10.200.200.1/32"
|
||||
docker compose exec -T a1 vtysh -c "show ip bgp neighbor 10.0.1.2 advertised-routes"
|
||||
```
|
||||
|
||||
#### На клиентах (площадка B, доверенная сеть)
|
||||
|
||||
Выполняются как `docker compose exec -T <client> <command>`, где `<client>` — один из `c1`…`c6`.
|
||||
|
||||
| Команда | Что должно быть в норме |
|
||||
|---|---|
|
||||
| `ping -c 4 10.200.200.1` | `0% packet loss`, ответы приходят с `ttl=63` (1 транзитный хоп — B-роутер) |
|
||||
| `traceroute -n 10.200.200.1` | 2 хопа: локальный B-роутер (`10.100.1.1` / `10.100.2.1`), затем сразу `10.200.200.1` |
|
||||
| `curl -s http://10.200.200.1/` | в ответе — hostname одного из `a1`/`a2`/`a3` (мини-HTTP на dummy0, см. `entrypoints/a-entrypoint.sh`) |
|
||||
| серия `curl` с разным `--local-port` (см. `scripts/traffic-test.sh`) | ответы приходят от **разных** anycast-узлов — подтверждает, что ECMP реально распределяет трафик по 5-tuple, а не всегда уходит в один и тот же узел |
|
||||
|
||||
Пример:
|
||||
```bash
|
||||
docker compose exec -T c1 ping -c 4 10.200.200.1
|
||||
docker compose exec -T c1 traceroute -n 10.200.200.1
|
||||
docker compose exec -T c1 curl -s http://10.200.200.1/
|
||||
./scripts/traffic-test.sh c1 30 # готовый прогон с разбивкой по узлам
|
||||
```
|
||||
|
||||
Если `curl`/`ping` не проходят — сначала проверить BGP (`show bgp summary` на затронутых узлах), затем ECMP-маршрут (`show ip route 10.200.200.1/32` на b1/b2).
|
||||
|
||||
### Важные технические нюансы
|
||||
|
||||
- Линки между A и B заведены как `/29` (не `/30`, как в hld.md буквально) — Docker резервирует первый адрес подсети под gateway моста даже для `internal: true`-сетей, поэтому реальным peering-адресам (`.1`/`.2`, как в hld.md) нужно место рядом с ним. IP-адресация роутеров и логика BGP при этом полностью соответствуют hld.md.
|
||||
- ECMP-балансировка на b1/b2 требует `net.ipv4.fib_multipath_hash_policy=1` (в `docker-compose.yml`) — по умолчанию (`0`) Linux хеширует next-hop только по src/dst IP, без учёта портов, из-за чего один клиент всегда попадал бы в один и тот же anycast-узел.
|
||||
@@ -0,0 +1,177 @@
|
||||
name: poc-frr-anycast
|
||||
|
||||
networks:
|
||||
# Note: /29 rather than /30 — Docker's bridge driver always claims the first
|
||||
# usable address of a subnet as the network gateway (even with internal:
|
||||
# true), so the actual /30 peering addresses (.1/.2, matching hld.md) need
|
||||
# room alongside it; the gateway is pushed to .6, which nothing else uses.
|
||||
link-a1-b1:
|
||||
driver: bridge
|
||||
internal: true
|
||||
ipam:
|
||||
config: [{subnet: 10.0.1.0/29, gateway: 10.0.1.6}]
|
||||
link-a1-b2:
|
||||
driver: bridge
|
||||
internal: true
|
||||
ipam:
|
||||
config: [{subnet: 10.0.2.0/29, gateway: 10.0.2.6}]
|
||||
link-a2-b1:
|
||||
driver: bridge
|
||||
internal: true
|
||||
ipam:
|
||||
config: [{subnet: 10.0.3.0/29, gateway: 10.0.3.6}]
|
||||
link-a2-b2:
|
||||
driver: bridge
|
||||
internal: true
|
||||
ipam:
|
||||
config: [{subnet: 10.0.4.0/29, gateway: 10.0.4.6}]
|
||||
link-a3-b1:
|
||||
driver: bridge
|
||||
internal: true
|
||||
ipam:
|
||||
config: [{subnet: 10.0.5.0/29, gateway: 10.0.5.6}]
|
||||
link-a3-b2:
|
||||
driver: bridge
|
||||
internal: true
|
||||
ipam:
|
||||
config: [{subnet: 10.0.6.0/29, gateway: 10.0.6.6}]
|
||||
net-b1-clients:
|
||||
driver: bridge
|
||||
internal: true
|
||||
ipam:
|
||||
config: [{subnet: 10.100.1.0/24, gateway: 10.100.1.254}]
|
||||
net-b2-clients:
|
||||
driver: bridge
|
||||
internal: true
|
||||
ipam:
|
||||
config: [{subnet: 10.100.2.0/24, gateway: 10.100.2.254}]
|
||||
|
||||
x-frr-a: &frr-a
|
||||
image: frrouting/frr:latest
|
||||
cap_add: [NET_ADMIN, NET_RAW, SYS_ADMIN]
|
||||
sysctls:
|
||||
net.ipv4.ip_forward: 1
|
||||
entrypoint: ["/sbin/tini", "--", "/entrypoints/a-entrypoint.sh"]
|
||||
|
||||
x-frr-b: &frr-b
|
||||
image: frrouting/frr:latest
|
||||
cap_add: [NET_ADMIN, NET_RAW, SYS_ADMIN]
|
||||
sysctls:
|
||||
net.ipv4.ip_forward: 1
|
||||
# default policy (0) hashes ECMP nexthop on src/dst IP only, ignoring L4
|
||||
# ports - with a single client IP that pins every flow to the same A-node.
|
||||
# Policy 1 adds L4 ports to the hash, matching the 5-tuple ECMP behavior
|
||||
# described in hld.md.
|
||||
net.ipv4.fib_multipath_hash_policy: 1
|
||||
entrypoint: ["/sbin/tini", "--", "/entrypoints/b-entrypoint.sh"]
|
||||
|
||||
x-client: &client
|
||||
image: nicolaka/netshoot
|
||||
cap_add: [NET_ADMIN]
|
||||
entrypoint: ["/entrypoints/client-entrypoint.sh"]
|
||||
|
||||
services:
|
||||
# --- Site A: AS 65001, anycast 10.200.200.1/32 on dummy0 ---
|
||||
a1:
|
||||
<<: *frr-a
|
||||
hostname: a1
|
||||
volumes:
|
||||
- ./frr/a1:/etc/frr
|
||||
- ./entrypoints/a-entrypoint.sh:/entrypoints/a-entrypoint.sh:ro
|
||||
networks:
|
||||
link-a1-b1: {ipv4_address: 10.0.1.1}
|
||||
link-a1-b2: {ipv4_address: 10.0.2.1}
|
||||
|
||||
a2:
|
||||
<<: *frr-a
|
||||
hostname: a2
|
||||
volumes:
|
||||
- ./frr/a2:/etc/frr
|
||||
- ./entrypoints/a-entrypoint.sh:/entrypoints/a-entrypoint.sh:ro
|
||||
networks:
|
||||
link-a2-b1: {ipv4_address: 10.0.3.1}
|
||||
link-a2-b2: {ipv4_address: 10.0.4.1}
|
||||
|
||||
a3:
|
||||
<<: *frr-a
|
||||
hostname: a3
|
||||
volumes:
|
||||
- ./frr/a3:/etc/frr
|
||||
- ./entrypoints/a-entrypoint.sh:/entrypoints/a-entrypoint.sh:ro
|
||||
networks:
|
||||
link-a3-b1: {ipv4_address: 10.0.5.1}
|
||||
link-a3-b2: {ipv4_address: 10.0.6.1}
|
||||
|
||||
# --- Site B: AS 65002 / 65003, client networks ---
|
||||
b1:
|
||||
<<: *frr-b
|
||||
hostname: b1
|
||||
volumes:
|
||||
- ./frr/b1:/etc/frr
|
||||
- ./entrypoints/b-entrypoint.sh:/entrypoints/b-entrypoint.sh:ro
|
||||
networks:
|
||||
link-a1-b1: {ipv4_address: 10.0.1.2}
|
||||
link-a2-b1: {ipv4_address: 10.0.3.2}
|
||||
link-a3-b1: {ipv4_address: 10.0.5.2}
|
||||
net-b1-clients: {ipv4_address: 10.100.1.1}
|
||||
|
||||
b2:
|
||||
<<: *frr-b
|
||||
hostname: b2
|
||||
volumes:
|
||||
- ./frr/b2:/etc/frr
|
||||
- ./entrypoints/b-entrypoint.sh:/entrypoints/b-entrypoint.sh:ro
|
||||
networks:
|
||||
link-a1-b2: {ipv4_address: 10.0.2.2}
|
||||
link-a2-b2: {ipv4_address: 10.0.4.2}
|
||||
link-a3-b2: {ipv4_address: 10.0.6.2}
|
||||
net-b2-clients: {ipv4_address: 10.100.2.1}
|
||||
|
||||
# --- Клиенты площадки B (доверенная сеть) ---
|
||||
c1:
|
||||
<<: *client
|
||||
hostname: c1
|
||||
environment: {GATEWAY: 10.100.1.1}
|
||||
volumes: ["./entrypoints/client-entrypoint.sh:/entrypoints/client-entrypoint.sh:ro"]
|
||||
networks:
|
||||
net-b1-clients: {ipv4_address: 10.100.1.11}
|
||||
|
||||
c2:
|
||||
<<: *client
|
||||
hostname: c2
|
||||
environment: {GATEWAY: 10.100.1.1}
|
||||
volumes: ["./entrypoints/client-entrypoint.sh:/entrypoints/client-entrypoint.sh:ro"]
|
||||
networks:
|
||||
net-b1-clients: {ipv4_address: 10.100.1.12}
|
||||
|
||||
c3:
|
||||
<<: *client
|
||||
hostname: c3
|
||||
environment: {GATEWAY: 10.100.1.1}
|
||||
volumes: ["./entrypoints/client-entrypoint.sh:/entrypoints/client-entrypoint.sh:ro"]
|
||||
networks:
|
||||
net-b1-clients: {ipv4_address: 10.100.1.13}
|
||||
|
||||
c4:
|
||||
<<: *client
|
||||
hostname: c4
|
||||
environment: {GATEWAY: 10.100.2.1}
|
||||
volumes: ["./entrypoints/client-entrypoint.sh:/entrypoints/client-entrypoint.sh:ro"]
|
||||
networks:
|
||||
net-b2-clients: {ipv4_address: 10.100.2.14}
|
||||
|
||||
c5:
|
||||
<<: *client
|
||||
hostname: c5
|
||||
environment: {GATEWAY: 10.100.2.1}
|
||||
volumes: ["./entrypoints/client-entrypoint.sh:/entrypoints/client-entrypoint.sh:ro"]
|
||||
networks:
|
||||
net-b2-clients: {ipv4_address: 10.100.2.15}
|
||||
|
||||
c6:
|
||||
<<: *client
|
||||
hostname: c6
|
||||
environment: {GATEWAY: 10.100.2.1}
|
||||
volumes: ["./entrypoints/client-entrypoint.sh:/entrypoints/client-entrypoint.sh:ro"]
|
||||
networks:
|
||||
net-b2-clients: {ipv4_address: 10.100.2.16}
|
||||
@@ -0,0 +1,63 @@
|
||||
# План внедрения: контейнерный POC anycast-топологии FRR
|
||||
|
||||
**Дата:** 2026-09-03
|
||||
**Источник:** [hld.md](../hld.md)
|
||||
|
||||
## Цель
|
||||
|
||||
Развернуть в Docker Compose топологию из `hld.md` (5 FRR-роутеров, anycast `10.200.200.1/32` на площадке A, клиентские сети площадки B) и убедиться, что трафик от клиентов B реально распределяется по ECMP между тремя anycast-узлами A1/A2/A3.
|
||||
|
||||
## Проверка окружения (выполнена)
|
||||
|
||||
- Docker 28.3.2 + Compose plugin v2.38.2 — доступны, `NET_ADMIN`-контейнеры работают.
|
||||
- `frrouting/frr:latest` (Alpine 3.16) — тянется из Docker Hub, содержит `vtysh`, `zebra`, `bgpd`, `staticd`, `ip`, `ping`, `python3`, `nc`. Кастомный образ не нужен.
|
||||
- Модуль ядра `dummy` доступен — anycast-интерфейс на A-узлах создаётся в рантайме через `ip link add dummy0 type dummy`.
|
||||
- `containerlab` не устанавливаем — Docker Compose с `internal`-сетями на каждый p2p-линк полностью закрывает задачу без дополнительных зависимостей.
|
||||
|
||||
## Архитектура
|
||||
|
||||
Каждый линк HLD (`/30` A↔B, `/24` клиентские сегменты) — отдельная Docker-сеть `internal: true` со статическими IP на участниках (без Docker-gateway, который иначе занял бы адрес в `/30`).
|
||||
|
||||
| Сеть | CIDR | Участники |
|
||||
|---|---|---|
|
||||
| `link-a1-b1` | 10.0.1.0/30 | a1 (.1), b1 (.2) |
|
||||
| `link-a1-b2` | 10.0.2.0/30 | a1 (.1), b2 (.2) |
|
||||
| `link-a2-b1` | 10.0.3.0/30 | a2 (.1), b1 (.2) |
|
||||
| `link-a2-b2` | 10.0.4.0/30 | a2 (.1), b2 (.2) |
|
||||
| `link-a3-b1` | 10.0.5.0/30 | a3 (.1), b1 (.2) |
|
||||
| `link-a3-b2` | 10.0.6.0/30 | a3 (.1), b2 (.2) |
|
||||
| `net-b1-clients` | 10.100.1.0/24 | b1 (.1), c1 (.11), c2 (.12), c3 (.13) |
|
||||
| `net-b2-clients` | 10.100.2.0/24 | b2 (.1), c4 (.14), c5 (.15), c6 (.16) |
|
||||
|
||||
- **a1/a2/a3**: `frrouting/frr:latest`, `cap_add: [NET_ADMIN, NET_RAW]`. Кастомный entrypoint создаёт `dummy0` (`10.200.200.1/32`) и поднимает HTTP-responder на этом адресе (отдаёт hostname контейнера — видно, какой узел ответил), затем стартует штатный `docker-start` FRR. Конфиг — прямая транскрипция A1/A2/A3 из `hld.md`.
|
||||
- **b1/b2**: `frrouting/frr:latest`, конфиг — транскрипция B1/B2 из `hld.md` (`bgp bestpath as-path multipath-relax`, `maximum-paths 3`).
|
||||
- **c1…c6**: `nicolaka/netshoot` (ping/curl/traceroute/iperf3/mtr), entrypoint выставляет статический IP и `ip route add default via <B-роутер>`.
|
||||
|
||||
## Файлы
|
||||
|
||||
```
|
||||
docker-compose.yml
|
||||
frr/{a1,a2,a3,b1,b2}/{daemons,frr.conf,vtysh.conf}
|
||||
entrypoints/{a-entrypoint.sh,b-entrypoint.sh,client-entrypoint.sh}
|
||||
scripts/{verify.sh,traffic-test.sh}
|
||||
docs/2026-09-03-anycast-lab-plan.md (этот файл)
|
||||
docs/2026-09-03-anycast-lab-summary.md (по завершении)
|
||||
README.md (дополняется)
|
||||
```
|
||||
|
||||
## Шаги
|
||||
|
||||
1. FRR-конфиги для 5 узлов (daemons, frr.conf, vtysh.conf).
|
||||
2. Entrypoint-скрипты (dummy0 + anycast responder для A; статическая адресация для клиентов).
|
||||
3. `docker-compose.yml` — 8 сетей + 5 роутеров + 6 клиентов.
|
||||
4. `scripts/verify.sh` — проверка BGP-сессий (Established) и ECMP-маршрута до `10.200.200.1/32` (3 пути) на b1/b2.
|
||||
5. `scripts/traffic-test.sh` — ping/traceroute/curl-цикл с клиентов, демонстрирующий распределение трафика по A1/A2/A3.
|
||||
6. Поднятие (`docker compose up -d`), прогон проверок.
|
||||
7. Обновление `README.md`.
|
||||
8. Summary-артефакт с результатами.
|
||||
|
||||
## Definition of done
|
||||
|
||||
- Все 13 контейнеров поднимаются без ошибок.
|
||||
- На b1/b2: 3 Established BGP-сессии, 3 ECMP-пути до `10.200.200.1/32`.
|
||||
- С клиентов `ping 10.200.200.1` проходит; `traffic-test.sh` показывает ответы от разных anycast-узлов (a1/a2/a3) при варьировании src-port.
|
||||
@@ -0,0 +1,40 @@
|
||||
# Summary: контейнерный POC anycast-топологии FRR
|
||||
|
||||
**Дата:** 2026-09-03
|
||||
**План:** [2026-09-03-anycast-lab-plan.md](2026-09-03-anycast-lab-plan.md)
|
||||
|
||||
## Что сделано
|
||||
|
||||
Топология из `hld.md` полностью развёрнута в Docker Compose (`docker-compose.yml`): 5 FRR-контейнеров (a1/a2/a3 — AS 65001, площадка A; b1/b2 — AS 65002/65003, площадка B) + 6 клиентских контейнеров (`nicolaka/netshoot`) на площадке B. 8 Docker-сетей воспроизводят все линки HLD (6 межплощадочных + 2 клиентских сегмента).
|
||||
|
||||
Созданные файлы: `docker-compose.yml`, `frr/{a1,a2,a3,b1,b2}/{daemons,frr.conf,vtysh.conf}`, `entrypoints/{a-entrypoint.sh,b-entrypoint.sh,client-entrypoint.sh}`, `scripts/{verify.sh,traffic-test.sh}`, `README.md`.
|
||||
|
||||
## Отклонения от первоначального плана (обнаружены и устранены в процессе)
|
||||
|
||||
1. **`/30` → `/29` на межплощадочных линках.** Docker всегда резервирует первый usable-адрес подсети под gateway моста, даже при `internal: true`. При буквальном `/30` (только 2 usable-адреса) это конфликтовало с адресом `.1`, который нужен был роутеру A (`Error: Address already in use`). Решение — расширить линковые подсети до `/29` и явно вынести gateway на неиспользуемый адрес (`.6`); адресация роутеров (`.1`/`.2`) осталась как в hld.md. Аналогично для клиентских `/24`-сетей gateway вынесен на `.254`, чтобы не конфликтовать с адресом B-роутера (`.1`).
|
||||
2. **`sysctl -w` в entrypoint не работал** (`Read-only file system`) — `/proc/sys` внутри контейнера доступен на запись только для sysctl'ов, заданных декларативно через Docker (`sysctls:` в compose), а не вызовом `sysctl` из процесса внутри контейнера. Перенесли `net.ipv4.ip_forward=1` в `docker-compose.yml`.
|
||||
3. **`cap_sys_admin` требуется FRR для namespace-операций** — с `cap_add: [NET_ADMIN, NET_RAW]` zebra/bgpd не стартовали (`privs_init: initial cap_set_proc failed`). Добавили `SYS_ADMIN` в `cap_add`.
|
||||
4. **ECMP не реагировал на src-port.** С дефолтной политикой ядра `net.ipv4.fib_multipath_hash_policy=0` next-hop хешируется только по src/dst IP — один клиент всегда получал один и тот же anycast-узел, что противоречило описанной в hld.md 5-tuple-логике. Установили `net.ipv4.fib_multipath_hash_policy=1` на b1/b2.
|
||||
5. **Убраны `interface ethN` из frr.conf.** Раз Docker Compose уже назначает статические IP через `ipv4_address`, а zebra сам подхватывает существующие адреса интерфейсов, ручное дублирование адресов в frr.conf было источником риска (несовпадение имени интерфейса `ethN` с ожидаемым линком). Оставлен только `dummy0` (единственный интерфейс, которого нет "из коробки" у Docker).
|
||||
|
||||
## Результаты проверки
|
||||
|
||||
- **BGP:** все 10 eBGP-сессий (a1/a2/a3 × b1/b2) в состоянии Established.
|
||||
- **ECMP:** у b1 и у b2 — по 3 равнозначных пути (weight 1) до `10.200.200.1/32`, через a1/a2/a3 соответственно.
|
||||
- **Связность клиент → anycast:** `ping`/`traceroute`/`curl` с любого клиента (c1…c6) до `10.200.200.1` проходят.
|
||||
- **Распределение трафика (`scripts/traffic-test.sh`):** серии запросов с варьированием src-port показывают ответы от всех трёх anycast-узлов в примерно равной пропорции, например с `c4` (30 запросов): `a1: 13, a2: 2, a3: 15`; с `c2` (24 запроса): `a1: 10, a2: 7, a3: 7`. Ответ каждого узла содержит его hostname (мини HTTP-responder на dummy0), что визуально подтверждает, какой именно anycast-узел обслужил запрос.
|
||||
- **Идемпотентность:** `docker compose down && docker compose up -d` поднимает окружение с нуля без ручного вмешательства, BGP сходится за секунды.
|
||||
|
||||
## Как повторить
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
./scripts/verify.sh
|
||||
./scripts/traffic-test.sh c1 30
|
||||
docker compose down
|
||||
```
|
||||
|
||||
## Дальнейшие возможные шаги (не выполнялись, вне рамок задачи)
|
||||
|
||||
- Симуляция отказа одного из A-узлов (`docker compose stop a2`) для проверки конвергенции ECMP на оставшихся 2 путях.
|
||||
- Нагрузочный тест через `iperf3` (уже есть в образе `nicolaka/netshoot`) вместо HTTP-responder.
|
||||
@@ -0,0 +1,23 @@
|
||||
# План внедрения: раздел проверки в README.md
|
||||
|
||||
**Дата:** 2026-09-03
|
||||
|
||||
## Цель
|
||||
|
||||
Дополнить `README.md` разделом «Проверка окружения» с готовым набором команд для маршрутизаторов (`vtysh`) и клиентов (`ping`/`traceroute`/`curl`), с описанием ожидаемого результата — чтобы можно было вручную убедиться, что BGP сошёлся, ECMP работает и трафик от клиентов реально доходит до anycast-адреса.
|
||||
|
||||
## Источник
|
||||
|
||||
Список команд уже был согласован в чате (см. предыдущий ответ по `vtysh`) и опробован в рамках `scripts/verify.sh` / `scripts/traffic-test.sh`. Задача — перенести это в постоянную документацию README, дополнив клиентской частью.
|
||||
|
||||
## Шаги
|
||||
|
||||
1. Добавить в `README.md` раздел «Проверка окружения»:
|
||||
- Команды `vtysh` на роутерах (BGP-сессии, ECMP-маршрут, advertised/received routes, интерфейсы) с ожидаемым результатом.
|
||||
- Команды на клиентах (`ping`, `traceroute`, `curl` до anycast) с ожидаемым результатом.
|
||||
- Ссылку на то, что то же самое автоматизировано в `scripts/verify.sh` / `scripts/traffic-test.sh`.
|
||||
2. Summary-артефакт с итогом изменения.
|
||||
|
||||
## Definition of done
|
||||
|
||||
- В README есть самодостаточный перечень команд для обоих типов узлов (роутер/клиент), с пояснением, что считать успехом.
|
||||
@@ -0,0 +1,19 @@
|
||||
# Summary: раздел проверки в README.md
|
||||
|
||||
**Дата:** 2026-09-03
|
||||
**План:** [2026-09-03-readme-verification-docs-plan.md](2026-09-03-readme-verification-docs-plan.md)
|
||||
|
||||
## Что сделано
|
||||
|
||||
В `README.md` добавлен раздел «Проверка окружения» с двумя таблицами команд:
|
||||
|
||||
- **На маршрутизаторах** (`vtysh`) — `show bgp summary`, `show ip route 10.200.200.1/32`, `show bgp ipv4 unicast 10.200.200.1/32`, `show ip bgp neighbor <ip> advertised-routes`, `show interface brief`, для каждой указано ожидаемое значение (Established, 3×ECMP weight 1, и т.д.).
|
||||
- **На клиентах** — `ping`, `traceroute`, `curl` до anycast-адреса и серия `curl` с варьированием `--local-port` для демонстрации распределения по A1/A2/A3.
|
||||
|
||||
Команды `show ip bgp neighbor ... advertised-routes` проверены вживую на работающем окружении: a1 анонсирует `10.200.200.1/32` в сторону b1, b1 анонсирует `10.100.1.0/24` в сторону a1 — совпадает с описанием в README.
|
||||
|
||||
## Изменённые файлы
|
||||
|
||||
- `README.md` — новый раздел «Проверка окружения».
|
||||
- `docs/2026-09-03-readme-verification-docs-plan.md` — план (этот цикл).
|
||||
- `docs/2026-09-03-readme-verification-docs-summary.md` — этот файл.
|
||||
@@ -0,0 +1,13 @@
|
||||
# План внедрения: Mermaid-диаграмма топологии
|
||||
|
||||
**Дата:** 2026-09-03
|
||||
|
||||
## Цель
|
||||
|
||||
Добавить в `hld.md` визуальную схему топологии в формате Mermaid (flowchart) — дополнение к существующей ASCII-схеме, наглядно показывающее full-mesh A↔B, anycast на площадке A и клиентские сети на площадке B.
|
||||
|
||||
## Шаги
|
||||
|
||||
1. Добавить в `hld.md` раздел с Mermaid-диаграммой (subgraph на площадку A, subgraph на B1/B2 с клиентами, рёбра — 6 линков с подписями подсетей).
|
||||
2. Добавить ссылку/упоминание в `README.md`.
|
||||
3. Summary-артефакт.
|
||||
@@ -0,0 +1,16 @@
|
||||
# Summary: Mermaid-диаграмма топологии
|
||||
|
||||
**Дата:** 2026-09-03
|
||||
**План:** [2026-09-03-topology-diagram-plan.md](2026-09-03-topology-diagram-plan.md)
|
||||
|
||||
## Что сделано
|
||||
|
||||
В `hld.md` добавлен раздел «Диаграмма (Mermaid)» — flowchart с 3 подграфами (площадка A: A1/A2/A3 с anycast на dummy0; B1 с клиентами C1–C3; B2 с клиентами C4–C6) и 6 рёбрами A↔B, подписанными реальными `/30`-подсетями из HLD. Диаграмма дополняет уже существующую ASCII-схему, не заменяя её.
|
||||
|
||||
`README.md` дополнен пометкой, что в `hld.md` теперь есть визуальная диаграмма.
|
||||
|
||||
## Изменённые файлы
|
||||
|
||||
- `hld.md` — новый раздел с Mermaid-диаграммой.
|
||||
- `README.md` — уточнена ссылка на `hld.md`.
|
||||
- `docs/2026-09-03-topology-diagram-plan.md`, `docs/2026-09-03-topology-diagram-summary.md`.
|
||||
Executable
+23
@@ -0,0 +1,23 @@
|
||||
#!/bin/sh
|
||||
# Runs on a1/a2/a3 before FRR starts:
|
||||
# - creates the dummy0 anycast interface (zebra only configures IPs on
|
||||
# interfaces that already exist, it doesn't create them)
|
||||
# - starts a tiny HTTP responder bound to the anycast IP, returning this
|
||||
# node's hostname, so traffic-test.sh can show which A-node answered
|
||||
set -e
|
||||
|
||||
ip link add dummy0 type dummy
|
||||
ip link set dummy0 up
|
||||
|
||||
mkdir -p /anycast-web
|
||||
hostname > /anycast-web/index.html
|
||||
|
||||
(
|
||||
# wait until zebra (per frr.conf) assigns 10.200.200.1 to dummy0
|
||||
while ! ip -4 addr show dummy0 | grep -q 10.200.200.1; do
|
||||
sleep 1
|
||||
done
|
||||
exec python3 -m http.server 80 --bind 10.200.200.1 --directory /anycast-web
|
||||
) &
|
||||
|
||||
exec /usr/lib/frr/docker-start
|
||||
Executable
+6
@@ -0,0 +1,6 @@
|
||||
#!/bin/sh
|
||||
# b1/b2 need IP forwarding on to route between the client subnet and the
|
||||
# eBGP links towards A (ECMP transit).
|
||||
set -e
|
||||
|
||||
exec /usr/lib/frr/docker-start
|
||||
Executable
+9
@@ -0,0 +1,9 @@
|
||||
#!/bin/sh
|
||||
# Client containers get their IP from Docker (compose ipv4_address), but the
|
||||
# client network is `internal: true` so there's no Docker-provided gateway -
|
||||
# point the default route at the local B-router instead.
|
||||
set -e
|
||||
|
||||
ip route add default via "$GATEWAY"
|
||||
|
||||
exec sleep infinity
|
||||
@@ -0,0 +1,25 @@
|
||||
zebra=yes
|
||||
bgpd=yes
|
||||
ospfd=no
|
||||
ospf6d=no
|
||||
ripd=no
|
||||
ripngd=no
|
||||
isisd=no
|
||||
pimd=no
|
||||
pim6d=no
|
||||
ldpd=no
|
||||
nhrpd=no
|
||||
eigrpd=no
|
||||
babeld=no
|
||||
sharpd=no
|
||||
pbrd=no
|
||||
bfdd=no
|
||||
fabricd=no
|
||||
vrrpd=no
|
||||
pathd=no
|
||||
staticd=yes
|
||||
|
||||
vtysh_enable=yes
|
||||
zebra_options=" -A 127.0.0.1 -s 90000000"
|
||||
bgpd_options=" -A 127.0.0.1"
|
||||
staticd_options="-A 127.0.0.1"
|
||||
@@ -0,0 +1,23 @@
|
||||
frr version 8.5
|
||||
frr defaults traditional
|
||||
hostname a1
|
||||
log stdout informational
|
||||
no ipv6 forwarding
|
||||
!
|
||||
interface dummy0
|
||||
ip address 10.200.200.1/32
|
||||
!
|
||||
router bgp 65001
|
||||
bgp router-id 10.0.0.1
|
||||
no bgp ebgp-requires-policy
|
||||
neighbor 10.0.1.2 remote-as 65002
|
||||
neighbor 10.0.2.2 remote-as 65003
|
||||
!
|
||||
address-family ipv4 unicast
|
||||
network 10.200.200.1/32
|
||||
neighbor 10.0.1.2 activate
|
||||
neighbor 10.0.2.2 activate
|
||||
exit-address-family
|
||||
!
|
||||
line vty
|
||||
!
|
||||
@@ -0,0 +1 @@
|
||||
service integrated-vtysh-config
|
||||
@@ -0,0 +1,25 @@
|
||||
zebra=yes
|
||||
bgpd=yes
|
||||
ospfd=no
|
||||
ospf6d=no
|
||||
ripd=no
|
||||
ripngd=no
|
||||
isisd=no
|
||||
pimd=no
|
||||
pim6d=no
|
||||
ldpd=no
|
||||
nhrpd=no
|
||||
eigrpd=no
|
||||
babeld=no
|
||||
sharpd=no
|
||||
pbrd=no
|
||||
bfdd=no
|
||||
fabricd=no
|
||||
vrrpd=no
|
||||
pathd=no
|
||||
staticd=yes
|
||||
|
||||
vtysh_enable=yes
|
||||
zebra_options=" -A 127.0.0.1 -s 90000000"
|
||||
bgpd_options=" -A 127.0.0.1"
|
||||
staticd_options="-A 127.0.0.1"
|
||||
@@ -0,0 +1,23 @@
|
||||
frr version 8.5
|
||||
frr defaults traditional
|
||||
hostname a2
|
||||
log stdout informational
|
||||
no ipv6 forwarding
|
||||
!
|
||||
interface dummy0
|
||||
ip address 10.200.200.1/32
|
||||
!
|
||||
router bgp 65001
|
||||
bgp router-id 10.0.0.2
|
||||
no bgp ebgp-requires-policy
|
||||
neighbor 10.0.3.2 remote-as 65002
|
||||
neighbor 10.0.4.2 remote-as 65003
|
||||
!
|
||||
address-family ipv4 unicast
|
||||
network 10.200.200.1/32
|
||||
neighbor 10.0.3.2 activate
|
||||
neighbor 10.0.4.2 activate
|
||||
exit-address-family
|
||||
!
|
||||
line vty
|
||||
!
|
||||
@@ -0,0 +1 @@
|
||||
service integrated-vtysh-config
|
||||
@@ -0,0 +1,25 @@
|
||||
zebra=yes
|
||||
bgpd=yes
|
||||
ospfd=no
|
||||
ospf6d=no
|
||||
ripd=no
|
||||
ripngd=no
|
||||
isisd=no
|
||||
pimd=no
|
||||
pim6d=no
|
||||
ldpd=no
|
||||
nhrpd=no
|
||||
eigrpd=no
|
||||
babeld=no
|
||||
sharpd=no
|
||||
pbrd=no
|
||||
bfdd=no
|
||||
fabricd=no
|
||||
vrrpd=no
|
||||
pathd=no
|
||||
staticd=yes
|
||||
|
||||
vtysh_enable=yes
|
||||
zebra_options=" -A 127.0.0.1 -s 90000000"
|
||||
bgpd_options=" -A 127.0.0.1"
|
||||
staticd_options="-A 127.0.0.1"
|
||||
@@ -0,0 +1,23 @@
|
||||
frr version 8.5
|
||||
frr defaults traditional
|
||||
hostname a3
|
||||
log stdout informational
|
||||
no ipv6 forwarding
|
||||
!
|
||||
interface dummy0
|
||||
ip address 10.200.200.1/32
|
||||
!
|
||||
router bgp 65001
|
||||
bgp router-id 10.0.0.3
|
||||
no bgp ebgp-requires-policy
|
||||
neighbor 10.0.5.2 remote-as 65002
|
||||
neighbor 10.0.6.2 remote-as 65003
|
||||
!
|
||||
address-family ipv4 unicast
|
||||
network 10.200.200.1/32
|
||||
neighbor 10.0.5.2 activate
|
||||
neighbor 10.0.6.2 activate
|
||||
exit-address-family
|
||||
!
|
||||
line vty
|
||||
!
|
||||
@@ -0,0 +1 @@
|
||||
service integrated-vtysh-config
|
||||
@@ -0,0 +1,25 @@
|
||||
zebra=yes
|
||||
bgpd=yes
|
||||
ospfd=no
|
||||
ospf6d=no
|
||||
ripd=no
|
||||
ripngd=no
|
||||
isisd=no
|
||||
pimd=no
|
||||
pim6d=no
|
||||
ldpd=no
|
||||
nhrpd=no
|
||||
eigrpd=no
|
||||
babeld=no
|
||||
sharpd=no
|
||||
pbrd=no
|
||||
bfdd=no
|
||||
fabricd=no
|
||||
vrrpd=no
|
||||
pathd=no
|
||||
staticd=yes
|
||||
|
||||
vtysh_enable=yes
|
||||
zebra_options=" -A 127.0.0.1 -s 90000000"
|
||||
bgpd_options=" -A 127.0.0.1"
|
||||
staticd_options="-A 127.0.0.1"
|
||||
@@ -0,0 +1,24 @@
|
||||
frr version 8.5
|
||||
frr defaults traditional
|
||||
hostname b1
|
||||
log stdout informational
|
||||
no ipv6 forwarding
|
||||
!
|
||||
router bgp 65002
|
||||
bgp router-id 10.0.0.4
|
||||
no bgp ebgp-requires-policy
|
||||
bgp bestpath as-path multipath-relax
|
||||
neighbor 10.0.1.1 remote-as 65001
|
||||
neighbor 10.0.3.1 remote-as 65001
|
||||
neighbor 10.0.5.1 remote-as 65001
|
||||
!
|
||||
address-family ipv4 unicast
|
||||
network 10.100.1.0/24
|
||||
maximum-paths 3
|
||||
neighbor 10.0.1.1 activate
|
||||
neighbor 10.0.3.1 activate
|
||||
neighbor 10.0.5.1 activate
|
||||
exit-address-family
|
||||
!
|
||||
line vty
|
||||
!
|
||||
@@ -0,0 +1 @@
|
||||
service integrated-vtysh-config
|
||||
@@ -0,0 +1,25 @@
|
||||
zebra=yes
|
||||
bgpd=yes
|
||||
ospfd=no
|
||||
ospf6d=no
|
||||
ripd=no
|
||||
ripngd=no
|
||||
isisd=no
|
||||
pimd=no
|
||||
pim6d=no
|
||||
ldpd=no
|
||||
nhrpd=no
|
||||
eigrpd=no
|
||||
babeld=no
|
||||
sharpd=no
|
||||
pbrd=no
|
||||
bfdd=no
|
||||
fabricd=no
|
||||
vrrpd=no
|
||||
pathd=no
|
||||
staticd=yes
|
||||
|
||||
vtysh_enable=yes
|
||||
zebra_options=" -A 127.0.0.1 -s 90000000"
|
||||
bgpd_options=" -A 127.0.0.1"
|
||||
staticd_options="-A 127.0.0.1"
|
||||
@@ -0,0 +1,24 @@
|
||||
frr version 8.5
|
||||
frr defaults traditional
|
||||
hostname b2
|
||||
log stdout informational
|
||||
no ipv6 forwarding
|
||||
!
|
||||
router bgp 65003
|
||||
bgp router-id 10.0.0.5
|
||||
no bgp ebgp-requires-policy
|
||||
bgp bestpath as-path multipath-relax
|
||||
neighbor 10.0.2.1 remote-as 65001
|
||||
neighbor 10.0.4.1 remote-as 65001
|
||||
neighbor 10.0.6.1 remote-as 65001
|
||||
!
|
||||
address-family ipv4 unicast
|
||||
network 10.100.2.0/24
|
||||
maximum-paths 3
|
||||
neighbor 10.0.2.1 activate
|
||||
neighbor 10.0.4.1 activate
|
||||
neighbor 10.0.6.1 activate
|
||||
exit-address-family
|
||||
!
|
||||
line vty
|
||||
!
|
||||
@@ -0,0 +1 @@
|
||||
service integrated-vtysh-config
|
||||
@@ -0,0 +1,245 @@
|
||||
|
||||
# Anycast FRR Configuration
|
||||
|
||||
## Топология
|
||||
|
||||
### Full Mesh между площадками
|
||||
|
||||
```cfg
|
||||
Site A (AS 65001) Site B
|
||||
A1 ───────── 10.0.1.0/30 ───────── B1 (AS 65002)
|
||||
A1 ───────── 10.0.2.0/30 ───────── B2 (AS 65003)
|
||||
|
||||
A2 ───────── 10.0.3.0/30 ───────── B1 (AS 65002)
|
||||
A2 ───────── 10.0.4.0/30 ───────── B2 (AS 65003)
|
||||
|
||||
A3 ───────── 10.0.5.0/30 ───────── B1 (AS 65002)
|
||||
A3 ───────── 10.0.6.0/30 ───────── B2 (AS 65003)
|
||||
```
|
||||
|
||||
- Anycast-адрес на всех A: `10.200.200.1/32` (dummy0)
|
||||
- Клиентские сети площадки Б:
|
||||
|
||||
- B1: `10.100.1.0/24`
|
||||
- B2: `10.100.2.0/24`
|
||||
|
||||
### Диаграмма (Mermaid)
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph SiteA["Площадка A — AS 65001 · anycast 10.200.200.1/32"]
|
||||
A1["A1<br/>dummy0: 10.200.200.1/32"]
|
||||
A2["A2<br/>dummy0: 10.200.200.1/32"]
|
||||
A3["A3<br/>dummy0: 10.200.200.1/32"]
|
||||
end
|
||||
|
||||
subgraph SiteB1["Площадка B — B1 · AS 65002 · 10.100.1.0/24"]
|
||||
B1["B1"]
|
||||
C1["C1<br/>10.100.1.11"]
|
||||
C2["C2<br/>10.100.1.12"]
|
||||
C3["C3<br/>10.100.1.13"]
|
||||
C1 --> B1
|
||||
C2 --> B1
|
||||
C3 --> B1
|
||||
end
|
||||
|
||||
subgraph SiteB2["Площадка B — B2 · AS 65003 · 10.100.2.0/24"]
|
||||
B2["B2"]
|
||||
C4["C4<br/>10.100.2.14"]
|
||||
C5["C5<br/>10.100.2.15"]
|
||||
C6["C6<br/>10.100.2.16"]
|
||||
C4 --> B2
|
||||
C5 --> B2
|
||||
C6 --> B2
|
||||
end
|
||||
|
||||
A1 ---|"10.0.1.0/30"| B1
|
||||
A2 ---|"10.0.3.0/30"| B1
|
||||
A3 ---|"10.0.5.0/30"| B1
|
||||
A1 ---|"10.0.2.0/30"| B2
|
||||
A2 ---|"10.0.4.0/30"| B2
|
||||
A3 ---|"10.0.6.0/30"| B2
|
||||
```
|
||||
|
||||
Каждый B-роутер держит 3 eBGP-сессии в сторону площадки A (ECMP, `maximum-paths 3`) — трафик клиентов к anycast-адресу распределяется по A1/A2/A3.
|
||||
|
||||
---
|
||||
|
||||
## Конфигурация FRR — площадка A
|
||||
|
||||
### A1 (AS 65001)
|
||||
|
||||
```cfg
|
||||
interface dummy0
|
||||
ip address 10.200.200.1/32
|
||||
!
|
||||
interface eth0
|
||||
ip address 10.0.1.1/30
|
||||
!
|
||||
interface eth1
|
||||
ip address 10.0.2.1/30
|
||||
!
|
||||
router bgp 65001
|
||||
bgp router-id 10.0.0.1
|
||||
no bgp ebgp-requires-policy
|
||||
neighbor 10.0.1.2 remote-as 65002
|
||||
neighbor 10.0.2.2 remote-as 65003
|
||||
!
|
||||
address-family ipv4 unicast
|
||||
network 10.200.200.1/32
|
||||
neighbor 10.0.1.2 activate
|
||||
neighbor 10.0.2.2 activate
|
||||
exit-address-family
|
||||
```
|
||||
|
||||
### A2 (AS 65001)
|
||||
|
||||
```cfg
|
||||
interface dummy0
|
||||
ip address 10.200.200.1/32
|
||||
!
|
||||
interface eth0
|
||||
ip address 10.0.3.1/30
|
||||
!
|
||||
interface eth1
|
||||
ip address 10.0.4.1/30
|
||||
!
|
||||
router bgp 65001
|
||||
bgp router-id 10.0.0.2
|
||||
no bgp ebgp-requires-policy
|
||||
neighbor 10.0.3.2 remote-as 65002
|
||||
neighbor 10.0.4.2 remote-as 65003
|
||||
!
|
||||
address-family ipv4 unicast
|
||||
network 10.200.200.1/32
|
||||
neighbor 10.0.3.2 activate
|
||||
neighbor 10.0.4.2 activate
|
||||
exit-address-family
|
||||
```
|
||||
|
||||
### A3 (AS 65001)
|
||||
|
||||
```cfg
|
||||
interface dummy0
|
||||
ip address 10.200.200.1/32
|
||||
!
|
||||
interface eth0
|
||||
ip address 10.0.5.1/30
|
||||
!
|
||||
interface eth1
|
||||
ip address 10.0.6.1/30
|
||||
!
|
||||
router bgp 65001
|
||||
bgp router-id 10.0.0.3
|
||||
no bgp ebgp-requires-policy
|
||||
neighbor 10.0.5.2 remote-as 65002
|
||||
neighbor 10.0.6.2 remote-as 65003
|
||||
!
|
||||
address-family ipv4 unicast
|
||||
network 10.200.200.1/32
|
||||
neighbor 10.0.5.2 activate
|
||||
neighbor 10.0.6.2 activate
|
||||
exit-address-family
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Конфигурация FRR — площадка B
|
||||
|
||||
### B1 (AS 65002)
|
||||
|
||||
```
|
||||
interface eth0
|
||||
ip address 10.0.1.2/30
|
||||
!
|
||||
interface eth1
|
||||
ip address 10.0.3.2/30
|
||||
!
|
||||
interface eth2
|
||||
ip address 10.0.5.2/30
|
||||
!
|
||||
interface eth3
|
||||
ip address 10.100.1.1/24
|
||||
!
|
||||
router bgp 65002
|
||||
bgp router-id 10.0.0.4
|
||||
no bgp ebgp-requires-policy
|
||||
bgp bestpath as-path multipath-relax
|
||||
neighbor 10.0.1.1 remote-as 65001
|
||||
neighbor 10.0.3.1 remote-as 65001
|
||||
neighbor 10.0.5.1 remote-as 65001
|
||||
!
|
||||
address-family ipv4 unicast
|
||||
network 10.100.1.0/24
|
||||
maximum-paths 3
|
||||
neighbor 10.0.1.1 activate
|
||||
neighbor 10.0.3.1 activate
|
||||
neighbor 10.0.5.1 activate
|
||||
exit-address-family
|
||||
```
|
||||
|
||||
### B2 (AS 65003)
|
||||
|
||||
```cfg
|
||||
interface eth0
|
||||
ip address 10.0.2.2/30
|
||||
!
|
||||
interface eth1
|
||||
ip address 10.0.4.2/30
|
||||
!
|
||||
interface eth2
|
||||
ip address 10.0.6.2/30
|
||||
!
|
||||
interface eth3
|
||||
ip address 10.100.2.1/24
|
||||
!
|
||||
router bgp 65003
|
||||
bgp router-id 10.0.0.5
|
||||
no bgp ebgp-requires-policy
|
||||
bgp bestpath as-path multipath-relax
|
||||
neighbor 10.0.2.1 remote-as 65001
|
||||
neighbor 10.0.4.1 remote-as 65001
|
||||
neighbor 10.0.6.1 remote-as 65001
|
||||
!
|
||||
address-family ipv4 unicast
|
||||
network 10.100.2.0/24
|
||||
maximum-paths 3
|
||||
neighbor 10.0.2.1 activate
|
||||
neighbor 10.0.4.1 activate
|
||||
neighbor 10.0.6.1 activate
|
||||
exit-address-family
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Traffic Flow
|
||||
|
||||
### От клиента к anycast-адресу
|
||||
|
||||
```cfg
|
||||
Клиенты (Site B) B-маршрутизаторы A-маршрутизаторы (Site A)
|
||||
[Anycast IP 10.200.200.1/32]
|
||||
C1 (10.100.1.11) ──► B1 ───────────────────► A1 ──► dummy0
|
||||
C2 (10.100.1.12) ──► B1 ───────────────────► A2 ──► dummy0
|
||||
C3 (10.100.1.13) ──► B1 ───────────────────► A3 ──► dummy0
|
||||
C4 (10.100.2.14) ──► B2 ───────────────────► A1 ──► dummy0
|
||||
C5 (10.100.2.15) ──► B2 ───────────────────► A2 ──► dummy0
|
||||
C6 (10.100.2.16) ──► B2 ───────────────────► A3 ──► dummy0
|
||||
```
|
||||
|
||||
Каждый B использует ECMP (3 равноправных eBGP-пути) и распределяет клиентские потоки по A1/A2/A3.
|
||||
|
||||
---
|
||||
|
||||
## ECMP и распределение трафика
|
||||
|
||||
> Логика распределения нагрузки на примере 6 клиентов к anycast адресу на площадке B.
|
||||
|
||||
При 6 клиентах каждый маршрутизатор площадки B (B1 или B2) имеет три равноправных eBGP-маршрута к anycast-префиксу `10.200.200.1/32` (через A1, A2, A3) и использует ECMP.
|
||||
|
||||
Распределение трафика по путям выполняется на основе хэша 5-tuple (src IP, dst IP, src port, dst port, protocol) каждого клиентского потока:
|
||||
|
||||
- если все 6 клиентов подключены к B1 → каждый поток попадает в один из трёх путей (A1/A2/A3), в среднем по 2 клиента на путь, но возможны отклонения из-за конкретных значений хэша;
|
||||
- если клиенты распределены между B1 и B2 → каждый маршрутизатор B независимо распределяет свои клиентские потоки по своим трём линкам (всего 6 линков в сторону A).
|
||||
|
||||
Итог: трафик от 6 клиентов разделяется на 3 (или 6) параллельных потоков, каждый клиент закрепляется за одним anycast-узлом (A1/A2/A3) на время сессии.
|
||||
Executable
+32
@@ -0,0 +1,32 @@
|
||||
#!/bin/sh
|
||||
# Drives traffic from a Site-B client to the anycast address and shows which
|
||||
# A-node (a1/a2/a3) answered each request, demonstrating ECMP distribution.
|
||||
# Usage: scripts/traffic-test.sh [client] [request-count]
|
||||
set -e
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
CLIENT="${1:-c1}"
|
||||
COUNT="${2:-20}"
|
||||
DST="10.200.200.1"
|
||||
|
||||
echo "==== $CLIENT: ping $DST ===="
|
||||
docker compose exec -T "$CLIENT" ping -c 4 "$DST"
|
||||
|
||||
echo
|
||||
echo "==== $CLIENT: traceroute $DST ===="
|
||||
docker compose exec -T "$CLIENT" traceroute -n -m 5 "$DST" 2>&1 || true
|
||||
|
||||
echo
|
||||
echo "==== $CLIENT: $COUNT requests to http://$DST, varying source port (ECMP hash input) ===="
|
||||
RESULTS=$(docker compose exec -T "$CLIENT" sh -c '
|
||||
for i in $(seq 1 '"$COUNT"'); do
|
||||
port=$((30000 + i))
|
||||
node=$(curl -s --local-port "$port" --max-time 3 "http://'"$DST"'/" || echo "no-answer")
|
||||
echo "req $i (src-port $port) -> $node"
|
||||
done
|
||||
')
|
||||
echo "$RESULTS"
|
||||
|
||||
echo
|
||||
echo "Distribution:"
|
||||
echo "$RESULTS" | awk '{print $NF}' | sort | uniq -c
|
||||
Executable
+16
@@ -0,0 +1,16 @@
|
||||
#!/bin/sh
|
||||
# Checks that BGP has converged and that b1/b2 have 3-way ECMP to the anycast prefix.
|
||||
set -e
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
for n in a1 a2 a3 b1 b2; do
|
||||
echo "==== $n: show bgp summary ===="
|
||||
docker compose exec -T "$n" vtysh -c "show bgp summary"
|
||||
echo
|
||||
done
|
||||
|
||||
for n in b1 b2; do
|
||||
echo "==== $n: show ip route 10.200.200.1/32 ===="
|
||||
docker compose exec -T "$n" vtysh -c "show ip route 10.200.200.1/32"
|
||||
echo
|
||||
done
|
||||
Reference in new issue
Block a user