cluster¶
Primary audience: TAPPaaS admin.
The Proxmox VE layer of TAPPaaS — turns commodity nodes into a cluster that provides the VM, LXC and HA services every other TAPPaaS module builds on.
What you get¶
| Capability | Access from | How |
|---|---|---|
Proxmox VE cluster (tappaas1, tappaas2, … — .10+, up to the mgmt DHCP pool) | mgmt zone | https://tappaas1.mgmt.internal:8006 (10.0.0.10) |
VM provisioning (cluster:vm) | consumer modules | dependsOn: ["cluster:vm"] → services/vm/ hooks |
LXC provisioning (cluster:lxc) | consumer modules | dependsOn: ["cluster:lxc"] → services/lxc/ hooks |
HA placement + replication (cluster:ha) | consumer modules | dependsOn: ["cluster:ha"] → services/ha/ hooks. haCpu picks the CPU model the guest runs: common (default) — the richest x86-64 level its node and HANode share, so every move between them is live; host — the node's full CPU, moved by stop and start. See services/ha/README.md |
| CPU inventory of the nodes | TAPPaaS admin | update.sh Step 9 runs proxmox-controller cpu-inventory --apply: each node's x86-64 level as KVM reports it, the level all nodes share, and which live moves of a cpu: host guest are safe — config/cluster-cpu.md (and .json, which haCpu reads) |
| Automatic disk growth (VMs) | consumer modules | every module update grows a VM disk whose / is 80% full by 50% when its pool has room — services/vm/auto-grow.sh, settings in site.json diskAutoGrow; see services/vm/README.md |
lan/wan bridge model (VLAN-aware trunk) | nodes | built by config-network.sh at install |
ZFS data pools (tankXY) | nodes | built by config-storage.sh at install |
| Controlled node/cluster reboots | TAPPaaS admin | reboot-node.sh / reboot-cluster.sh. A node is drained only when every running HA guest on it can move live to the node HA would choose; otherwise it is left for a window (DEFERRED:) and nothing moves. haCpu: host guests are moved off by stop and start first, and back afterwards |
| Key-only SSH on every node (#19) | TAPPaaS admin | enforced by update.sh Step 5 (tappaas-cicd/scripts/ssh-key-only.sh, shared with the machine modules' sshKeyOnly, #756). No password SSH for any user; root by key only. The web GUI, its node Shell and the physical console still take the root password |
| Guests wait for the firewall before they start (#127) | TAPPaaS admin | enforced by update.sh Step 5d (setup-guest-hook.sh). Guests take their address by DHCP from the firewall VM, and after a cold start of the site (a power cut) each node starts its guests at once while the HA firewall waits for quorum. Every guest but the firewall carries a pre-start hookscript (local:snippets/tappaas-guest-hook.sh) that waits until the firewall answers DNS, at most 180 s, then starts the guest anyway; with the firewall up it costs one probe. In Grafana: {source="proxmox"} \|= "tappaas-guest-hook". A guest's own hookscript is kept, and reported |
| Microcode and an audit trail on every node (#128) | TAPPaaS admin | enforced by update.sh Step 5b (setup-host-hardening.sh). The CPU vendor's microcode package; auditd records changes made from a login session (SSH, the sweep, the console, the GUI node Shell) to /etc/pve, /etc/ssh, /root/.ssh and /etc/network, and forwards them to the logging module. In Grafana: {source="proxmox"} \|= "tappaas-pve" (or tappaas-ssh, tappaas-network). Changes made through the web GUI or API are PVE's own task log lines, already in Loki. A key added to or removed from root's authorized_keys is logged as tappaas-rootkeys-changed: (a 5-minute fingerprint check) and alerts as RootKeyChanged; a rewrite with the same keys does not |
| Proxmox's own notification mails off (#126) | TAPPaaS admin | update.sh Step 5c disables PVE's built-in default-matcher (cluster-wide), so smartd/ZED system mail, backup-job mails, replication, fencing and update notices no longer go to root@pam's address. Alerts come from the logging module instead, through site-notify.sh (see logging/DESIGN.md, Alerting from the logs). Disabled, not deleted: pvesh set /cluster/notifications/matchers/default-matcher --disable 0 brings PVE's mails back until the next cluster update; a matcher of the site's own is left alone |
| Every node on the site's timezone (#837) | TAPPaaS admin | update.sh Step 5e sets each node's timezone to site.json .location.timezone when it differs, and says so. Only the timezone; keyboard and locale stay as installed. The site-timezone health gate reports a node or guest still on another zone |
A guest is either a VM or an LXC container, never both.
Architecture¶
flowchart TB
subgraph Capabilities
Compute[Compute Capability]
HA[High Availability Capability]
end
subgraph ClusterModule["cluster module"]
PVE[Proxmox Virtual Environment]
VMService([vm service])
LXCService([lxc service])
HAService([ha service])
VMService -.->|provided by| PVE
LXCService -.->|provided by| PVE
HAService -.->|provided by| PVE
end
Compute -.->|realized by| PVE
HA -.->|realized by| PVE The Compute and High Availability capabilities are realized by Proxmox VE, which provides the vm, lxc and ha services (the module's provides in cluster.json) that every other TAPPaaS module builds on.
What is not included¶
- The firewall/router itself — that is the
networkmodule (its VM runs on this cluster). - The VM base images — the
templatesmodule. - Backup, identity, logging — separate foundation modules.
- General Proxmox administration guidance beyond the TAPPaaS conventions.
Requirements¶
- One node (or three for a cluster) capable of running Proxmox VE 9.2, each with two NICs (one WAN, one LAN) — three or more for a redundant LAN uplink (see Redundant LAN uplink).
- A dedicated boot disk (ext4/LVM) plus separate data disks for the
tankXYpools. - An existing network with a free IP, DHCP and internet for the bootstrap.
- Multi-node: a switch between the nodes — unmanaged works out of the box; a managed switch needs VLAN trunking configured first (see the repo-root INSTALL.md).
Two-node clusters¶
Two nodes are two votes, and Proxmox needs a majority — two — to stay quorate. When one node is down the other is not quorate, and HA fences it: the whole site goes down, the firewall with it. site-manager node add warns when a cluster reaches two nodes, and the health check (quorum) keeps saying so.
The fix is a third vote: a QDevice — corosync-qnetd on a small always-on Debian machine that is not a node (a debianhost, or the satellite). It holds no guests and needs almost no resources; the nodes must reach it on TCP 5403.
site-manager node qdevice add <host> # --user root if not the tappaas user
site-manager node qdevice status
site-manager node qdevice remove
add installs corosync-qnetd on the host and corosync-qdevice on every node, lets the first node log in to the host as root once (to exchange certificates), and runs pvecm qdevice setup; pvecm status then lists the Qdevice and Expected votes is 3. It restarts corosync on every node, so run it in a quiet moment.
A third node does the same and adds capacity. A QDevice with three nodes is not needed (and Proxmox advises against it on an odd number of nodes).
Redundant LAN uplink¶
A node's lan bridge carries everything that keeps it in the cluster: management, corosync, backup sync and every guest VLAN. On one port behind one switch, a pulled cable, a dead port or a switch being re-provisioned takes all of it down at once — and with HA's watchdog armed (the default), a node that loses quorum resets itself. The health check (lan-uplink) warns about exactly that combination.
The fix is redundancy in the link itself: two (or more) ports bonded under the lan bridge as lanbond, cabled to two different switches that are connected to each other. The node keeps one address and one corosync link; a failed port, cable or switch is absorbed below corosync, and a node that really loses its LAN is still fenced and its guests move.
Pick the mode by your ports and switches:
| Ports | Switches | Mode | Behaviour |
|---|---|---|---|
| any mix (10G + 1G) | any two, interconnected | active-backup (default) | one leg carries traffic — the primary (the fastest) when it is up; the other takes over |
| equal speeds (2 × 10G) | one switch, or two acting as one (stack, MLAG) | lacp | both legs carry traffic (802.3ad) |
Proxmox documents active-backup as the bond mode for cluster traffic; lacp across two switches works only where the switches present one LAG to the node — two independent switches cannot, and the bond will not aggregate.
Switch side. - active-backup: both ports are ordinary trunks with identical VLANs and MTU — a VLAN missing on the backup port only shows on failover. No LAG. - lacp: one LAG per node spanning both switch ports, LACP active, fast rate, the same VLANs as the node's current uplink.
Commands.
site-manager node network show tappaas2 # declared vs live, and what is wrong
site-manager node network set tappaas2 --lan-ports nic2,nic3 # declare and preview the change
site-manager node network set tappaas2 --apply # converge it
site-manager node network test tappaas2 --apply # fail each leg over in turn
--lan-mode lacp, --lan-primary <port> and --mtu <n> go with set; ports may be given by name or MAC address. A new node gets the same flags on node add.
A node whose second port is its wan bridge's — a node that will never run the firewall — can give it to the bond: --no-wan with set moves it from wan into lan (e.g. --lan-ports enp97s0,enp98s0 --no-wan, 10G primary, 1G backup). Its switch port must first carry the same VLANs as the lan port. It is refused while a guest is on the node's wan bridge.
set --apply changes one node at a time: it moves the node's HA guests off (HA maintenance), re-carries lan on the declared ports with a 45-second automatic rollback, checks from the mothership that the node is reachable, every leg is up, LACP has aggregated and corosync is connected — and only then keeps the change. Anything else and the node goes back to its previous uplink on its own. Then the node returns to HA placement.
Switching to lacp needs both ends: the node speaking LACP and its switch ports one aggregate (LAG). When the switch controller knows which switch ports the node's LAN NICs reach (switch-manager add-port <switch> <port> --type node --target <node> --target-port <nic>) and the switch has a vendor controller (UniFi, MikroTik), set --lan-mode lacp --apply does both: the node switches to LACP first (running on one leg until the switch answers), then the aggregate <node>-lan is formed, and the change is kept once LACP has aggregated — otherwise the aggregate is taken apart and the node reverts. Leaving lacp takes the aggregate apart first. On a switch that is configured by hand — or whose ports switch-manager does not know, or two switches that are not an MLAG pair — the step is printed, and --switch-wait 600 gives you time to do it while the node waits. The two legs may reach two switches when they are a declared MLAG pair (switch-manager mlag-pair set, MikroTik): the aggregate is then formed across both, the same mlag-id on each. Never form the aggregate first, against a node still in active-backup: the switch would send half its frames to the leg the node ignores.
test --apply (on a drained node too) takes each leg down for ten seconds and checks management and corosync stay up; a timer on the node brings the leg back after 30 seconds whatever happens. Pulling one cable at a time while watching site-manager node network show <node> is the test that also proves the switch side.
What is recorded and watched. The ports are declared in the node's config (config/<node>.json, uplinks, by MAC — interface names can move). A node that has none gets its current layout recorded by the next cluster update, which also installs lldpd in receive-only mode so show can say which switch and port each leg reaches. The health check warns when a leg is down, a bond runs on its backup, LACP has no partner, both legs reach the same switch, MTUs differ, or the live layout differs from the declaration; a leg that goes down and does not come back is also mailed (LanLegDown); a flap that recovers within the window, as at a node's boot, is not.
When you work on the switches, change one switch at a time and wait until site-manager node network show reports every leg up again before the next.
Alternatives considered¶
- XCP-ng — seems less polished and with fewer features (though it also seems more "free").
- FreeNAS/TrueNAS — good for storage (same ZFS underpinning) but not really a cloud platform: no clustering and HA like Proxmox.
- Dedicated SAN/NAS for storage — ZFS on commodity local disks delivers the redundancy without extra hardware (up to 50% cheaper in small/medium deployments); Ceph/Garage S3 remain a roadmap option for synchronous cross-node replication.
Source: the TAPPaaS curation criteria (Design Principles, tappaas.org What → Principles). Depth: see DESIGN.md.
Dependencies¶
None — cluster is the bottom layer of the foundation (dependsOn: []). It provides vm, lxc and ha to everything else.
For installation steps see INSTALL.md.
Design and implementation detail: DESIGN.md. Test coverage: TEST.md.