Skip to main content
RunBook Academy

Proxmox VEXXVII · Multi-Cluster and Multi-TenancyRunning more than one

Operating a fleet of clusters

Advanced⏱ ~30 minpveshjq

What you'll learn

  • Build a fleet inventory from each cluster API and explain why the cluster name must be a label on every metric and alert
  • Design a staged rollout across clusters with defined ordering, soak time and abort criteria
  • Distinguish per-cluster alerting from fleet-level alerting, and say which belongs where
  • Adapt an on-call runbook so the responder identifies the cluster before diagnosing
  • Decide what to centralise and what to deliberately leave per cluster

Prerequisites

Verified against Proxmox VE 9.2.4 · Proxmox Backup Server 4.2.5 · Ceph Squid / Tentacle · Debian 13 (Trixie) · Linux kernel 7.0 (PVE 9.2 default) · 2026-08-12

Not yet marked complete on this device.

One cluster has an answer to “where is VM 141”: the cluster. Four clusters do not, and everything that used to be a single lookup becomes a search.

That is the shape of the whole problem. Nothing about operating a fleet is conceptually new — inventory, change, alerting, on-call — but every one of them acquires an extra dimension, and the tooling that was adequate at one cluster quietly stops being adequate at three.

Inventory: one query per cluster, one table

The single most useful cluster-wide endpoint is /cluster/resources. It returns every node, storage, guest and pool in one call, which is exactly the thing you need per cluster to build a fleet view.

Read-only / Safeeverything this cluster knows about, in one call
pvesh get /cluster/resources --output-format json | jq -r '.[] | [.type, .id, .node // "-", .status // "-"] | @tsv' | head -20

pvesh get /cluster/resources --type vm --output-format json | jq 'length'
Read-only / Safe
$ pvesh get /cluster/resources --output-format json | jq -r '.[] | [.type, .id, .node // "-", .status // "-"] | @tsv' | head
node	node/pve-a1	pve-a1	online
node	node/pve-a2	pve-a2	online
node	node/pve-a3	pve-a3	online
storage	storage/pve-a1/shared-rbd	pve-a1	available
storage	storage/pve-a1/local-zfs	pve-a1	available
qemu	qemu/141	pve-a2	running
qemu	qemu/142	pve-a1	running
lxc	lxc/205	pve-a3	running
pool	pool/tenant-blue	-	-
Read-only / Safea fleet inventory, stamped with the cluster name
declare -A FLEET=(
[site-a]="192.0.2.10"
[site-b]="192.0.2.60"
[site-c]="192.0.2.110"
)

for cluster in "${!FLEET[@]}"; do
host="${FLEET[$cluster]}"
token="$(cat "/etc/fleet/tokens/${cluster}")"
curl -sS --fail \
  --header "Authorization: PVEAPIToken=fleet-audit@pve!ro=${token}" \
  "https://${host}:8006/api2/json/cluster/resources?type=vm" \
| jq -r --arg c "$cluster" '.data[] | [$c, .vmid, .name // "-", .node, .status] | @tsv'
done | sort
Read-only / Safe
$ ./fleet-inventory.sh | column -t
site-a  141     app-frontend-01   pve-a2  running
site-a  142     app-frontend-02   pve-a1  running
site-b  100141  db-primary        pve-b3  running
site-b  100142  db-replica        pve-b1  running
site-c  200310  build-runner-01   pve-c2  stopped

The receiving identity for this should be an auditor: a user with PVEAuditor on /, a privilege-separated token, and nothing else. It is read-only by construction, so a leak of a fleet inventory token is an information disclosure rather than an outage.

Alerting: two levels, and do not merge them

The instinct at cluster number two is to point everything at one alert pipeline. That is right for delivery and wrong for authorship, because two genuinely different classes of alert exist.

Per-cluster alerts are the ones you already have. A node down, a pool approaching full, a failed backup job, a Ceph health warning, an HA fencing event. Their scope is one cluster, they are meaningful without reference to any other cluster, and PVE’s own notification system can raise many of them.

Fleet alerts are the ones that do not exist at one cluster and cannot be computed inside one:

Fleet alertWhy one cluster cannot raise it
A cluster has stopped reporting entirelyThe silent cluster is the one that would have told you
Version skew has exceeded policyRequires comparing versions across clusters
A configuration difference has appeared since yesterdayRequires the drift report from the previous lesson
Aggregate capacity headroom is below the level needed to lose a siteRequires summing across clusters
A guest exists on two clusters with the same identityRequires seeing both
The migration campaign has stalledRequires state that spans the boundary

