Добавлена локальная локальная балансировка
This commit is contained in:
1 parent
afd4f057be
commit
d80a6c44b0
17 files changed
+1628
-369
No files matched your search
Executable
+114
@@ -0,0 +1,114 @@
|
||||
#!/bin/bash
|
||||
# Подключение ВМ приватных сегментов к мосту br-lb (шаг 3).
|
||||
#
|
||||
# В целевой среде порты виртуальных машин создаёт гипервизор, и каждая ВМ уже
|
||||
# висит на vSwitch. В контейнерном стенде эту роль выполняет этот скрипт: для
|
||||
# каждого участника сегмента он создаёт veth-пару, один конец кладёт в сетевое
|
||||
# пространство имён контейнера, другой — в netns балансировщика и добавляет в
|
||||
# br-lb с детерминированным ofport.
|
||||
#
|
||||
# Адрес, MAC и маршруты назначаются СНАРУЖИ, как это делают гипервизор и DHCP.
|
||||
# Внутри ОС ВМ ничего не настраивается — это условие шага 3.
|
||||
#
|
||||
# Запускать на хосте с правами root, после docker compose up:
|
||||
# sudo scripts/attach-segment.sh # подключить
|
||||
# sudo scripts/attach-segment.sh --status # показать порты моста
|
||||
# sudo scripts/attach-segment.sh --detach # снять порты
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")/.."
|
||||
# shellcheck disable=SC1091
|
||||
source lb/topology.env
|
||||
|
||||
LB_CONTAINER=hpnn-lb
|
||||
|
||||
log() { echo "[attach] $*"; }
|
||||
die() { echo "[attach] ОШИБКА: $*" >&2; exit 1; }
|
||||
|
||||
[ "$(id -u)" = 0 ] || die "нужны права root (sudo scripts/attach-segment.sh)"
|
||||
|
||||
pid_of() {
|
||||
local pid
|
||||
pid=$(docker inspect -f '{{.State.Pid}}' "$1" 2>/dev/null) || die "контейнер $1 не найден"
|
||||
[ "$pid" != "0" ] || die "контейнер $1 не запущен"
|
||||
echo "$pid"
|
||||
}
|
||||
|
||||
# attach <контейнер> <имя порта> <ofport> <mac> <ip> <префикс> <шлюз|-> [сеть via LB]...
|
||||
attach() {
|
||||
local cname="$1" port="$2" ofport="$3" mac="$4" ip="$5" plen="$6" gw="$7"
|
||||
shift 7
|
||||
local pid pid_lb tmp
|
||||
|
||||
pid=$(pid_of "$cname")
|
||||
pid_lb=$(pid_of "$LB_CONTAINER")
|
||||
|
||||
if nsenter -t "$pid" -n ip link show eth0 >/dev/null 2>&1; then
|
||||
log "$cname уже подключён, пропуск"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Временное имя нужно потому, что оба конца veth рождаются в netns хоста и
|
||||
# имя eth0 там почти наверняка занято.
|
||||
tmp="tmp$$-${ofport}"
|
||||
ip link add "$port" type veth peer name "$tmp"
|
||||
ip link set "$port" netns "$pid_lb"
|
||||
ip link set "$tmp" netns "$pid"
|
||||
|
||||
nsenter -t "$pid" -n ip link set "$tmp" name eth0
|
||||
nsenter -t "$pid" -n ip link set eth0 address "$mac"
|
||||
nsenter -t "$pid" -n ip addr replace "$ip/$plen" dev eth0
|
||||
nsenter -t "$pid" -n ip link set eth0 up
|
||||
# Профиль A: default остаётся на шлюзе Docker, через LB маршрутизируются
|
||||
# только клиентские префиксы и соседний сегмент. Клиентам не выдаётся ничего.
|
||||
[ "$gw" = "-" ] || nsenter -t "$pid" -n ip route replace default via "$gw"
|
||||
while [ $# -ge 1 ]; do
|
||||
nsenter -t "$pid" -n ip route replace "$1" via "$2"
|
||||
shift 2
|
||||
done
|
||||
|
||||
nsenter -t "$pid_lb" -n ip link set "$port" up
|
||||
docker exec "$LB_CONTAINER" ovs-vsctl --may-exist add-port "$BR" "$port" \
|
||||
-- set Interface "$port" ofport_request="$ofport" >/dev/null
|
||||
|
||||
log "$cname -> порт $port (ofport $ofport), $ip/$plen, MAC $mac"
|
||||
}
|
||||
|
||||
detach() {
|
||||
local port
|
||||
for port in be1 be3 cli2 be2 be4 cli3; do
|
||||
docker exec "$LB_CONTAINER" ovs-vsctl --if-exists del-port "$BR" "$port" >/dev/null 2>&1 || true
|
||||
done
|
||||
log "порты сегментов сняты с моста (интерфейсы исчезнут вместе с контейнерами)"
|
||||
}
|
||||
|
||||
status() {
|
||||
docker exec "$LB_CONTAINER" ovs-ofctl -O "$OF" show "$BR" | grep -E '^\s+[0-9]+\('
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
--status) status; exit 0 ;;
|
||||
--detach) detach; exit 0 ;;
|
||||
esac
|
||||
|
||||
p2len=${P2_NET##*/}
|
||||
p3len=${P3_NET##*/}
|
||||
|
||||
# Бэкенды сегмента 2: default на docker-шлюз, клиентский префикс и соседний
|
||||
# сегмент — через балансировщик.
|
||||
attach hpnn-be1 be1 "$BE1_OFPORT" "$BE1_MAC" "$BE1_IP" "$p2len" "$P2_DOCKER_GW" \
|
||||
192.168.5.0/24 "$LB_P2_IP" "$P3_NET" "$LB_P2_IP"
|
||||
attach hpnn-be3 be3 "$BE3_OFPORT" "$BE3_MAC" "$BE3_IP" "$p2len" "$P2_DOCKER_GW" \
|
||||
192.168.5.0/24 "$LB_P2_IP" "$P3_NET" "$LB_P2_IP"
|
||||
attach hpnn-be2 be2 "$BE2_OFPORT" "$BE2_MAC" "$BE2_IP" "$p3len" "$P3_DOCKER_GW" \
|
||||
192.168.5.0/24 "$LB_P3_IP" "$P2_NET" "$LB_P3_IP"
|
||||
attach hpnn-be4 be4 "$BE4_OFPORT" "$BE4_MAC" "$BE4_IP" "$p3len" "$P3_DOCKER_GW" \
|
||||
192.168.5.0/24 "$LB_P3_IP" "$P2_NET" "$LB_P3_IP"
|
||||
|
||||
# Клиенты: только адрес и маска. Ни маршрутов, ни дополнительных интерфейсов —
|
||||
# ровно то, что требует условие шага 3.
|
||||
attach hpnn-cli2 cli2 "$CLI2_OFPORT" "$CLI2_MAC" "$CLI2_IP" "$p2len" -
|
||||
attach hpnn-cli3 cli3 "$CLI3_OFPORT" "$CLI3_MAC" "$CLI3_IP" "$p3len" -
|
||||
|
||||
log "готово"
|
||||
status
|
||||
+90
-7
@@ -18,7 +18,7 @@ skip() { echo -e " \033[33mПРОПУСК\033[0m $*"; }
|
||||
head_() { echo; echo "== $*"; }
|
||||
|
||||
head_ "1. Контейнеры"
|
||||
for c in hpnn-lb hpnn-be1 hpnn-be2 hpnn-be3 hpnn-be4; do
|
||||
for c in hpnn-lb hpnn-be1 hpnn-be2 hpnn-be3 hpnn-be4 hpnn-cli2 hpnn-cli3; do
|
||||
state=$(docker inspect -f '{{.State.Status}}' "$c" 2>/dev/null || echo "нет")
|
||||
[ "$state" = "running" ] && ok "$c: $state" || bad "$c: $state"
|
||||
done
|
||||
@@ -28,11 +28,17 @@ ports=$($DC exec -T lb-router ovs-vsctl list-ports br-lb 2>/dev/null | tr '\n' '
|
||||
for p in pub0 p2 p3; do
|
||||
echo "$ports" | grep -qw "$p" && ok "порт $p в мосту" || bad "порт $p отсутствует (есть: $ports)"
|
||||
done
|
||||
# Шаг 3: каждая ВМ сегмента — отдельный порт моста. Без этого балансировщик не
|
||||
# увидит ответ бэкенда клиенту-соседу и приватный листенер работать не сможет.
|
||||
for p in be1 be3 cli2 be2 be4 cli3; do
|
||||
echo "$ports" | grep -qw "$p" && ok "ВМ $p подключена портом OVS" \
|
||||
|| bad "порт $p отсутствует — выполните make attach"
|
||||
done
|
||||
dp=$($DC exec -T lb-router ovs-appctl dpif/show 2>/dev/null | head -1)
|
||||
[ -n "$dp" ] && ok "датапас: $dp" || bad "датапас не поднят"
|
||||
|
||||
head_ "3. Пайплайн"
|
||||
for t in 0 5 6 10 11 12 15 16 20 21; do
|
||||
for t in 0 1 5 6 10 11 12 15 16 20 21 24 25; do
|
||||
n=$($DC exec -T lb-router ovs-ofctl -O OpenFlow15 dump-flows br-lb "table=$t" 2>/dev/null | grep -c 'table=')
|
||||
[ "${n:-0}" -gt 0 ] && ok "таблица $t: $n правил" || bad "таблица $t пуста"
|
||||
done
|
||||
@@ -90,9 +96,12 @@ else
|
||||
skip "shim не поднят — выполните: sudo scripts/host-prereq.sh --shim"
|
||||
fi
|
||||
|
||||
head_ "7. Профиль A: собственный egress бэкенда идёт мимо балансировщика"
|
||||
head_ "7. Профиль A: собственный egress бэкенда не маршрутизируется балансировщиком"
|
||||
# С шага 3 br-lb коммутирует сегмент, поэтому egress физически через него
|
||||
# проходит — но только как через коммутатор. Признак: кадр адресован MAC
|
||||
# docker-шлюза, а не MAC маршрутизатора сегмента (02:42:0a:14:00:01).
|
||||
dump=$(mktemp)
|
||||
timeout 12 $DC exec -T lb-router timeout 8 tcpdump -ni p2 -c 2 'host 1.1.1.1' >"$dump" 2>&1 &
|
||||
timeout 12 $DC exec -T lb-router timeout 8 tcpdump -eni be1 -c 2 'host 1.1.1.1' >"$dump" 2>&1 &
|
||||
tcpdump_pid=$!
|
||||
sleep 2
|
||||
code=$($DC exec -T be1 curl -s -o /dev/null -w '%{http_code}' --max-time 6 http://1.1.1.1/ 2>/dev/null)
|
||||
@@ -100,9 +109,13 @@ wait $tcpdump_pid 2>/dev/null
|
||||
[ "${code:-000}" != "000" ] && ok "be1 достучался наружу (HTTP $code) через шлюз Docker" \
|
||||
|| bad "be1 не имеет выхода наружу (HTTP ${code:-000})"
|
||||
if grep -q '^[0-9][0-9]:' "$dump"; then
|
||||
bad "трафик egress виден на порту p2 балансировщика:"; sed 's/^/ /' "$dump"
|
||||
if grep -q '> 02:42:0a:14:00:01' "$dump"; then
|
||||
bad "egress адресован маршрутизатору сегмента — default ушёл на балансировщик"
|
||||
else
|
||||
ok "egress адресован шлюзу Docker, а не маршрутизатору сегмента"
|
||||
fi
|
||||
else
|
||||
ok "на порту p2 балансировщика этого трафика нет"
|
||||
bad "трафик egress не наблюдается на порту ВМ"
|
||||
fi
|
||||
rm -f "$dump"
|
||||
|
||||
@@ -144,7 +157,9 @@ done
|
||||
[ "$uneven" -eq 0 ] && ok "слоты поделены поровну:$dist" || bad "неравномерная раскладка:$dist"
|
||||
|
||||
head_ "9. Источник health-проб — уникальный адрес узла, не VIP"
|
||||
src=$($DC exec -T lb-router timeout 6 tcpdump -ni p2 -c 1 'tcp port 8080 and tcp[tcpflags] & tcp-syn != 0' 2>/dev/null \
|
||||
# Слушаем порт самой ВМ: с шага 3 проба идёт коммутацией сегмента, а не через
|
||||
# аплинк p2, поэтому на аплинке её больше не видно.
|
||||
src=$($DC exec -T lb-router timeout 6 tcpdump -ni be1 -c 1 'tcp port 8080 and tcp[tcpflags] & tcp-syn != 0' 2>/dev/null \
|
||||
| sed -n 's/.*IP \([0-9.]*\)\.[0-9]* > .*/\1/p' | head -1)
|
||||
[ "$src" = "10.20.0.253" ] && ok "проба к be1 уходит с $src" || bad "проба уходит с ${src:-неизвестно}, ожидался 10.20.0.253"
|
||||
|
||||
@@ -240,6 +255,74 @@ sleep 1
|
||||
&& ok "после apply.sh раскладка та же: $d_before" \
|
||||
|| bad "раскладка изменилась: $(digest), слотов $(snapshot | wc -l)"
|
||||
|
||||
head_ "15. Листенер в приватном сегменте (шаг 3)"
|
||||
# Клиент cli2 живёт в одном сегменте с be1 и be3 — тот самый случай, ради
|
||||
# которого сегмент переведён на порты OVS. В его ОС не настроено ничего, кроме
|
||||
# адреса: VIP находится в его собственной подсети.
|
||||
P2_VIP=10.20.0.100
|
||||
routes=$($DC exec -T cli2 ip route 2>/dev/null | grep -c 'via')
|
||||
[ "${routes:-1}" -eq 0 ] && ok "в ОС клиента нет ни одного маршрута — только адрес" \
|
||||
|| bad "у клиента появились маршруты: их быть не должно"
|
||||
|
||||
$DC exec -T cli2 ping -c1 -W2 "$P2_VIP" >/dev/null 2>&1 \
|
||||
&& ok "ping $P2_VIP из сегмента (ARP- и ICMP-респондеры OVS)" \
|
||||
|| bad "VIP приватного листенера недоступен"
|
||||
|
||||
declare -A phits=()
|
||||
pclient=""; pserved=""; pown=0
|
||||
for _ in $(seq 24); do
|
||||
line=$($DC exec -T cli2 curl -s --max-time 5 "http://$P2_VIP/" 2>/dev/null) || continue
|
||||
b=$(echo "$line" | sed -n 's/.*backend=\([a-z0-9]*\).*/\1/p')
|
||||
c=$(echo "$line" | sed -n 's/.*client=\([0-9.]*\):.*/\1/p')
|
||||
sv=$(echo "$line" | sed -n 's/.*served=\([0-9.]*:[0-9]*\).*/\1/p')
|
||||
[ -n "$b" ] && phits[$b]=$(( ${phits[$b]:-0} + 1 ))
|
||||
[ -n "$c" ] && pclient=$c
|
||||
case "$b" in be1|be3) pown=$((pown + 1)); pserved=$sv ;; esac
|
||||
done
|
||||
echo " распределение: $(for k in "${!phits[@]}"; do printf '%s=%s ' "$k" "${phits[$k]}"; done)"
|
||||
[ "${#phits[@]}" -ge 3 ] && ok "приватный листенер балансирует между ${#phits[@]} членами из 4" \
|
||||
|| bad "задействовано лишь ${#phits[@]} членов"
|
||||
|
||||
# Ключевая проверка шага: бэкенд видит исходный адрес клиента.
|
||||
[ "$pclient" = "10.20.0.10" ] && ok "бэкенд видит исходный IP клиента: $pclient (SNAT не выполняется)" \
|
||||
|| bad "бэкенд видит $pclient вместо 10.20.0.10"
|
||||
|
||||
# Тот самый случай: член пула в одном сегменте с клиентом. Ответ уходит по L2
|
||||
# напрямую, но проходит через br-lb, где таблица 24 снимает трансляцию.
|
||||
[ "$pown" -gt 0 ] && ok "члены из сегмента клиента обслужили $pown запросов" \
|
||||
|| bad "ни один запрос не попал на be1/be3 — коммутируемый путь не проверен"
|
||||
case "$pserved" in
|
||||
10.20.0.[23]:8080) ok "трансляция порта выполнена: листенер 80 -> член $pserved" ;;
|
||||
"") bad "не удалось определить адрес обслуживания" ;;
|
||||
*) bad "неожиданный адрес обслуживания: $pserved" ;;
|
||||
esac
|
||||
|
||||
# Путь остался коммутируемым: TTL ответа от VIP не уменьшен, значит клиент и
|
||||
# бэкенд по-прежнему L2-соседи и балансировщик не стал для них L3-хопом.
|
||||
ttls=$(mktemp)
|
||||
timeout 12 $DC exec -T lb-router timeout 8 tcpdump -ni cli2 -c 4 -v \
|
||||
"tcp and src host $P2_VIP and tcp[tcpflags] & tcp-syn != 0" >"$ttls" 2>/dev/null &
|
||||
tpid=$!
|
||||
sleep 1
|
||||
for _ in $(seq 12); do $DC exec -T cli2 curl -s --max-time 3 -o /dev/null "http://$P2_VIP/" 2>/dev/null; done
|
||||
wait $tpid 2>/dev/null
|
||||
grep -q 'ttl 64' "$ttls" && ok "ответ от VIP приходит с TTL 64 — путь коммутируемый, не маршрутный" \
|
||||
|| bad "TTL ответа уменьшен: путь стал маршрутизируемым"
|
||||
rm -f "$ttls"
|
||||
|
||||
head_ "16. Внутрисегментный трафик не блокирован"
|
||||
out=$($DC exec -T cli2 curl -s --max-time 5 http://10.20.0.2:8080/ 2>/dev/null)
|
||||
echo "$out" | grep -q 'backend=be1' && ok "клиент обращается к бэкенду напрямую, минуя VIP" \
|
||||
|| bad "прямое обращение клиента к бэкенду не работает"
|
||||
out=$($DC exec -T be1 curl -s --max-time 5 http://10.20.0.3:8080/ 2>/dev/null)
|
||||
echo "$out" | grep -q 'backend=be3' && ok "be1 -> be3 внутри сегмента" \
|
||||
|| bad "связность между бэкендами сегмента нарушена"
|
||||
$DC exec -T be1 ping -c1 -W2 10.20.0.254 >/dev/null 2>&1 \
|
||||
&& ok "бэкенд достаёт шлюз Docker через аплинк сегмента" \
|
||||
|| bad "шлюз сегмента недоступен"
|
||||
$DC exec -T cli3 curl -s --max-time 5 http://10.30.0.3:8080/ 2>/dev/null | grep -q 'backend=be4' \
|
||||
&& ok "cli3 -> be4 во втором сегменте" || bad "связность второго сегмента нарушена"
|
||||
|
||||
echo
|
||||
echo "Итог: успешно $pass, провалено $fail"
|
||||
[ "$fail" -eq 0 ]
|
||||
Reference in new issue
Block a user