WireGuard Mesh
Topology
Full mesh with the bastion as the central hub. All peers connect to the bastion; Kubernetes nodes and Proxmox hosts are spokes.
graph TB
subgraph Bastion["Bastion (OVH VPS)"]
WG0_B[wg0 - Hub]
end
subgraph K8s["Kubernetes Nodes"]
CP[Control Plane]
W1[Worker 1]
W2[Worker 2]
end
subgraph Proxmox["Proxmox Hosts"]
ED[EliteDesk]
TC[ThinkCentre]
end
subgraph LXCs["LXCs (behind NAT)"]
CT101[SigNoz - 10.0.0.201]
CT100[OneUptime - 10.0.0.51]
end
WG0_B --- CP
WG0_B --- W1
WG0_B --- W2
WG0_B --- ED
WG0_B --- TC
ED -->|NAT forward| CT101
TC -->|NAT forward| CT100Peer Configuration
| Peer | WireGuard IP | PSK | Notes |
|---|---|---|---|
| bastion | 10.10.0.1 | — | Hub node |
| talos-cp | 10.10.1.1 | No | Talos v1.13 does not support PresharedKey |
| talos-w1 | 10.10.1.2 | No | Talos v1.13 does not support PresharedKey |
| talos-w2 | 10.10.1.3 | No | Talos v1.13 does not support PresharedKey |
| EliteDesk | 10.10.0.150 | Yes | NAT to SigNoz LXC 10.0.0.201 |
| ThinkCentre | 10.10.0.50 | Yes | NAT to OneUptime LXC 10.0.0.51 |
| Mac | 10.10.0.10 | — | Client |
| ER706W router | 10.10.0.20 | — | Home site-to-site |
Proxmox Host Forwarding
Each Proxmox host forwards traffic to its LXCs via:
- IP forwarding enabled in sysctl (managed by the
commonAnsible role) - nftables MASQUERADE rule for WireGuard-to-LXC traffic
NAT rules are now persistent via nftables (not iptables). The bastion and Proxmox hosts use a table ip nat with MASQUERADE for traffic from the WireGuard interface to LXC destinations. This is configured via the firewall_nat_destinations Ansible variable.
# Example nftables NAT rule (bastion/Proxmox hosts)
table ip nat {
chain postrouting {
type nat hook postrouting priority 100; policy accept;
oifname "wg0" ip daddr 10.0.0.0/24 masquerade
}
}INFO
The WireGuard template no longer includes redundant sysctl PostUp commands -- IP forwarding is handled by the common Ansible role.
Bastion Routing
The bastion uses PostUp routes to reach LXC IPs through the respective Proxmox host WireGuard addresses:
[Peer] # EliteDesk
AllowedIPs = 10.10.0.150/32, 10.0.0.201/32
[Peer] # ThinkCentre
AllowedIPs = 10.10.0.50/32, 10.0.0.51/32MetalLB VIP Routing
MetalLB L2 mode assigns VIP 10.10.1.200 to the Traefik LoadBalancer service. However, MetalLB L2 uses ARP announcements which do not cross WireGuard tunnels. The bastion must have the VIP in its WireGuard AllowedIPs for the node that owns the VIP.
[Peer] # K8s node holding MetalLB VIP
AllowedIPs = 10.10.1.1/32, 10.10.1.200/32The bastion Ansible variable k8s_traefik_upstream controls which IP Caddy uses as the Traefik upstream. With MetalLB, this is set to 10.10.1.200 (the VIP) instead of individual node IPs.
WARNING
If MetalLB moves the VIP to a different node (e.g., after a node failure), update the AllowedIPs in the WireGuard config for the new node peer. This is managed via inventories/production/hosts.yml.
OTel Relay Pattern
K8s nodes have NO route to 10.0.0.x (home LAN). Telemetry from the K8s OTel DaemonSet is sent to the bastion relay (10.10.0.1:4317), which forwards to SigNoz at 10.0.0.201:4317.
K8s DaemonSet → 10.10.0.1:4317 (bastion relay) → 10.0.0.201:4317 (SigNoz)Ansible Management
Two Jinja2 templates handle WireGuard configuration:
| Template | Target |
|---|---|
wg0.conf.j2 | Bastion — generates full peer list |
wg0-proxmox.conf.j2 | Proxmox hosts — generates bastion peer + NAT rules |
WARNING
Running the Ansible WireGuard playbook removes any peers not defined in the inventory. Always ensure new peers are added to the inventory before running the playbook.