A fully virtual 3-node SEAPATH cluster running on QEMU/KVM, for local development and testing without physical hardware.
libvirt/qemu-kvminstalled and running (systemctl status libvirtd)terraform>= 1.3 with the dmacvicar/libvirt providervirshCLI (usually part oflibvirt-client)openvswitchinstalled and running (systemctl status openvswitch) — backs the cluster ring segments so OVS RSTP BPDUs flow between guests (Linux bridges drop them)- Passwordless
sudotoovs-vsctlfor the current user —make apply/make destroyrunsudo ovs-vsctl add-br/del-brto manage the host-side ring bridges. Drop a file in/etc/sudoers.d/such as:<your-user> ALL=(root) NOPASSWD: /usr/bin/ovs-vsctl ansible2.16 — installed byprepare.shin the SEAPATH Ansible repo (see Quick Start)- A SEAPATH qcow2 image with an
ansibleuser whose~/.ssh/authorized_keyscontains your public key - Fencing (STONITH) requires
fence-virtandfence-virtdpackages on the host (on Fedora alsofence-virtd-tcpandfence-virtd-libvirt; Ubuntu bundles these into the main package). Usescripts/fence-setup-host.shfor one-shot setup. Thefence-virtpackage is also needed inside the VMs (make fence-setuphandles this).
# 1. Clone this repo and the SEAPATH Ansible repo as siblings
git clone https://github.com/seapath/virtual-cluster.git
git clone https://github.com/seapath/ansible.git
# 2. Install Ansible dependencies from the Ansible repo
cd ansible
./prepare.sh
cd ../seapath-virtual-sandbox
# 3. Copy and edit the Terraform variable file
cp terraform.tfvars.example terraform/terraform.tfvars
$EDITOR terraform/terraform.tfvars # Set base_image_path at minimum
# 4. Initialise Terraform and create the VMs
make init
make apply
# 5. Verify SSH connectivity
make ansible-ping
# 6. Run the full SEAPATH setup
make ansible-setupThe Makefile expects the SEAPATH Ansible repo at ../ansible by default. Override with:
make ansible-setup ANSIBLE_REPO=/path/to/ansible- Mode: NAT, CIDR
192.168.100.0/24 - DHCP reservations via XSLT injection (MAC → IP):
| Node | MAC | IP |
|---|---|---|
| node1 | 52:54:00:aa:bb:01 |
192.168.100.101 |
| node2 | 52:54:00:aa:bb:02 |
192.168.100.102 |
| node3 | 52:54:00:aa:bb:03 |
192.168.100.103 |
Three isolated L2 segments wire the nodes in a ring:
| Network | Node A side | Node B side |
|---|---|---|
seapath-cluster-12 |
node1 NIC2 (team0_0) |
node2 NIC3 (team0_1) |
seapath-cluster-23 |
node2 NIC2 (team0_0) |
node3 NIC3 (team0_1) |
seapath-cluster-31 |
node3 NIC2 (team0_0) |
node1 NIC3 (team0_1) |
Each segment is backed by a dedicated host-side Open vSwitch bridge (ovs-ring12/23/31) rather than a libvirt-managed Linux bridge. This is required because the Linux bridge driver drops STP BPDUs (01:80:C2:00:00:00) — a behaviour hardcoded in the kernel via BR_GROUPFWD_RESTRICTED and not overridable via group_fwd_mask. Without BPDU forwarding the guest-side OVS RSTP never breaks the ring, and the resulting broadcast loop stalls Ceph mon election. OVS forwards BPDUs transparently, so the guests can converge to a proper RSTP topology.
Cluster IPs (assigned statically by Ansible): node1=192.168.55.1, node2=192.168.55.2, node3=192.168.55.3.
Fixed PCI slot addresses are injected via XSLT so the guest OS sees predictable names:
| Slot | NIC | Interface |
|---|---|---|
0x03 |
NIC1 admin | enp0s3 |
0x04 |
NIC2 team0_0 | enp0s4 |
0x05 |
NIC3 team0_1 | enp0s5 |
| Target | Description |
|---|---|
init |
Initialise Terraform (run once) |
plan |
Show planned changes |
apply |
Create/update VMs and networks |
destroy |
Tear down everything |
start |
Start all VMs |
stop |
Gracefully stop all VMs |
snapshot |
Snapshot all VMs (default name: default) |
restore |
Restore all VMs to a snapshot |
snapshot-list |
List snapshots for all VMs |
snapshot-delete |
Delete all snapshots for all VMs |
ssh-node{1,2,3} |
SSH into a node |
console-node{1,2,3} |
Open virsh serial console |
ansible-ping |
Test SSH connectivity |
ansible-setup |
Full SEAPATH setup |
ansible-setup-network |
Network configuration only |
ansible-setup-ceph |
Ceph deployment only |
ansible-setup-ha |
HA (Pacemaker/Corosync) only |
fence-key-gen |
Generate shared key for fence_virt ↔ fence_virtd |
fence-key-push |
Install fence-virt on VMs and push the shared key |
fence-virtd-config |
Print a sample fence_virt.conf for the host |
fence-setup |
Run fence-key-gen + fence-key-push |
Override the snapshot name with SNAPSHOT:
make snapshot SNAPSHOT=after-network
make restore SNAPSHOT=after-networkAll virsh commands default to qemu:///system. Override with LIBVIRT_URI if needed:
make start LIBVIRT_URI=qemu:///sessionPass extra Ansible flags with ANSIBLE_OPTS:
make ansible-setup ANSIBLE_OPTS="-v --check"make init— Terraform initialises without errorsmake apply— 3 VMs created, 4 networks visible invirsh net-listmake ansible-ping— All 3 nodes respondmake ansible-setup-network— OVS bridgeteam0visible on each nodevirsh console seapath-node1— Ring interfaces (enp0s4,enp0s5) are upmake fence-setup && make ansible-setup-ha— STONITH fencing enabled, 3 fence_virt primitives registered in Pacemakerssh ansible@192.168.100.102 "sudo /bin/sh -c 'fence_virt -T 192.168.100.1 -p 1229 -k /etc/cluster/fence_virt.key -o list'"— VM can reach fence_virtd on the host
PCI slot conflicts: If libvirt already places a device at slot 0x03–0x05, the XSLT will fail or produce duplicate addresses. Bump the slots to 0x06/0x07/0x08 in xslt/domain-pci.xsl and update the iface_* variables and the inventory accordingly.
Image prerequisites: The qcow2 image must have an ansible user with your SSH public key pre-loaded. This is the responsibility of the SEAPATH image build, not this sandbox.
Ceph deployment path: The inventory is compatible with both ceph-ansible and cephadm. The actual path is auto-detected by the detect_seapath_distro role based on the OS in the image.
No PTP: ptp_interface is intentionally omitted — there is no PTP hardware in a virtual sandbox.
Resource usage: Each node uses 4 GiB RAM and 4 vCPUs by default, plus 20 GiB for the Ceph OSD disk. A full 3-node cluster requires at least 12 GiB free RAM on the host.
STONITH fencing requires fence_virtd on the host: Fencing uses fence_virt inside the VMs talking to fence_virtd on the host via TCP (port 1229). If fence_virtd is not running, fencing probes fail and stonith resources show as "Stopped" in Pacemaker. Use scripts/fence-setup-host.sh for one-shot host configuration; this setup is out of scope for Terraform by design (the host OS is not managed by this sandbox). After terraform destroy && make apply, the shared key must be pushed again with make fence-setup.