#!/bin/bash # Подъём Open vSwitch внутри контейнера и сборка моста br-lb. # # Контейнер работает как чистый OpenFlow-роутер: IP-адреса на интерфейсах не # настраиваются, сетевой стек ядра в форвардинге не участвует. Вся L3-логика # (ARP, маршрутизация, DNAT) живёт в таблицах OpenFlow — см. pipeline.sh. set -euo pipefail LB_DIR=/opt/lb # shellcheck disable=SC1091 source "$LB_DIR/topology.env" log() { echo "[lb-router] $*"; } die() { echo "[lb-router] ОШИБКА: $*" >&2; exit 1; } # --- 1. Kernel datapath ------------------------------------------------------ log "загрузка модуля openvswitch" modprobe openvswitch 2>/dev/null || true grep -q '^openvswitch ' /proc/modules || die \ "модуль openvswitch не загружен. Выполните на хосте: modprobe openvswitch (см. scripts/host-prereq.sh)" # --- 2. Запуск ovsdb-server и ovs-vswitchd ----------------------------------- mkdir -p /var/run/openvswitch /var/log/openvswitch /etc/openvswitch rm -f /var/run/openvswitch/*.pid if [ ! -f /etc/openvswitch/conf.db ]; then log "инициализация OVSDB" ovsdb-tool create /etc/openvswitch/conf.db /usr/share/openvswitch/vswitch.ovsschema fi log "запуск ovsdb-server" ovsdb-server /etc/openvswitch/conf.db \ --remote=punix:/var/run/openvswitch/db.sock \ --remote=db:Open_vSwitch,Open_vSwitch,manager_options \ --pidfile --detach --log-file ovs-vsctl --no-wait init log "запуск ovs-vswitchd" ovs-vswitchd --pidfile --detach --log-file # --- 3. Мост ----------------------------------------------------------------- log "создание моста $BR" ovs-vsctl --may-exist add-br "$BR" \ -- set bridge "$BR" fail_mode=secure \ -- set bridge "$BR" protocols=OpenFlow13,OpenFlow15 \ -- set bridge "$BR" other-config:disable-in-band=true ip link set dev "$BR" up # --- 4. Перенос интерфейсов Docker в мост ------------------------------------ # Интерфейсы опознаются по MAC (имена eth0/eth1/eth2 Docker раздаёт в # непредсказуемом порядке), переименовываются в осмысленные и лишаются IP: # адрес нужен только Docker IPAM, чтобы зарезервировать его за контейнером. find_dev_by_mac() { local mac="$1" d for d in /sys/class/net/*; do [ -f "$d/address" ] || continue [ "$(cat "$d/address")" = "$mac" ] && { basename "$d"; return 0; } done return 1 } attach_port() { local mac="$1" name="$2" ofport="$3" dev dev=$(find_dev_by_mac "$mac") || die "интерфейс с MAC $mac не найден" if [ "$dev" != "$name" ]; then ip link set dev "$dev" down ip addr flush dev "$dev" ip link set dev "$dev" name "$name" else ip addr flush dev "$name" fi ip link set dev "$name" up ovs-vsctl --may-exist add-port "$BR" "$name" \ -- set Interface "$name" ofport_request="$ofport" log "порт $name (MAC $mac) -> $BR, ofport $ofport" } attach_port "$PUB_MAC" "$PUB_IFNAME" "$PUB_OFPORT" attach_port "$P2_MAC" "$P2_IFNAME" "$P2_OFPORT" attach_port "$P3_MAC" "$P3_IFNAME" "$P3_OFPORT" # --- 4a. Порты-источники health-проб ----------------------------------------- # Единственные адреса, которые узел держит в ядре: с них уходят пробы (§7.1 # дизайна v1 — источник проб должен быть уникальным адресом узла, не VIP). # # MAC членов пула здесь больше НЕ прописывается статически (шаг 5): hcif-порты # — настоящие L3-интерфейсы ядра в том же сегменте, что и бэкенды, и ядро само # резолвит их MAC обычным ARP. Первая проба после старта чуть медленнее (кадр # ARP-запроса уходит через br-lb и возвращается ответом реального бэкенда), но # дальше запись живёт в neigh-таблице ядра обычным старением ARP — именно её # читает hcd (hc/neigh.go) и заливает в таблицу 21 OpenFlow. hc_port() { local name="$1" ofport="$2" mac="$3" ip="$4" net="$5" ovs-vsctl --may-exist add-port "$BR" "$name" \ -- set Interface "$name" type=internal ofport_request="$ofport" \ -- set Interface "$name" mac="\"$mac\"" ip link set dev "$name" address "$mac" ip addr replace "$ip/${net##*/}" dev "$name" ip link set dev "$name" up log "порт $name ($ip) — источник health-проб" } hc_port "$HC2_IFNAME" "$HC2_OFPORT" "$HC2_MAC" "$HC2_IP" "$P2_NET" hc_port "$HC3_IFNAME" "$HC3_OFPORT" "$HC3_MAC" "$HC3_IP" "$P3_NET" # Ждём, пока vswitchd реально привяжет порты к датапасу. for name in "$PUB_IFNAME" "$P2_IFNAME" "$P3_IFNAME"; do for _ in $(seq 30); do ofport=$(ovs-vsctl --if-exists get Interface "$name" ofport || echo -1) [ "$ofport" -gt 0 ] 2>/dev/null && break sleep 0.5 done [ "${ofport:-0}" -gt 0 ] 2>/dev/null || die "порт $name не привязался к датапасу: $(ovs-vsctl get Interface "$name" error 2>/dev/null || true)" done # --- 5. Пайплайн ------------------------------------------------------------- # Таблица слотов здесь пуста (fail-close) — её наполнит hcd после первых # успешных проб, поэтому apply.sh на этом этапе демона ещё не дёргает. "$LB_DIR/apply.sh" --no-reapply log "готово. Порты: $(ovs-vsctl list-ports "$BR" | tr '\n' ' ')" # --- 6. Health-check --------------------------------------------------------- # Демон становится основным процессом контейнера; ovsdb-server и ovs-vswitchd # работают демонами рядом. Логи проб и переходов видны в docker logs. CONF=$("$LB_DIR/hc-config.sh") log "конфигурация health-check: $CONF" tail -F --pid=$$ /var/log/openvswitch/ovs-vswitchd.log 2>/dev/null & exec hcd -config "$CONF"