The most important of these is the first. Absence of signal is the failure mode that a per-cluster alerting design cannot see, because every alert it knows how to raise depends on the cluster being alive enough to raise it. Whatever aggregates your fleet must have a heartbeat per cluster and must alert on its absence, with a threshold shorter than the time it takes for that cluster’s silence to matter.

Staged change across clusters

The single biggest operational gain from having several clusters is that a change no longer has to land everywhere at once. That gain is only realised if you actually stage it, and staging has four components.

An order, written down. Least critical to most critical, with the validation cluster first. Not “we will start with whichever is convenient”.

A soak time between stages, defined in advance. Long enough for the failure mode you are worried about to appear. For a kernel or microcode change that means at least one full business cycle and one backup cycle; for a firewall rule it may be an hour. The number should come from the failure you are guarding against, not from the calendar.

Abort criteria, written before you start. “If any of these is true, stop and do not proceed to the next cluster.” Concrete and observable: a fencing event, a failed backup, a specific error in a specific log, a latency threshold. Criteria authored after a stage has gone wrong are negotiated criteria.

A rollback that has been tested at least once, on the validation cluster.

On-call across a fleet

Three changes to the on-call model, in descending order of how often they are skipped.

1. Every alert names its cluster, first. Not in a label at the bottom, not implied by the node name — in the title. The responder’s first action is opening the right GUI, and an alert that does not say which one costs a minute of confusion at the worst time.

2. Every runbook starts by establishing which cluster. Runbooks written for a single cluster start at step one with a diagnostic command. Fleet runbooks need a step zero: identify the cluster, open its baseline document, note its version and its known differences. Part XXII’s runbook material applies; the addition is small and the omission is expensive.

3. Credentials and access must already exist for every cluster. The responder who can log in to two of three clusters is a responder who will escalate at 3 a.m. for an access problem. Access to every cluster is part of the on-call rotation’s prerequisites, and it should be tested when someone joins the rotation rather than when they are first paged.

What to centralise, and what not to

CentraliseKeep per cluster
Metrics, logs and dashboards, with a cluster labelThe PVE notification targets, so a cluster can still shout when the aggregator is down
Inventory and CMDBBackup jobs and windows, which legitimately differ by site
The configuration baseline and drift reportHA rules and groups, which are per-cluster by nature
Change records and the rollout orderNode network configuration, certificates, corosync
On-call rotation, escalation and runbooksThe local console and IPMI path, which must not depend on the central system
Backup verification resultsThe restore capability itself: each cluster must be restorable without the others

The line running through the right-hand column is independent recoverability. A fleet is only as resilient as its ability to lose the central layer and keep operating. If losing the aggregator means losing alerting, or losing PDM means losing the ability to manage a cluster, you have re-created the single fault domain that splitting was supposed to remove — just one level up.

Key takeaways

  • /cluster/resources is the one call that returns every node, storage, guest and pool for a cluster, and it is the right basis for a fleet inventory.
  • No PVE API response names its own cluster. Identity is added by the collector, and inferring it from node names breaks the first time someone names a node inconsistently.
  • Fleet inventory should run as a privilege-separated PVEAuditor token per cluster, so a leak is disclosure rather than an outage.
  • Per-cluster alerts and fleet alerts are different classes. The essential fleet alert is a heartbeat per cluster, because absence of signal is invisible to every threshold rule you own.
  • Stage changes with a written order, a soak time derived from the failure mode, abort criteria authored before you start, a tested rollback, and an end date that stops temporary skew becoming permanent.
  • A validation cluster must match production’s shape — storage type, topology class, HA and Ceph usage — even if it does not match its size. If it does not, say what it validates and what it does not.
  • On-call needs the cluster named in the alert title, a step zero in every runbook that identifies the cluster, and credentials for every cluster tested before the first page.
  • Centralise metrics, inventory, baselines and runbooks. Keep per-cluster notification targets, console access and restore capability, so losing the central layer is a degradation and not an outage.

Knowledge check

Knowledge check · 5 questions

  1. Q1. A fleet dashboard is built by querying /cluster/resources on each of four clusters and merging the results. Rows keep appearing under the wrong cluster. What is the design error?

  2. Q2. Three clusters report to one Prometheus. A firewall change silently blocks scraping of cluster C on Friday. Nothing fires all weekend. What rule was missing?

  3. Q3. Which of these belong in a staged cross-cluster rollout record before the first stage begins? Select all that apply.

  4. Q4. Once a central aggregation layer such as PDM or a shared Prometheus is in place, per-cluster notification targets can be removed to avoid duplicate alerts.

  5. Q5. A three-node cluster with local ZFS is proposed as the validation stage for an upgrade that will later land on two twenty-node hyper-converged Ceph clusters. What is the correct position?

Passing score: 75%. Answers are checked in this browser.