evpn_setup.sh— универсальный, запускается на каждой ВМ (нода сама определяет себя по IP192.168.200.x). Ставит FRR (если нет), настраивает bridge+VXLAN+netns, генерируетfrr.confи перезапускает FRR.test_evpn.sh— тест: BGP, VNI, ping'и из netns, MAC-таблицы.deploy.sh— раскатка со своей рабочей машины на все 3 ноды по SSH + автотест.
Итоговая адресация:
| ВМ | underlay | AS | netns | IP в netns |
|---|---|---|---|---|
| node130 | 192.168.200.130 | 65001 | ns130 | 10.0.0.130/24 |
| node131 | 192.168.200.131 | 65002 | ns131 | 10.0.0.131/24 |
| node132 | 192.168.200.132 | 65003 | ns132 | 10.0.0.132/24 |
#!/usr/bin/env bash
# evpn_setup.sh — нода L2 EVPN-VXLAN (FRR) + netns
# Использование: sudo ./evpn_setup.sh [130|131|132] [underlay-iface]
set -euo pipefail
[[ $EUID -eq 0 ]] || { echo "запускать от root"; exit 1; }
as_of() { case "$1" in 130) echo 65001;; 131) echo 65002;; 132) echo 65003;; esac; }
# ---------- номер ноды ----------
NODE="${1:-$(hostname -I | tr ' ' '\n' | awk -F. '$1==192&&$2==168&&$3==200{print $4; exit}')}"
case "$NODE" in
130) PEERS="131 132";; 131) PEERS="130 132";; 132) PEERS="130 131";;
*) echo "не знаю ноду '$NODE' (жду 130/131/132)"; exit 1;;
esac
IP_U="192.168.200.$NODE"; VNI=10010; VX="vxlan$VNI"; BR=br0
NS="ns$NODE"; NS_IP="10.0.0.$NODE/24"
# ---------- underlay-интерфейс ----------
IF_U="${2:-$(ip -4 -o addr show | awk -F' +|/' -v ip="$IP_U" '$4==ip{print $2; exit}')}"
[[ -n "$IF_U" ]] || IF_U=$(ip route get "$IP_U" 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i=="dev"){print $(i+1); exit}}')
[[ -n "$IF_U" ]] || IF_U=eth1
echo ">>> node$NODE (AS $(as_of "$NODE")), underlay: $IF_U = $IP_U/24"
# ---------- sysctl ----------
modprobe vxlan 2>/dev/null || true
sysctl -qw net.ipv4.ip_forward=1
sysctl -qw net.ipv4.conf.all.rp_filter=0
sysctl -qw "net.ipv4.conf.$IF_U.rp_filter=0" 2>/dev/null || true
# ---------- underlay ----------
ip link set "$IF_U" up
ip -4 addr show dev "$IF_U" | grep -qw "$IP_U" || ip addr add "$IP_U/24" dev "$IF_U"
# ---------- bridge + VTEP ----------
ip link add "$BR" type bridge 2>/dev/null || true
ip link set "$BR" mtu 1450; ip link set "$BR" up
ip link show "$VX" &>/dev/null || ip link add "$VX" type vxlan id "$VNI" local "$IP_U" dstport 4789 nolearning
ip link set "$VX" type vxlan local "$IP_U" dstport 4789 nolearning 2>/dev/null || true
ip link set "$VX" mtu 1450; ip link set "$VX" master "$BR" 2>/dev/null || true
ip link set "$VX" up
# ---------- netns + veth ----------
ip netns list | awk '{print $1}' | grep -qx "$NS" || ip netns add "$NS"
if ! ip link show veth_h &>/dev/null; then
ip link add veth_h type veth peer name veth_ns
ip link set veth_h mtu 1450; ip link set veth_h master "$BR"; ip link set veth_h up
ip link set veth_ns netns "$NS"
fi
ip -n "$NS" link set lo up
ip -n "$NS" link set veth_ns mtu 1450 up
ip -n "$NS" -4 addr show dev veth_ns | grep -qw "10.0.0.$NODE" || ip -n "$NS" addr add "$NS_IP" dev veth_ns
# ---------- firewall ----------
if command -v iptables &>/dev/null; then
iptables -C INPUT -p udp --dport 4789 -j ACCEPT 2>/dev/null || iptables -I INPUT 1 -p udp --dport 4789 -j ACCEPT
iptables -C INPUT -p tcp --dport 179 -j ACCEPT 2>/dev/null || iptables -I INPUT 1 -p tcp --dport 179 -j ACCEPT
fi
# ---------- FRR ----------
if ! command -v vtysh &>/dev/null; then
export DEBIAN_FRONTEND=noninteractive
apt-get update -qq && apt-get install -y -qq frr
fi
touch /etc/frr/daemons
grep -q '^zebra=' /etc/frr/daemons && sed -i 's/^zebra=.*/zebra=yes/' /etc/frr/daemons || echo 'zebra=yes' >> /etc/frr/daemons
grep -q '^bgpd=' /etc/frr/daemons && sed -i 's/^bgpd=.*/bgpd=yes/' /etc/frr/daemons || echo 'bgpd=yes' >> /etc/frr/daemons
NEIGH_CFG=""; NEIGH_ACT=""
for p in $PEERS; do
NEIGH_CFG+=" neighbor 192.168.200.$p remote-as $(as_of "$p")"$'\n'
NEIGH_ACT+=" neighbor 192.168.200.$p activate"$'\n'
done
cat > /etc/frr/frr.conf <<EOF
frr version 8.4
frr defaults traditional
hostname node$NODE
log file /var/log/frr/frr.log
service integrated-vtysh-config
!
ip forwarding
!
router bgp $(as_of "$NODE")
bgp router-id $IP_U
no bgp ebgp-requires-policy
${NEIGH_CFG} address-family l2vpn evpn
${NEIGH_ACT} advertise-all-vni
exit-address-family
!
line vty
!
end
EOF
chown frr:frr /etc/frr/frr.conf /etc/frr/daemons 2>/dev/null || true
systemctl enable frr >/dev/null 2>&1 || true
systemctl restart frr
echo ">>> итог:"
ip -br addr show "$IF_U"; ip -br link show "$BR" "$VX"
ip netns list | grep "^$NS"
echo ">>> жду сходимость BGP 5 c..."; sleep 5
vtysh -c 'show bgp l2vpn evpn summary' || true
echo ">>> готово. Тест: ./test_evpn.sh $NODE"#!/usr/bin/env bash
# test_evpn.sh — проверка L2 связности netns через EVPN-VXLAN
set -uo pipefail
NODE="${1:-$(hostname -I | tr ' ' '\n' | awk -F. '$1==192&&$2==168&&$3==200{print $4; exit}')}"
case "$NODE" in 130|131|132) ;; *) echo "usage: $0 [130|131|132]"; exit 1;; esac
NS="ns$NODE"; NS_IP="10.0.0.$NODE"; VNI=10010; RC=0
section() { echo; echo "===== $* ====="; }
section "$NS ($NS_IP): BGP EVPN"
vtysh -c 'show bgp l2vpn evpn summary'
section "VNI"
vtysh -c 'show evpn vni'
section "ping из $NS"
for peer in 130 131 132; do
[[ "$peer" == "$NODE" ]] && continue
if ip netns exec "$NS" ping -c 3 -W 2 -q "10.0.0.$peer" >/tmp/p.$$ 2>&1; then
echo " OK $NS_IP -> 10.0.0.$peer ($(grep -o '[0-9]*% packet loss' /tmp/p.$$ | head -1))"
else
echo " FAIL $NS_IP -> 10.0.0.$peer"; tail -3 /tmp/p.$$; RC=1
fi
done
rm -f /tmp/p.$$
section "ARP-кэш $NS (выученные MAC соседей)"
ip netns exec "$NS" ip neigh
section "MAC-таблица EVPN (VNI $VNI)"
vtysh -c "show evpn mac vni $VNI"
section "remote VTEP в FDB vxlan$VNI"
bridge fdb show dev vxlan$VNI | grep dst || echo " (пусто — повторите через пару секунд после ping)"
section "итог"
[[ $RC -eq 0 ]] && echo "ВСЁ ОК" || echo "ЕСТЬ ПРОБЛЕМЫ"
exit $RC
# --- если FAIL, смотреть: udp/4789 (tcpdump -ni <if> udp port 4789),
# tcp/179 (BGP), firewall, MTU 1450 на br0/veth, соседей Established#!/usr/bin/env bash
set -euo pipefail
NODES=(130 131 132)
DIR=$(cd "$(dirname "$0")" && pwd)
SSH_USER="${SSH_USER:-root}" # SSH_USER=admin ./deploy.sh
RUN='bash'; [[ "$SSH_USER" == "root" ]] || RUN='sudo bash'
for n in "${NODES[@]}"; do
h="$SSH_USER@192.168.200.$n"
echo "===== $h: копирование ====="
scp -q "$DIR/evpn_setup.sh" "$DIR/test_evpn.sh" "$h:/tmp/"
echo "===== $h: настройка ====="
ssh "$h" "$RUN /tmp/evpn_setup.sh"
done
echo "===== сходимость BGP: 10 c ====="; sleep 10
FAIL=0
for n in "${NODES[@]}"; do
h="$SSH_USER@192.168.200.$n"
echo "===== $h: тест ====="
ssh "$h" "$RUN /tmp/test_evpn.sh" || FAIL=1
done
exit $FAIL# с рабочей машины:
ssh-copy-id root@192.168.200.130 root@192.168.200.131 root@192.168.200.132
./deploy.sh
# либо вручную на каждой ВМ:
sudo bash evpn_setup.sh # нода определится по своему IP
./test_evpn.shПримечания:
- скрипт идемпотентный — можно перезапускать, ничего не задублируется;
- если underlay-IP ещё не назначен на ВМ — передайте интерфейс явно:
evpn_setup.sh 130 ens4; - откат на ноде:
ip netns del ns130; ip link del veth_h; ip link del vxlan10010; ip link del br0; - первый ping может не пройти 1–2 попытки (ARP идёт как BUM, потом MAC'и уйдут в EVPN Type-2) — тест это переживает,
-c 3.