This document outlines the setup for a dynamically provisioned systemd-vmspawn multi-tenant test environment for bbctl that uses VyOS, WireGuard, VXLAN, OSPF, and L3VPN technologies.
The lab will consist of:
- Management Plane: Secure WireGuard overlay network for router management
- Service Provider Network: OSPF-based core network with BGP EVPN for tenant isolation
- Tenant Networks: L3VPN with VXLAN encapsulation for tenant traffic
- Integration Points: API endpoints for bbctl to manage and automate infrastructure
┌────────────────────────────────────────────────────────────────────┐
│ Host System │
│ │
│ ┌─────────────────────┐ ┌─────────────────────┐ │
│ │ systemd-vmspawn │ │ systemd-vmspawn │ │
│ │ │ │ │ │
│ │ ┌───────────────┐ │ │ ┌───────────────┐ │ │
│ │ │ VyOS Router │ │ │ │ VyOS Router │ │ ... │
│ │ │ (PE1) │◄─┼──┼──► (PE2) │ │ │
│ │ └──────┬────────┘ │ │ └──────┬────────┘ │ │
│ │ │ │ │ │ │ │
│ │ ┌──────┴────────┐ │ │ ┌──────┴────────┐ │ │
│ │ │ Tenant VMs │ │ │ │ Tenant VMs │ │ │
│ │ └───────────────┘ │ │ └───────────────┘ │ │
│ └─────────────────────┘ └─────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ Management Bridge │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ Data Bridge │ │
│ └────────────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────────────┘
-
Host Setup:
- Arch Linux (as specified in your vyos-network-plan.md)
- systemd-vmspawn for container deployment
- Linux bridge setup for network connectivity
-
Network Configuration:
- Management network (172.27.0.0/16)
- Backbone network (172.16.0.0/16)
- Public IP space simulation (5.254.54.0/26)
- Tenant space (100.64.0.0/16)
We'll create two types of VyOS images:
- Base VyOS Image: Minimal image with core functionality
- Provider Edge Router Image: Pre-configured with L3VPN, EVPN, and WireGuard
#!/bin/bash
# Setup script for VyOS lab base infrastructure
# Create network bridges
ip link add br-mgmt type bridge
ip link add br-data type bridge
ip link set br-mgmt up
ip link set br-data up
# Assign management IP
ip addr add 172.27.0.1/16 dev br-mgmt
# Setup NAT for outbound connectivity
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE#!/bin/bash
# Build VyOS base image for systemd-vmspawn
# Create VyOS image using mkosi
cat > mkosi.default << EOF
[Distribution]
Distribution=vyos
Release=current
[Output]
Format=disk
Output=vyos-base.img
Size=2G
EOF
# Run mkosi to build the image
mkosi#!/bin/bash
# Deploy a VyOS Provider Edge router using systemd-vmspawn
ROUTER_ID=$1
ROUTER_NAME="vyos-pe${ROUTER_ID}"
ROUTER_MGMT_IP="172.27.0.${ROUTER_ID}0"
ROUTER_BACKBONE_IP="172.16.0.${ROUTER_ID}"
# Create cloud-init configuration
cat > cloud-init.yaml << EOF
#cloud-config
vyos_config_commands:
# Setup system basics
- set system host-name ${ROUTER_NAME}
# Setup management interface
- set interfaces ethernet eth0 address ${ROUTER_MGMT_IP}/16
- set interfaces ethernet eth0 description 'Management'
# Setup backbone interface
- set interfaces ethernet eth1 address ${ROUTER_BACKBONE_IP}/16
- set interfaces ethernet eth1 description 'Backbone'
# Setup OSPF
- set protocols ospf area 0 network ${ROUTER_BACKBONE_IP}/16
# Enable HTTP API
- set service https api keys id admin key 'bbctl-test-api'
- set service https listen-address 0.0.0.0
EOF
# Create systemd-vmspawn service
cat > /etc/systemd/system/${ROUTER_NAME}.service << EOF
[Unit]
Description=VyOS PE${ROUTER_ID} Router
After=network.target
[Service]
Type=notify
ExecStart=/usr/bin/systemd-vmspawn --network-bridge=br-mgmt --network-bridge=br-data -i /var/lib/machines/vyos-base.img --cloud-init=cloud-init.yaml -n ${ROUTER_NAME}
ExecStop=/usr/bin/machinectl poweroff ${ROUTER_NAME}
KillMode=mixed
Restart=on-failure
TimeoutStartSec=300
[Install]
WantedBy=multi-user.target
EOF
# Start the service
systemctl enable --now ${ROUTER_NAME}.service#!/bin/bash
# Configure L3VPN with EVPN for a VyOS router
ROUTER_ID=$1
ROUTER_NAME="vyos-pe${ROUTER_ID}"
AS_NUMBER=65000
ROUTER_LOOPBACK="172.29.255.${ROUTER_ID}"
# Configure BGP, EVPN, and L3VPN
vyos_config_commands="
# Configure loopback
set interfaces dummy dum0 address ${ROUTER_LOOPBACK}/32
# Configure BGP
set protocols bgp system-as ${AS_NUMBER}
set protocols bgp parameters router-id ${ROUTER_LOOPBACK}
# Configure EVPN
set protocols bgp neighbor 172.29.255.1 remote-as ${AS_NUMBER}
set protocols bgp neighbor 172.29.255.1 update-source dum0
set protocols bgp neighbor 172.29.255.1 address-family l2vpn-evpn activate
set protocols bgp neighbor 172.29.255.2 remote-as ${AS_NUMBER}
set protocols bgp neighbor 172.29.255.2 update-source dum0
set protocols bgp neighbor 172.29.255.2 address-family l2vpn-evpn activate
set protocols bgp l2vpn-evpn advertise-all-vni
# Configure tenant VRFs
set vrf name tenant1 table 2000
set vrf name tenant1 protocols bgp address-family ipv4-unicast route-target vpn export '${AS_NUMBER}:2000'
set vrf name tenant1 protocols bgp address-family ipv4-unicast route-target vpn import '${AS_NUMBER}:2000'
set vrf name tenant2 table 3000
set vrf name tenant2 protocols bgp address-family ipv4-unicast route-target vpn export '${AS_NUMBER}:3000'
set vrf name tenant2 protocols bgp address-family ipv4-unicast route-target vpn import '${AS_NUMBER}:3000'
# Configure VXLAN interfaces
set interfaces vxlan vxlan2000 vni 2000
set interfaces vxlan vxlan2000 source-address ${ROUTER_LOOPBACK}
set interfaces vxlan vxlan2000 mtu 9000
set interfaces vxlan vxlan2000 vrf tenant1
set interfaces vxlan vxlan3000 vni 3000
set interfaces vxlan vxlan3000 source-address ${ROUTER_LOOPBACK}
set interfaces vxlan vxlan3000 mtu 9000
set interfaces vxlan vxlan3000 vrf tenant2
"
# Apply configuration to the router
machinectl shell ${ROUTER_NAME} /opt/vyatta/bin/vyatta-cfg-cmd-wrapper begin
echo "$vyos_config_commands" | while read cmd; do
if [[ -n "$cmd" && ! "$cmd" =~ ^# ]]; then
machinectl shell ${ROUTER_NAME} /opt/vyatta/bin/vyatta-cfg-cmd-wrapper "$cmd"
fi
done
machinectl shell ${ROUTER_NAME} /opt/vyatta/bin/vyatta-cfg-cmd-wrapper commit
machinectl shell ${ROUTER_NAME} /opt/vyatta/bin/vyatta-cfg-cmd-wrapper save#!/bin/bash
# Configure WireGuard for secure management plane
ROUTER_ID=$1
ROUTER_NAME="vyos-pe${ROUTER_ID}"
WG_PRIVATE_KEY=$(machinectl shell ${ROUTER_NAME} generate pki wireguard | grep 'Private key:' | cut -d' ' -f3)
WG_PUBLIC_KEY=$(machinectl shell ${ROUTER_NAME} generate pki wireguard show-public | grep 'Public key:' | cut -d' ' -f3)
WG_ADDRESS="172.27.100.${ROUTER_ID}/24"
WG_PORT=$((51820 + ROUTER_ID))
# Configure WireGuard interface
vyos_config_commands="
# WireGuard interface for secure management
set interfaces wireguard wg0 address ${WG_ADDRESS}
set interfaces wireguard wg0 description 'Secure Management Plane'
set interfaces wireguard wg0 port ${WG_PORT}
set interfaces wireguard wg0 private-key ${WG_PRIVATE_KEY}
"
# Add peer configurations based on the router ID
if [ "$ROUTER_ID" -eq "1" ]; then
# Router 1 peers with 2
vyos_config_commands+="
set interfaces wireguard wg0 peer PE2 allowed-ips 172.27.100.2/32
set interfaces wireguard wg0 peer PE2 persistent-keepalive 25
# Replace with actual public key from PE2
set interfaces wireguard wg0 peer PE2 public-key REPLACE_WITH_PE2_PUBLIC_KEY
"
elif [ "$ROUTER_ID" -eq "2" ]; then
# Router 2 peers with 1
vyos_config_commands+="
set interfaces wireguard wg0 peer PE1 allowed-ips 172.27.100.1/32
set interfaces wireguard wg0 peer PE1 persistent-keepalive 25
# Replace with actual public key from PE1
set interfaces wireguard wg0 peer PE1 public-key REPLACE_WITH_PE1_PUBLIC_KEY
"
fi
# Apply configuration to the router
machinectl shell ${ROUTER_NAME} /opt/vyatta/bin/vyatta-cfg-cmd-wrapper begin
echo "$vyos_config_commands" | while read cmd; do
if [[ -n "$cmd" && ! "$cmd" =~ ^# ]]; then
machinectl shell ${ROUTER_NAME} /opt/vyatta/bin/vyatta-cfg-cmd-wrapper "$cmd"
fi
done
machinectl shell ${ROUTER_NAME} /opt/vyatta/bin/vyatta-cfg-cmd-wrapper commit
machinectl shell ${ROUTER_NAME} /opt/vyatta/bin/vyatta-cfg-cmd-wrapper save
# Output the public key for use in other routers
echo "WireGuard public key for ${ROUTER_NAME}: ${WG_PUBLIC_KEY}"#!/bin/bash
# Deploy a tenant VM
TENANT_ID=$1
VM_ID=$2
ROUTER_ID=$3
ROUTER_NAME="vyos-pe${ROUTER_ID}"
VM_NAME="tenant${TENANT_ID}-vm${VM_ID}"
VRF_ID=$((1000 + TENANT_ID * 1000))
VM_IP="10.${TENANT_ID}.${ROUTER_ID}.${VM_ID}"
# Create a simple VM image
qemu-img create -f qcow2 ${VM_NAME}.qcow2 5G
# Create systemd-vmspawn service for the VM
cat > /etc/systemd/system/${VM_NAME}.service << EOF
[Unit]
Description=Tenant ${TENANT_ID} VM ${VM_ID}
After=${ROUTER_NAME}.service
[Service]
Type=notify
ExecStart=/usr/bin/systemd-vmspawn --network-zone=${ROUTER_NAME} -i ${VM_NAME}.qcow2 -n ${VM_NAME}
ExecStop=/usr/bin/machinectl poweroff ${VM_NAME}
KillMode=mixed
Restart=on-failure
TimeoutStartSec=300
[Install]
WantedBy=multi-user.target
EOF
# Start the VM
systemctl enable --now ${VM_NAME}.service
# Configure tenant network on the router
vyos_config_commands="
# Add interface for tenant VM
set interfaces ethernet eth${VM_ID + 1} vrf tenant${TENANT_ID}
set interfaces ethernet eth${VM_ID + 1} address ${VM_IP}/24
set interfaces ethernet eth${VM_ID + 1} description 'Tenant ${TENANT_ID} VM ${VM_ID}'
"
# Apply configuration to the router
machinectl shell ${ROUTER_NAME} /opt/vyatta/bin/vyatta-cfg-cmd-wrapper begin
echo "$vyos_config_commands" | while read cmd; do
if [[ -n "$cmd" && ! "$cmd" =~ ^# ]]; then
machinectl shell ${ROUTER_NAME} /opt/vyatta/bin/vyatta-cfg-cmd-wrapper "$cmd"
fi
done
machinectl shell ${ROUTER_NAME} /opt/vyatta/bin/vyatta-cfg-cmd-wrapper commit
machinectl shell ${ROUTER_NAME} /opt/vyatta/bin/vyatta-cfg-cmd-wrapper saveLet's create a master orchestration script to deploy the entire testbed:
#!/bin/bash
# Master orchestration script for VyOS lab deployment
set -e
# Setup base infrastructure
echo "Setting up base infrastructure..."
./setup-base.sh
# Build VyOS image
echo "Building VyOS base image..."
./build-vyos-image.sh
# Deploy Provider Edge routers
echo "Deploying Provider Edge routers..."
./deploy-pe-router.sh 1
./deploy-pe-router.sh 2
# Store WireGuard public keys
PE1_PUBKEY=$(./setup-wireguard.sh 1 | grep "public key" | awk '{print $6}')
PE2_PUBKEY=$(./setup-wireguard.sh 2 | grep "public key" | awk '{print $6}')
# Update WireGuard peers with correct public keys
sed -i "s/REPLACE_WITH_PE2_PUBLIC_KEY/$PE2_PUBKEY/" wireguard-config.sh
sed -i "s/REPLACE_WITH_PE1_PUBLIC_KEY/$PE1_PUBKEY/" wireguard-config.sh
# Finalize WireGuard setup
./wireguard-config.sh 1
./wireguard-config.sh 2
# Configure L3VPN/EVPN
echo "Configuring L3VPN/EVPN..."
./setup-l3vpn-evpn.sh 1
./setup-l3vpn-evpn.sh 2
# Deploy tenant VMs
echo "Deploying tenant VMs..."
./deploy-tenant-vm.sh 1 1 1 # Tenant 1, VM 1, on Router 1
./deploy-tenant-vm.sh 1 2 2 # Tenant 1, VM 2, on Router 2
./deploy-tenant-vm.sh 2 1 1 # Tenant 2, VM 1, on Router 1
./deploy-tenant-vm.sh 2 2 2 # Tenant 2, VM 2, on Router 2
echo "Lab deployment complete!"
echo "Management IPs:"
echo " PE1: 172.27.0.10"
echo " PE2: 172.27.0.20"
echo "WireGuard Management IPs:"
echo " PE1: 172.27.100.1"
echo " PE2: 172.27.100.2"
echo "API access:"
echo " PE1: https://172.27.0.10/api/ (key: bbctl-test-api)"
echo " PE2: https://172.27.0.20/api/ (key: bbctl-test-api)"Now, let's set up the bbctl CLI to work with our lab environment. We'll create integration scripts and update the CLI with appropriate command-line options.
Create a configuration file for bbctl to access the test environment:
# bbctl test configuration for VyOS lab
[providers]
[providers.vyos-pe1]
provider_type = "VyOS"
name = "vyos-pe1"
host = "172.27.0.10"
params = { network_type = "l3vpn-evpn" }
[providers.vyos-pe2]
provider_type = "VyOS"
name = "vyos-pe2"
host = "172.27.0.20"
params = { network_type = "l3vpn-evpn" }
[regions]
[regions.region1]
id = "region1"
name = "Region 1"
provider = "VyOS"
location = "Local DC 1"
available = true
[regions.region2]
id = "region2"
name = "Region 2"
provider = "VyOS"
location = "Local DC 2"
available = true
[credentials]
[credentials.vyos-pe1]
username = "vyos"
api_key = "bbctl-test-api"
ssh_port = 22
api_port = 443
[credentials.vyos-pe2]
username = "vyos"
api_key = "bbctl-test-api"
ssh_port = 22
api_port = 443Sample commands to test bbctl with the lab environment:
# Test connection to VyOS routers
bbctl test-vyos --host 172.27.0.10 --port 22 --username vyos --api-key bbctl-test-api
# List instances
bbctl instances list
# Create a new instance on the lab
bbctl instances create test-vm --provider vyos-pe1 --region region1 --cpu 1 --memory 1 --disk 5
# Create a new network
bbctl networks create tenant-net --cidr 10.100.0.0/24
# Connect instance to network
bbctl networks connect tenant-net --instance $INSTANCE_IDThe following methods can be used to verify and troubleshoot the test environment:
-
Verify OSPF adjacencies:
show ip ospf neighbor -
Verify BGP EVPN:
show bgp l2vpn evpn -
Verify L3VPN routes:
show ip route vrf all -
Verify WireGuard status:
show interfaces wireguard -
Test connectivity between tenants:
# From tenant1-vm1 ping 10.1.2.1 # Should work ping 10.2.1.1 # Should fail due to VRF isolation
- Add support for Docker container deployment
- Implement automated testing with the lab
- Add CI/CD pipeline for continuous testing
- Extend the lab with additional provider types (Proxmox)
- Implement high availability scenarios