Stale bootstrap_expect plus empty data directories: no cluster leader
Northwind's production cluster began as five Consul servers with bootstrap_expect = 5 hard-coded in a static configuration file. Two servers were decommissioned six months ago using consul leave and consul operator raft remove-peer, leaving three voters, but nobody edited the configuration. Last weekend a log rotation bug filled /var/consul on all three survivors and corrupted the Raft state. Rather than recover in place, the SRE team replaced all three instances from the golden image, so each started with an empty data directory, and started them within four minutes of each other. All three now log [WARN] raft: no known peers, aborting election and [ERR] agent: failed to sync remote state: No cluster leader in a loop. consul members shows all three servers alive and consul operator raft list-peers returns "No cluster leader". Nightly snapshots have run successfully for 90 days; the newest one in S3 is 14 hours old. The on-call engineer wants to start one server with -bootstrap to force an election and then rejoin the other two.
1. The servers report alive in `consul members` yet no leader exists. Explain precisely which subsystem each signal comes from and the root cause of the missing leader.
Read the worked answer
consul members reports Serf LAN membership, a gossip layer entirely separate from Raft. All three agents can gossip, so all three show alive regardless of Raft health.
Raft needs a persisted configuration or an initial bootstrap. Because the instances were rebuilt with empty data directories, there is no on-disk Raft configuration. In that state a server self-bootstraps only when it observes bootstrap_expect peers. All three configs say 5, only 3 exist, so every server waits for two servers that will never appear and none creates an initial configuration. The log line raft: no known peers, aborting election is the fingerprint of an empty Raft configuration, not of quorum loss (which produces repeated election timeouts with known peers).
So the root cause is compound: loss of Raft state + a bootstrap_expect value that no longer matches the real voter count. Either condition alone would not have produced this outage.
2. Compare the two recovery paths and recommend one with a concrete procedure.
Read the worked answer
Path A: relabel and restart (fresh cluster). Set bootstrap_expect = 3 on all three servers and restart. This forms a new empty cluster: all KV data, intentions, ACL policies and tokens, and service registrations disappear. With ACLs enabled, a fresh cluster with no seeded management token is also unusable until you supply acl.tokens.initial_management or re-bootstrap ACLs. Acceptable only if the snapshot is untrustworthy.
Path B: restore the 14-hour-old snapshot (recommended).
- Stop all Consul agents.
- Start one server alone with
bootstrap_expect = 1(or-bootstrap-expect=1); wipe its data directory first if it holds partial state. - Wait for a leader, then run
consul snapshot restore backup.snapwith a management token that exists in the snapshot. - Verify
consul operator raft list-peersshows one voter andconsul kv getreturns expected keys. - Start the other two servers with
bootstrap_expect = 3; they join and receive the restored state. Revert the temporarybootstrap_expect = 1and restart the first server one last time. - Confirm three voters, one leader, autopilot healthy, ACL token list populated, and that client agents have re-registered services.
Data loss is limited to roughly 14 hours of KV writes and catalog changes; agent anti-entropy re-publishes registrations automatically. Never leave -bootstrap in a production config: with persistent state an accidental -bootstrap can create an isolated single-node cluster.
3. What would you change so a future rebuild cannot silently reintroduce this failure?
Read the worked answer
- Derive
bootstrap_expectfrom inventory, not a static literal. Render it from the same source that defines server count, and add a pre-flight assertion in the DR runbook thatconsul operator raft list-peersvoter count equalsbootstrap_expect. - Alert on the actual failure mode. Page on
consul.raft.state.leader == 0sustained, onraft: no known peerslog matches, and on disk usage of the data directory (the original trigger). - Protect the data directory. Move
/var/consulto a dedicated volume with log rotation capped, and alert at 80% utilization rather than discovering corruption. - Keep snapshots externally and verify them. Store in S3 with retention, run
consul snapshot inspecton each new file, and rehearse a restore quarterly into a scratch environment. - Use Autopilot intentionally. With
cleanup_dead_serversandserver_stabilization_timeconfigured, dead voters are removed automatically, so the voter count andbootstrap_expectdo not drift apart silently.