3.4 High availability
High availability in SpatiumDDI is three separate problems: redundant DNS, redundant DHCP, and a redundant control plane. Each uses different mechanics and fails in a different way. If you mix them up you can end up with a three-node cluster that still drops DNS when one box reboots. This module takes each layer in turn, then puts them together in a failure table you can use in design reviews.
What you’ll learn
Section titled “What you’ll learn”- Build redundant DNS with several servers per group, explain why no zone transfer is involved, and know the DNSSEC exception
- Configure a Kea HA pair, and separate what SpatiumDDI renders from what Kea does itself
- Choose a control-plane HA path: the appliance or Helm, since Compose HA isn’t supported in 1.0
- Predict what keeps serving for each common failure, and why HA doesn’t replace backups
Three layers, scaled independently
Section titled “Three layers, scaled independently”| Layer | How you make it redundant | Protects |
|---|---|---|
| DNS | More servers in the same DNS server group | Name resolution |
| DHCP | Two Kea servers in one DHCP server group (DHCPv4) | Leases and renewals |
| Control plane | Several api/worker/frontend replicas, plus HA PostgreSQL and Redis | The UI, the API and your ability to make changes |
The layers don’t cover for each other. The appliance docs say it bluntly: control-plane HA is not serving HA. A cluster whose DNS and DHCP roles sit on one node still loses DNS and DHCP when that node goes down.
DNS: more servers in the group
Section titled “DNS: more servers in the group”Add a second (or third) agent-managed server to the same group and you have redundant authoritative DNS. Unless the group’s zones are DNSSEC-signed, nothing else needs configuring.
- Every enabled server renders each of the group’s zones as an independent authoritative copy, and gets its own queued op for every record change.
is_primarydoesn’t make one server “the writer” in an agent-managed group. It only orders the fan-out and picks whose result the audit trail reports.- If one server is unreachable, its ops wait as
pendingand drain when it returns, while the others apply straight away. The zone’s per-server serial status shows who has converged. - Hand clients more than one of the servers, for example in DHCP option 6, so they can fail over.
Signed zones are the exception. Every agent engine signs with keys held on that server, so two servers in a group would serve two DNSKEY sets against one DS. SpatiumDDI therefore signs only on a group with one agent server, and refuses to add or move a server into a group that holds signed zones; disabled servers count (#1762). For signed zones, keep one signing server and add redundancy with secondary zones in another group (module 3.2 shows the pattern).
A zone already signed on several servers shows a red DNSSEC: multiple signers badge and raises the critical dnssec_multi_signer alert. An agent that registers itself into such a group through AGENT_GROUP isn’t refused, because it would retry forever, so check for that alert after you add agents.
DHCP: a Kea HA pair on the group
Section titled “DHCP: a Kea HA pair on the group”Put two Kea servers in one DHCP server group, pick an HA Mode on the group, and fill in the HA Peer URL on each server. Each server’s URL is its own HA listener, typically http://<that server's address>:8000/ on the shipped image; SpatiumDDI renders every member’s URL into each peer’s list, which is how the partner finds it. On appliances, empty URLs are filled in for you when the second Kea member joins a host-networked HA group (#2963). When both URLs are set, SpatiumDDI renders Kea’s HA hook into both configs.
- Hot Standby (one active, one passive): the primary serves every client, and the standby takes over when Kea decides the partner is down.
- Load Balancing (both active): Kea splits clients between the peers by a hash of the client identifier.
Scopes, pools, reservations and classes live on the group, so both peers always render the same config.
| SpatiumDDI does | Kea does |
|---|---|
| Renders the HA hook with an identical peer list in both configs, plus your heartbeat and delay tuning | Replicates leases and runs the HA state machine, peer to peer over TCP 8000 |
Generates a per-group CA and peer certificate, so only the group’s own peers can use the HA listener; a superadmin rotates it with POST /api/v1/dhcp/server-groups/{id}/ha-peer-credential/rotate |
Requires that certificate from its partner, over HTTPS, once every member’s agent supports it |
| Resolves peer hostnames to IP addresses (Kea only accepts IP literals) and re-checks them every 30 s | Decides when to fail over, including after the control plane is gone |
Polls status-get about every 15 s to drive the HA v4 pill in the UI |
Keeps answering from its own lease store if replication breaks |
A third Kea member now has defined behavior. With its own HA Peer URL it joins as a backup peer that receives lease updates but takes no part in the heartbeat. Without one it’s sidelined: no HA hook and none of the group’s scopes, and the group page says “More than two Kea members” (#3151). Two things still aren’t built: driving HA state transitions (maintenance, continue, force-sync) from the UI, and gating DDNS writes on HA state, so in some windows both peers could try to write the same record.
Windows DHCP uses its own failover relationships. Since 2026.10.02-1 SpatiumDDI reads them on every lease sync, writes each scope only to the members that hold it (both partners when a relationship covers it), and can create, edit and replicate relationships from the group’s Windows DHCP failover panel over the CredSSP transport (#1110). Kea and Windows DHCP can’t share a group; module 3.2 has the details.
The control plane
Section titled “The control plane”In every shape the rules are the same: api, worker and frontend are stateless and scale out, PostgreSQL and Redis need their own HA, and beat stays a singleton.
- Install every node after the first as an Additional node, then promote from
/appliance → Fleet → Manage control plane cluster…. Sizes are odd only (1, 3, 5, 7); the API refuses an even total. A second First node would be a separate SpatiumDDI, not a member. - Within about 60 seconds of promoting two nodes you have etcd quorum across three, CloudNativePG at three instances, a 3-node Redis Sentinel set, and three api, worker and frontend replicas.
- Size every member at 4 vCPU, 8 GiB and a 48 GiB SSD: on 40 GiB disks a slot upgrade followed by a rolling upgrade reached the disk-usage warning.
- CloudNativePG replicates asynchronously, so a failover can lose the last few commits.
- Per-role node labels keep control-plane workloads on control-plane nodes, so a DNS-only appliance never gets PostgreSQL.
- Set a MetalLB pool and control-plane VIP under Appliance → Network & Host. The self-signed Web UI certificate grows to cover the VIP.
- You can’t demote the etcd seed, and you can’t revoke a node while it’s a live cluster member; demote it first.
- k3s takes an etcd snapshot every 6 hours and keeps 8. Restoring one resets etcd to a single node, so it’s for disaster recovery only.
postgresql.kind: cnpgrenders a CloudNativePG cluster (instances: 3, or 5 or 7 for larger installs). Install the CloudNativePG operator first. The apps connect through the-rwService, which always points at the current primary.- Replication is asynchronous: a failover can lose the last few commits. If you need zero data loss, configure synchronous replication on the cluster yourself and accept the write latency.
- The chart sets 20-second tolerations on the database pods, so a hard node loss fails over in about a minute instead of about five.
redis.kind: sentinelwithreplicas: 3gives a quorum of 2; the apps find the master through asentinel://URL.- The api’s replica count comes from its autoscaler (
api.autoscaling.minReplicasandmaxReplicas, 2 to 10 by default, on CPU);api.replicasapplies only with autoscaling off. Scale worker and frontend with their replica counts. Beat stays a single replica with theRecreatestrategy. - Promote/demote and the VIP are appliance features; on a plain cluster you front the frontend with your own Ingress or load balancer.
- Compose HA isn’t supported in 1.0 (#1236). The repo’s Patroni overlay (
k8s/ha/postgres-docker-compose.yaml) doesn’t work: Patroni never starts, and on an existing install it comes up on empty volumes. - The release
docker-compose.ymlhard-codesDATABASE_URL,REDIS_URL,CELERY_BROKER_URLandCELERY_RESULT_BACKEND, so pointing.envat HAProxy or Sentinel changes nothing. A hand-built HA control plane means maintaining your own compose files or VM services. Making Compose HA real is #137. - If you build one anyway: two or more api hosts behind a load balancer. The API holds no session state, so stickiness only matters for streaming (SSE) connections.
- One beat. Leader election for beat isn’t shipped, so if its host dies, recreate beat elsewhere.
- For a supported HA control plane, use the appliance or Kubernetes with CloudNativePG.
Point agents at the VIP
Section titled “Point agents at the VIP”An agent follows one control-plane URL. Point DNS and DHCP agents (and operators’ browsers) at the VIP or load balancer, never at one node’s IP: an agent pinned to a node loses its control plane whenever that node is down, even while the cluster is healthy. It keeps serving from cache, but it gets no changes until the node returns. When you install an Additional node, give the VIP as its control plane URL. Cluster members themselves are safe: their supervisors reach the api through the in-cluster Service.
What survives which failure
Section titled “What survives which failure”| Failure | Impact | What keeps serving |
|---|---|---|
| One DNS server in a group | Its ops wait as pending; marked unreachable after 90 s |
The group’s other servers answer and take changes; the stale one catches up on return |
| One Kea in an HA pair | The HA v4 pill shows the change of state |
The surviving peer, under Kea’s own HA state machine |
| Whole control plane unreachable | No UI, API, changes or scheduled jobs; agents spool leases, query logs and metrics and replay them on reconnect | Every agent from its last-known-good cache; Kea HA peer to peer |
| Redis down | Celery loses its broker, so DNS bundles stop being rendered. The long-poll falls back to polling: DHCP changes land within about 2 s, and DNS changes wait for the api, which builds the bundle itself after 120 s | Config delivery through the long-poll, and all DNS and DHCP |
Worker not consuming bundles, or no worker at all |
No DNS bundle renders. If the rest of the worker runs, agent_bundle_render_failed fires after 10 minutes; with no worker, alerts and other background jobs stop too |
DNS changes through the api’s fallback after 120 s (it can time out on very large groups); DHCP config delivery |
| PostgreSQL primary lost (HA) | The api can’t reach the database until a replica is promoted (about a minute on Helm); the last few commits can be lost | Agents from cache; the api resumes on the new primary |
| One of three appliance control-plane nodes | etcd keeps quorum (2 of 3), the database fails over (possibly losing the last few commits), the VIP moves | The UI on the same address, and DNS/DHCP on every node that carries the roles |
| Beat stopped | Scheduled jobs stop; the dashboard’s Platform Health card flags Beat (celery-beat) |
Everything interactive, and all DNS and DHCP |
| A bad config bundle | The agent reverts, quarantines it and raises agent_config_rejected |
That server, on its previous good config |
HA is not a backup
Section titled “HA is not a backup”Replication copies mistakes as fast as it copies good changes. A deleted zone disappears from every DNS server and every database replica within seconds. Your undo buttons are the Trash (30 days) and your backups, so keep both working:
- Schedule backups to a remote destination and run Restore Drills against them. A drill replays the newest archive into a scratch database and checks it without touching the live one. The
backup_failedandbackup_stalealerts are on by default for every scheduled destination (#1262). - On a hand-built Patroni cluster behind HAProxy, point restores at the read/write port, not the read-only one.
- DNSSEC keys live on each agent’s own volume, whatever the engine, not in the backup.
- An etcd snapshot restore is disaster recovery for the cluster, not a way to undo a bad change.
Knowledge check
Section titled “Knowledge check”Go deeper
Section titled “Go deeper”- System architecture: high availability: the three HA tiers
- Deployment topologies: topologies 3, 4 and 7 and how they fail
- Sizing: the three-node cluster figures and why its disks are 48 GiB
- Install the OS appliance: Additional nodes and growing to a high-availability control plane
- OS appliance: promote/demote, the VIP, etcd snapshots and rolling back
- Kubernetes deployment: CloudNativePG, Sentinel and autoscaling settings
- DHCP features and DHCP drivers: Kea HA, peer authentication, Windows failover and the Kea 3.0 caveat
- System administration: backup and restore: archives, restore, restore drills and backup alerts