MVP Single Node with Manual Config in Docker
This commit is contained in:
commit
8212699f23
27 files changed
+3818
No files matched your search
@@ -0,0 +1,33 @@
|
||||
# Контекст сборки — корень репозитория: в образ едет и демон health-check.
|
||||
FROM golang:1.23-alpine AS build
|
||||
WORKDIR /src
|
||||
COPY hc/go.mod ./
|
||||
COPY hc/*.go ./
|
||||
RUN CGO_ENABLED=0 go build -trimpath -o /out/hcd .
|
||||
|
||||
FROM debian:bookworm-slim
|
||||
|
||||
# openvswitch-switch — ovsdb-server и ovs-vswitchd; kmod — modprobe openvswitch;
|
||||
# curl — обращения к API демона из lbctl; остальное — диагностика стенда.
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
openvswitch-switch \
|
||||
openvswitch-common \
|
||||
iproute2 \
|
||||
kmod \
|
||||
tcpdump \
|
||||
iputils-ping \
|
||||
conntrack \
|
||||
curl \
|
||||
procps \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY --from=build /out/hcd /usr/local/bin/hcd
|
||||
COPY lb/topology.env lb/pipeline.sh lb/apply.sh lb/entrypoint.sh lb/lbctl.sh lb/hc-config.sh /opt/lb/
|
||||
RUN chmod +x /opt/lb/*.sh && ln -s /opt/lb/lbctl.sh /usr/local/bin/lbctl
|
||||
|
||||
HEALTHCHECK --interval=15s --timeout=5s --start-period=20s --retries=3 \
|
||||
CMD ovs-ofctl -O OpenFlow15 dump-flows br-lb table=11 >/dev/null 2>&1 \
|
||||
&& curl -fsS --max-time 3 http://127.0.0.1:9111/status >/dev/null || exit 1
|
||||
|
||||
ENTRYPOINT ["/opt/lb/entrypoint.sh"]
|
||||
Executable
+34
@@ -0,0 +1,34 @@
|
||||
#!/bin/bash
|
||||
# Генерация и применение OpenFlow-пайплайна.
|
||||
# Вызывается из entrypoint.sh при старте и вручную (make flows) при правках.
|
||||
#
|
||||
# apply.sh применить пайплайн и попросить hcd перезалить слоты
|
||||
# apply.sh --no-reapply только пайплайн (демон ещё не запущен)
|
||||
set -euo pipefail
|
||||
|
||||
LB_DIR=/opt/lb
|
||||
# shellcheck disable=SC1091
|
||||
source "$LB_DIR/topology.env"
|
||||
|
||||
FLOWS=/var/run/openvswitch/flows.txt
|
||||
|
||||
# Группа балансировки шага 1 больше не используется: её место заняла таблица
|
||||
# слотов (таблица 11), которую ведёт демон hcd.
|
||||
ovs-ofctl -O "$OF" del-groups "$BR" 2>/dev/null || true
|
||||
|
||||
# Правила применяются атомарным бандлом: датапас не проходит через
|
||||
# промежуточное состояние с частично залитым пайплайном.
|
||||
"$LB_DIR/pipeline.sh" > "$FLOWS"
|
||||
ovs-ofctl -O "$OF" --bundle replace-flows "$BR" "$FLOWS"
|
||||
|
||||
echo "[lb-router] пайплайн применён: $(grep -cv '^\s*\(#\|$\)' "$FLOWS") правил"
|
||||
|
||||
# replace-flows стирает и таблицу слотов, поэтому демона просят залить её
|
||||
# заново — в актуальном составе пула, а не в полном.
|
||||
if [ "${1:-}" != "--no-reapply" ]; then
|
||||
if out=$(curl -fsS --max-time 5 "http://$HC_API/reapply" 2>&1); then
|
||||
echo "[lb-router] $out"
|
||||
else
|
||||
echo "[lb-router] hcd недоступен ($out) — таблица слотов пуста, датапас в fail-close" >&2
|
||||
fi
|
||||
fi
|
||||
Executable
+136
@@ -0,0 +1,136 @@
|
||||
#!/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 бэкенда прописывается статически: ARP-резолвер узлу не нужен, MAC членов
|
||||
# пула и так зафиксированы в docker-compose.yml.
|
||||
hc_port() {
|
||||
local name="$1" ofport="$2" mac="$3" ip="$4" net="$5"
|
||||
shift 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
|
||||
# Остальные аргументы — пары «адрес MAC» членов пула в этом сегменте.
|
||||
local peers=""
|
||||
while [ $# -ge 2 ]; do
|
||||
ip neigh replace "$1" lladdr "$2" dev "$name" nud permanent
|
||||
peers="$peers $1"
|
||||
shift 2
|
||||
done
|
||||
log "порт $name ($ip) — источник health-проб для$peers"
|
||||
}
|
||||
|
||||
hc_port "$HC2_IFNAME" "$HC2_OFPORT" "$HC2_MAC" "$HC2_IP" "$P2_NET" \
|
||||
"$BE1_IP" "$BE1_MAC" "$BE3_IP" "$BE3_MAC"
|
||||
hc_port "$HC3_IFNAME" "$HC3_OFPORT" "$HC3_MAC" "$HC3_IP" "$P3_NET" \
|
||||
"$BE2_IP" "$BE2_MAC" "$BE4_IP" "$BE4_MAC"
|
||||
|
||||
# Ждём, пока 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"
|
||||
Executable
+66
@@ -0,0 +1,66 @@
|
||||
#!/bin/bash
|
||||
# Генерация конфигурации демона hcd из topology.env — единственного источника
|
||||
# правды по адресам стенда. Пока пул задан статически; на следующем шаге его
|
||||
# место займёт модель Load_Balancer -> Listener -> Pool -> Member в OVSDB.
|
||||
set -euo pipefail
|
||||
|
||||
LB_DIR=/opt/lb
|
||||
# shellcheck disable=SC1091
|
||||
source "$LB_DIR/topology.env"
|
||||
|
||||
OUT=${1:-/var/run/openvswitch/hc.json}
|
||||
|
||||
cat > "$OUT" <<EOF
|
||||
{
|
||||
"bridge": "$BR",
|
||||
"of_version": "$OF",
|
||||
"pool_id": $POOL_ID,
|
||||
"slots": $SLOTS,
|
||||
"slot_table": 11,
|
||||
"dnat_table": 12,
|
||||
"probe": "$HC_PROBE",
|
||||
"http_path": "$HC_HTTP_PATH",
|
||||
"interval": "$HC_INTERVAL",
|
||||
"timeout": "$HC_TIMEOUT",
|
||||
"rise": $HC_RISE,
|
||||
"fall": $HC_FALL,
|
||||
"api": "$HC_API",
|
||||
"apply_script": "$LB_DIR/apply.sh",
|
||||
"members": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "be1",
|
||||
"address": "$BE1_IP",
|
||||
"port": $BE1_PORT,
|
||||
"weight": 1,
|
||||
"source": "$HC2_IP"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "be2",
|
||||
"address": "$BE2_IP",
|
||||
"port": $BE2_PORT,
|
||||
"weight": 1,
|
||||
"source": "$HC3_IP"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"name": "be3",
|
||||
"address": "$BE3_IP",
|
||||
"port": $BE3_PORT,
|
||||
"weight": 1,
|
||||
"source": "$HC2_IP"
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"name": "be4",
|
||||
"address": "$BE4_IP",
|
||||
"port": $BE4_PORT,
|
||||
"weight": 1,
|
||||
"source": "$HC3_IP"
|
||||
}
|
||||
]
|
||||
}
|
||||
EOF
|
||||
|
||||
echo "$OUT"
|
||||
Executable
+83
@@ -0,0 +1,83 @@
|
||||
#!/bin/bash
|
||||
# Инструмент осмотра датапаса. Запускается внутри контейнера lb-router:
|
||||
# docker compose exec lb-router lbctl <команда>
|
||||
set -euo pipefail
|
||||
|
||||
LB_DIR=/opt/lb
|
||||
# shellcheck disable=SC1091
|
||||
source "$LB_DIR/topology.env"
|
||||
|
||||
usage() {
|
||||
cat <<USAGE
|
||||
lbctl — осмотр датапаса стенда hpnn_v2
|
||||
|
||||
ports порты моста и их ofport
|
||||
flows [табл] правила (все или конкретной таблицы), со счётчиками
|
||||
health состояние членов пула по данным health-проб
|
||||
slots раскладка таблицы слотов и счётчики пакетов
|
||||
drain <член> вывести член из балансировки (пробы продолжаются)
|
||||
enable <член> вернуть член в балансировку
|
||||
metrics метрики в формате Prometheus
|
||||
conns таблица соединений ct (зона $CT_ZONE)
|
||||
trace <src_ip> [src_port]
|
||||
ofproto/trace для TCP-сессии клиент -> VIP:$VIP_PORT
|
||||
reload перегенерировать и применить пайплайн
|
||||
USAGE
|
||||
}
|
||||
|
||||
api() { curl -fsS --max-time 5 "http://$HC_API$1"; }
|
||||
|
||||
case "${1:-}" in
|
||||
ports)
|
||||
ovs-vsctl show
|
||||
ovs-ofctl -O "$OF" show "$BR" | grep -E '^\s+[0-9]+\('
|
||||
;;
|
||||
flows)
|
||||
if [ -n "${2:-}" ]; then
|
||||
ovs-ofctl -O "$OF" dump-flows "$BR" "table=$2"
|
||||
else
|
||||
ovs-ofctl -O "$OF" dump-flows "$BR"
|
||||
fi
|
||||
;;
|
||||
health)
|
||||
api /status.txt
|
||||
;;
|
||||
status)
|
||||
api /status
|
||||
;;
|
||||
slots)
|
||||
echo "Раскладка по данным датапаса (счётчики правил дают per-member статистику):"
|
||||
ovs-ofctl -O "$OF" dump-flows "$BR" table=11 \
|
||||
| sed -n 's/.*n_packets=\([0-9]*\).*set_field:\(0x[0-9a-f]*\)->reg2.*/\2 \1/p' \
|
||||
| awk '{slots[$1]++; pkts[$1]+=$2}
|
||||
END {for (m in slots) printf " член id=%s: слотов %d, пакетов %d\n", m, slots[m], pkts[m]}' \
|
||||
| sort
|
||||
drops=$(ovs-ofctl -O "$OF" dump-flows "$BR" table=11 | sed -n 's/.*n_packets=\([0-9]*\).*priority=0.*/\1/p')
|
||||
echo " отброшено правилом fail-close: ${drops:-0}"
|
||||
api /status.txt | sed -n '2p'
|
||||
;;
|
||||
drain)
|
||||
api "/drain?member=${2:?укажите имя члена}"
|
||||
;;
|
||||
enable)
|
||||
api "/enable?member=${2:?укажите имя члена}"
|
||||
;;
|
||||
metrics)
|
||||
api /metrics
|
||||
;;
|
||||
conns)
|
||||
ovs-appctl dpctl/dump-conntrack | grep -F "zone=$CT_ZONE" || echo "соединений нет"
|
||||
;;
|
||||
trace)
|
||||
src_ip="${2:?укажите IP клиента}"
|
||||
src_port="${3:-40000}"
|
||||
ovs-appctl ofproto/trace "$BR" \
|
||||
"in_port=$PUB_OFPORT,dl_src=00:11:22:33:44:55,dl_dst=$PUB_MAC,dl_type=0x0800,nw_src=$src_ip,nw_dst=$VIP,nw_proto=6,nw_ttl=64,tp_src=$src_port,tp_dst=$VIP_PORT,tcp_flags=syn"
|
||||
;;
|
||||
reload)
|
||||
"$LB_DIR/apply.sh"
|
||||
;;
|
||||
*)
|
||||
usage
|
||||
;;
|
||||
esac
|
||||
Executable
+202
@@ -0,0 +1,202 @@
|
||||
#!/bin/bash
|
||||
# Генератор OpenFlow-пайплайна br-lb. Печатает flow-файл в stdout.
|
||||
# Применяется атомарным бандлом: ovs-ofctl --bundle -O OpenFlow15 replace-flows.
|
||||
#
|
||||
# Карта таблиц:
|
||||
# 0 — классификация по in_port и типу трафика, обучение MAC клиентов
|
||||
# 5 — ARP-респондер для собственных адресов узла и VIP
|
||||
# 6 — ICMP echo-респондер для тех же адресов
|
||||
# 10 — листенеры: хэш сессии в слот (multipath), прочий IP -> маршрутизация
|
||||
# 11 — таблица слотов: слот -> член пула. Заливает и обновляет демон hcd
|
||||
# 12 — применение DNAT выбранным членом пула (по reg2)
|
||||
# 15 — обратный путь из приватных сегментов (снятие DNAT через ct)
|
||||
# 16 — пост-ct hook (счётчики, место под будущие проверки)
|
||||
# 20 — маршрутизация (LPM через приоритет = длина префикса)
|
||||
# 21 — adjacency: next-hop MAC + выходной порт
|
||||
#
|
||||
# Регистры: reg1 — номер слота, reg2 — идентификатор члена пула.
|
||||
#
|
||||
# Нумерация не произвольна: goto_table в OpenFlow разрешает переход только
|
||||
# вперёд, поэтому обратный путь (15/16) стоит до общей маршрутизации (20).
|
||||
set -euo pipefail
|
||||
|
||||
# shellcheck disable=SC1091
|
||||
source "$(dirname "$0")/topology.env"
|
||||
|
||||
ip2hex() { local IFS=.; read -ra o <<<"$1"; printf '0x%02x%02x%02x%02x' "${o[0]}" "${o[1]}" "${o[2]}" "${o[3]}"; }
|
||||
mac2hex() { echo "0x${1//:/}"; }
|
||||
|
||||
PUB_MAC_H=$(mac2hex "$PUB_MAC")
|
||||
P2_MAC_H=$(mac2hex "$P2_MAC")
|
||||
P3_MAC_H=$(mac2hex "$P3_MAC")
|
||||
|
||||
LB_PUB_IP_H=$(ip2hex "$LB_PUB_IP")
|
||||
VIP_H=$(ip2hex "$VIP")
|
||||
LB_P2_IP_H=$(ip2hex "$LB_P2_IP")
|
||||
LB_P3_IP_H=$(ip2hex "$LB_P3_IP")
|
||||
|
||||
HC2_MAC_H=$(mac2hex "$HC2_MAC")
|
||||
HC3_MAC_H=$(mac2hex "$HC3_MAC")
|
||||
HC2_IP_H=$(ip2hex "$HC2_IP")
|
||||
HC3_IP_H=$(ip2hex "$HC3_IP")
|
||||
|
||||
# Действие обучения: по IP-адресу отправителя создаёт в таблице 21 запись
|
||||
# adjacency для обратного пути — куда и с каким MAC отправлять ответы этому
|
||||
# клиенту. Заменяет ARP-резолвер, которого у чисто-OpenFlow узла нет.
|
||||
LEARN="learn(table=21,priority=90,hard_timeout=300,eth_type=0x0800,NXM_OF_IP_DST[]=NXM_OF_IP_SRC[],load:NXM_OF_ETH_SRC[]->NXM_OF_ETH_DST[],load:$PUB_MAC_H->NXM_OF_ETH_SRC[],output:NXM_OF_IN_PORT[])"
|
||||
|
||||
# arp_responder <ip> <ip_hex> <mac> <mac_hex>
|
||||
arp_responder() {
|
||||
echo "table=5,priority=100,arp,arp_op=1,arp_tpa=$1 actions=move:NXM_OF_ETH_SRC[]->NXM_OF_ETH_DST[],mod_dl_src:$3,load:0x2->NXM_OF_ARP_OP[],move:NXM_NX_ARP_SHA[]->NXM_NX_ARP_THA[],move:NXM_OF_ARP_SPA[]->NXM_OF_ARP_TPA[],load:$4->NXM_NX_ARP_SHA[],load:$2->NXM_OF_ARP_SPA[],IN_PORT"
|
||||
}
|
||||
|
||||
# icmp_responder <ip> <ip_hex> <mac_hex>
|
||||
icmp_responder() {
|
||||
echo "table=6,priority=100,icmp,nw_dst=$1,icmp_type=8 actions=move:NXM_OF_ETH_SRC[]->NXM_OF_ETH_DST[],load:$3->NXM_OF_ETH_SRC[],move:NXM_OF_IP_SRC[]->NXM_OF_IP_DST[],load:$2->NXM_OF_IP_SRC[],load:0->NXM_OF_ICMP_TYPE[],IN_PORT"
|
||||
}
|
||||
|
||||
cat <<EOF
|
||||
# =============================================================================
|
||||
# Таблица 0 — классификация
|
||||
# =============================================================================
|
||||
# ARP обрабатывает собственный респондер (таблица 5): ядро в форвардинге не
|
||||
# участвует, поэтому штатного ARP-стека у узла нет.
|
||||
table=0,priority=200,arp actions=goto_table:5
|
||||
|
||||
# ICMP echo-request на собственные адреса узла и на VIP — в респондер.
|
||||
table=0,priority=190,icmp,nw_dst=$LB_PUB_IP,icmp_type=8 actions=goto_table:6
|
||||
table=0,priority=190,icmp,nw_dst=$VIP,icmp_type=8 actions=goto_table:6
|
||||
table=0,priority=190,icmp,nw_dst=$LB_P2_IP,icmp_type=8 actions=goto_table:6
|
||||
table=0,priority=190,icmp,nw_dst=$LB_P3_IP,icmp_type=8 actions=goto_table:6
|
||||
|
||||
# Вход из публичного сегмента: запоминаем MAC отправителя как adjacency для
|
||||
# обратного пути (замена ARP-резолвера на клиентской стороне), затем к
|
||||
# листенерам. Записи живут 300 с и обновляются каждым пакетом клиента.
|
||||
#
|
||||
# Обучение включено только для трафика, адресованного стенду (VIP, адрес узла,
|
||||
# приватные сегменты). Иначе в таблицу 21 попадал бы весь широковещательный
|
||||
# шум LAN — macvlan-порт видит DHCP, mDNS и SSDP всех соседей по сегменту.
|
||||
table=0,priority=110,in_port=$PUB_OFPORT,ip,nw_dst=$VIP actions=$LEARN,goto_table:10
|
||||
table=0,priority=110,in_port=$PUB_OFPORT,ip,nw_dst=$LB_PUB_IP actions=$LEARN,goto_table:10
|
||||
table=0,priority=110,in_port=$PUB_OFPORT,ip,nw_dst=$P2_NET actions=$LEARN,goto_table:10
|
||||
table=0,priority=110,in_port=$PUB_OFPORT,ip,nw_dst=$P3_NET actions=$LEARN,goto_table:10
|
||||
table=0,priority=100,in_port=$PUB_OFPORT,ip actions=drop
|
||||
|
||||
# Вход из приватных сегментов: обратный путь балансируемых сессий и транзит.
|
||||
table=0,priority=100,in_port=$P2_OFPORT,ip actions=goto_table:15
|
||||
table=0,priority=100,in_port=$P3_OFPORT,ip actions=goto_table:15
|
||||
|
||||
# Health-пробы, порождённые самим узлом: сразу в маршрутизацию, без ct и без
|
||||
# обучения — это трафик из стека узла, а не клиентская сессия.
|
||||
table=0,priority=100,in_port=$HC2_OFPORT,ip actions=goto_table:20
|
||||
table=0,priority=100,in_port=$HC3_OFPORT,ip actions=goto_table:20
|
||||
|
||||
# Всё остальное (не-IP, не-ARP: broadcast, IPv6, DHCP) в прототипе не нужно.
|
||||
table=0,priority=0 actions=drop
|
||||
|
||||
# =============================================================================
|
||||
# Таблица 5 — ARP-респондер
|
||||
# =============================================================================
|
||||
$(arp_responder "$LB_PUB_IP" "$LB_PUB_IP_H" "$PUB_MAC" "$PUB_MAC_H")
|
||||
$(arp_responder "$VIP" "$VIP_H" "$PUB_MAC" "$PUB_MAC_H")
|
||||
$(arp_responder "$LB_P2_IP" "$LB_P2_IP_H" "$P2_MAC" "$P2_MAC_H")
|
||||
$(arp_responder "$LB_P3_IP" "$LB_P3_IP_H" "$P3_MAC" "$P3_MAC_H")
|
||||
# Адреса источника health-проб: бэкенд резолвит их, отвечая на пробу.
|
||||
$(arp_responder "$HC2_IP" "$HC2_IP_H" "$HC2_MAC" "$HC2_MAC_H")
|
||||
$(arp_responder "$HC3_IP" "$HC3_IP_H" "$HC3_MAC" "$HC3_MAC_H")
|
||||
table=5,priority=0 actions=drop
|
||||
|
||||
# =============================================================================
|
||||
# Таблица 6 — ICMP echo-респондер
|
||||
# =============================================================================
|
||||
$(icmp_responder "$LB_PUB_IP" "$LB_PUB_IP_H" "$PUB_MAC_H")
|
||||
$(icmp_responder "$VIP" "$VIP_H" "$PUB_MAC_H")
|
||||
$(icmp_responder "$LB_P2_IP" "$LB_P2_IP_H" "$P2_MAC_H")
|
||||
$(icmp_responder "$LB_P3_IP" "$LB_P3_IP_H" "$P3_MAC_H")
|
||||
table=6,priority=0 actions=drop
|
||||
|
||||
# =============================================================================
|
||||
# Таблица 10 — листенеры
|
||||
# =============================================================================
|
||||
# Единственный листенер прототипа: TCP $VIP:$VIP_PORT.
|
||||
# multipath раскладывает сессию в один из $SLOTS слотов по симметричному
|
||||
# L4-хэшу; basis = POOL_ID, поэтому разные пулы дают независимые раскладки.
|
||||
# Хэш симметричен, то есть прямое и обратное направление дают один слот —
|
||||
# свойство, необходимое для будущего stateless-датапаса.
|
||||
table=10,priority=200,tcp,nw_dst=$VIP,tp_dst=$VIP_PORT actions=multipath(symmetric_l4,$POOL_ID,modulo_n,$SLOTS,0,NXM_NX_REG1[]),goto_table:11
|
||||
|
||||
# Трафик на VIP, для которого листенера нет, — отбрасываем со счётчиком.
|
||||
table=10,priority=150,ip,nw_dst=$VIP actions=drop
|
||||
|
||||
# Прочий IP из публичного сегмента маршрутизируется обычным образом
|
||||
# (клиент может обратиться напрямую к бэкенду, минуя балансировку).
|
||||
table=10,priority=100,ip actions=goto_table:20
|
||||
table=10,priority=0 actions=drop
|
||||
|
||||
# =============================================================================
|
||||
# Таблица 11 — таблица слотов
|
||||
# =============================================================================
|
||||
# Содержимое ($SLOTS правил вида reg1=<слот> -> reg2=<член пула>) рассчитывает
|
||||
# и заливает демон hcd по Maglev-раскладке живых членов пула. Здесь только
|
||||
# основание fail-close: пока пул пуст, трафик на VIP отбрасывается со
|
||||
# счётчиком, а не уходит на заведомо мёртвый бэкенд.
|
||||
#
|
||||
# Счётчики правил этой таблицы дают бесплатную per-member статистику.
|
||||
table=11,priority=0 actions=drop
|
||||
|
||||
# =============================================================================
|
||||
# Таблица 12 — DNAT выбранным членом пула
|
||||
# =============================================================================
|
||||
# Слот положил идентификатор члена в reg2. SNAT не выполняется: бэкенд видит
|
||||
# реальный адрес клиента.
|
||||
table=12,priority=100,ip,reg2=0x1 actions=ct(commit,zone=$CT_ZONE,nat(dst=$BE1_IP:$BE1_PORT),table=20)
|
||||
table=12,priority=100,ip,reg2=0x2 actions=ct(commit,zone=$CT_ZONE,nat(dst=$BE2_IP:$BE2_PORT),table=20)
|
||||
table=12,priority=100,ip,reg2=0x3 actions=ct(commit,zone=$CT_ZONE,nat(dst=$BE3_IP:$BE3_PORT),table=20)
|
||||
table=12,priority=100,ip,reg2=0x4 actions=ct(commit,zone=$CT_ZONE,nat(dst=$BE4_IP:$BE4_PORT),table=20)
|
||||
table=12,priority=0 actions=drop
|
||||
|
||||
# =============================================================================
|
||||
# Таблица 15 — обратный путь из приватных сегментов
|
||||
# =============================================================================
|
||||
# ct(nat) без commit снимает ранее выполненный DNAT: source возвращается к VIP.
|
||||
# Для сессий, которых нет в таблице соединений (трафик be1<->be2, обращения
|
||||
# бэкендов к клиентским префиксам), пакет проходит без изменений.
|
||||
table=15,priority=100,tcp actions=ct(table=16,zone=$CT_ZONE,nat)
|
||||
table=15,priority=50,ip actions=goto_table:20
|
||||
table=15,priority=0 actions=drop
|
||||
|
||||
# =============================================================================
|
||||
# Таблица 16 — после ct
|
||||
# =============================================================================
|
||||
table=16,priority=100,ip actions=goto_table:20
|
||||
table=16,priority=0 actions=drop
|
||||
|
||||
# =============================================================================
|
||||
# Таблица 20 — маршрутизация
|
||||
# =============================================================================
|
||||
# Приоритет = длина префикса, что даёт longest-prefix match средствами
|
||||
# OpenFlow. Дефолтного маршрута нет: неизвестное назначение отбрасывается.
|
||||
#
|
||||
# Собственные адреса узла (/32, приоритет 32) — ответы бэкендов на health-пробы.
|
||||
# TTL для них не уменьшается: пакет адресован самому узлу, а не транзитный.
|
||||
table=20,priority=32,ip,nw_dst=$HC2_IP actions=goto_table:21
|
||||
table=20,priority=32,ip,nw_dst=$HC3_IP actions=goto_table:21
|
||||
table=20,priority=24,ip,nw_dst=$P2_NET actions=dec_ttl,mod_dl_src:$P2_MAC,goto_table:21
|
||||
table=20,priority=24,ip,nw_dst=$P3_NET actions=dec_ttl,mod_dl_src:$P3_MAC,goto_table:21
|
||||
table=20,priority=24,ip,nw_dst=$PUB_NET actions=dec_ttl,mod_dl_src:$PUB_MAC,goto_table:21
|
||||
table=20,priority=0 actions=drop
|
||||
|
||||
# =============================================================================
|
||||
# Таблица 21 — adjacency (next-hop MAC + выходной порт)
|
||||
# =============================================================================
|
||||
# Бэкенды прописаны статически: их MAC фиксирован в docker-compose.yml.
|
||||
table=21,priority=100,ip,nw_dst=$BE1_IP actions=mod_dl_dst:$BE1_MAC,output:$P2_OFPORT
|
||||
table=21,priority=100,ip,nw_dst=$BE2_IP actions=mod_dl_dst:$BE2_MAC,output:$P3_OFPORT
|
||||
table=21,priority=100,ip,nw_dst=$BE3_IP actions=mod_dl_dst:$BE3_MAC,output:$P2_OFPORT
|
||||
table=21,priority=100,ip,nw_dst=$BE4_IP actions=mod_dl_dst:$BE4_MAC,output:$P3_OFPORT
|
||||
# Ответы на health-пробы — в стек узла через internal-порты.
|
||||
table=21,priority=100,ip,nw_dst=$HC2_IP actions=mod_dl_dst:$HC2_MAC,output:$HC2_OFPORT
|
||||
table=21,priority=100,ip,nw_dst=$HC3_IP actions=mod_dl_dst:$HC3_MAC,output:$HC3_OFPORT
|
||||
# priority=90 — записи клиентов, устанавливаемые действием learn из таблицы 0.
|
||||
table=21,priority=0 actions=drop
|
||||
EOF
|
||||
@@ -0,0 +1,70 @@
|
||||
# Единственный источник правды по адресам стенда шага 1.
|
||||
# Используется entrypoint.sh (сборка моста) и pipeline.sh (генерация OpenFlow).
|
||||
# Значения здесь должны совпадать с docker-compose.yml.
|
||||
|
||||
BR=br-lb
|
||||
OF=OpenFlow15
|
||||
CT_ZONE=1
|
||||
|
||||
# --- сеть 1: публичный сегмент (macvlan поверх enp3s0, реальный L2 LAN) -------
|
||||
# IP-адреса ниже существуют ТОЛЬКО в OpenFlow-правилах: ARP-респондер отвечает
|
||||
# на них MAC-адресом PUB_MAC. На интерфейсе адрес не настраивается.
|
||||
PUB_IFNAME=pub0
|
||||
PUB_OFPORT=1
|
||||
PUB_MAC=02:42:c0:a8:05:14
|
||||
PUB_NET=192.168.5.0/24
|
||||
LB_PUB_IP=192.168.5.20
|
||||
VIP=192.168.5.21
|
||||
VIP_PORT=80
|
||||
|
||||
# --- сеть 2: приватный сегмент бэкенда be1 -----------------------------------
|
||||
P2_IFNAME=p2
|
||||
P2_OFPORT=2
|
||||
P2_MAC=02:42:0a:14:00:01
|
||||
P2_NET=10.20.0.0/24
|
||||
LB_P2_IP=10.20.0.1
|
||||
BE1_IP=10.20.0.2
|
||||
BE1_MAC=02:42:0a:14:00:02
|
||||
BE1_PORT=8080
|
||||
BE3_IP=10.20.0.3
|
||||
BE3_MAC=02:42:0a:14:00:03
|
||||
BE3_PORT=8080
|
||||
# Уникальный адрес узла в сегменте — источник health-проб (§7.1 дизайна v1).
|
||||
# Живёт на internal-порту OVS: это единственный адрес, который узел держит в
|
||||
# ядре, весь остальной форвардинг идёт мимо стека.
|
||||
HC2_IFNAME=hcif-p2
|
||||
HC2_OFPORT=4
|
||||
HC2_MAC=02:42:0a:14:00:fd
|
||||
HC2_IP=10.20.0.253
|
||||
|
||||
# --- сеть 3: приватный сегмент бэкенда be2 -----------------------------------
|
||||
P3_IFNAME=p3
|
||||
P3_OFPORT=3
|
||||
P3_MAC=02:42:0a:1e:00:01
|
||||
P3_NET=10.30.0.0/24
|
||||
LB_P3_IP=10.30.0.1
|
||||
BE2_IP=10.30.0.2
|
||||
BE2_MAC=02:42:0a:1e:00:02
|
||||
BE2_PORT=8080
|
||||
BE4_IP=10.30.0.3
|
||||
BE4_MAC=02:42:0a:1e:00:03
|
||||
BE4_PORT=8080
|
||||
HC3_IFNAME=hcif-p3
|
||||
HC3_OFPORT=5
|
||||
HC3_MAC=02:42:0a:1e:00:fd
|
||||
HC3_IP=10.30.0.253
|
||||
|
||||
# --- пул и таблица слотов ----------------------------------------------------
|
||||
# POOL_ID служит basis хэша multipath: разные пулы дают независимые раскладки.
|
||||
POOL_ID=1
|
||||
# Слотов на пул (§5.1 дизайна v1). Определяет гранулярность весов.
|
||||
SLOTS=1024
|
||||
|
||||
# --- health-check ------------------------------------------------------------
|
||||
HC_PROBE=http # http | tcp
|
||||
HC_HTTP_PATH=/healthz
|
||||
HC_INTERVAL=2s
|
||||
HC_TIMEOUT=1s
|
||||
HC_RISE=2 # успехов подряд для перевода в up
|
||||
HC_FALL=3 # неудач подряд для перевода в down
|
||||
HC_API=127.0.0.1:9111
|
||||
Reference in new issue
Block a user