⚙️ What is etcd?

etcd is a distributed, strongly consistent key-value store that serves as Kubernetes' sole persistent backing store. Every cluster object — Pods, Deployments, Secrets, ConfigMaps, RBAC rules — lives in etcd. If etcd is lost without a backup, the cluster state is irrecoverable.

etcd uses the Raft consensus algorithm to guarantee that all members agree on every write before it is committed — even in the face of network partitions and node failures.

etcd Leader Handles all writes Replicates log to followers Follower 1 Votes in elections Can serve reads (stale) Follower 2 Votes in elections Can serve reads (stale) AppendEntries AppendEntries ACK ACK Quorum = (n/2)+1 = 2 of 3 kube-apiserver

Leader Election

A leader is elected via Raft when the cluster starts or the current leader times out. Only the leader accepts writes.

Log Replication

Every write is appended to the leader's log and replicated via AppendEntries RPCs. Committed once a quorum ACKs.

Quorum

A cluster of n nodes tolerates (n-1)/2 failures. 3-node = 1 failure. 5-node = 2 failures. Always use odd counts.

Strong Consistency

Every committed read reflects all prior writes — no stale reads from leader (linearizable). Followers serve stale reads by default.

🗄️ Data Model — Keys, MVCC & Revisions

Key Namespace Structure

Kubernetes stores all objects under the /registry prefix. The path follows the pattern /registry/{group}/{resource}/{namespace}/{name} — or without namespace for cluster-scoped resources.

/registry/apps/deployments/default/nginx
Deployment nginx in namespace default
/registry/core/pods/kube-system/coredns-5d
Pod in kube-system
/registry/core/secrets/my-app/db-password
Secret in my-app namespace
/registry/rbac.authorization.k8s.io/clusterroles/admin
Cluster-scoped ClusterRole
/registry/apiregistration.k8s.io/apiservices/v1.apps
APIService registration
/registry/events/default/nginx.event-abc
Event (high churn — short TTL)

MVCC — Multi-Version Concurrency Control

etcd never overwrites values in place. Every write appends a new version keyed by the global revision (a monotonically increasing integer). Old versions are retained until compaction removes them. This is how the API server serves watch history without re-reading etcd.

# Every key has:
# - mod_revision:    etcd revision when last modified
# - create_revision: etcd revision when first created
# - version:         number of times this key has been modified

ETCDCTL_API=3 etcdctl \
  --endpoints=https://127.0.0.1:2379 \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  --cert=/etc/kubernetes/pki/apiserver-etcd-client.crt \
  --key=/etc/kubernetes/pki/apiserver-etcd-client.key \
  get /registry/apps/deployments/default/nginx \
  --print-value-only | auger decode

# List all keys under a prefix
etcdctl get /registry/ --prefix --keys-only | head -30

# Get a key at a specific historical revision
etcdctl get /registry/apps/deployments/default/nginx \
  --rev=12345 --print-value-only | auger decode

# Watch for changes in real time
etcdctl watch /registry/apps/deployments/ --prefix
ℹ️ Values are Protobuf-encoded etcd stores Kubernetes objects as Protobuf (not JSON). Use auger or kubectl get --raw to decode. Raw etcdctl get output will appear as binary.

Revision vs resourceVersion

ConceptWhereMeaning
revisionetcd internalGlobal monotonic counter incremented on every write to any key
resourceVersionKubernetes object metadataThe etcd revision at which this object was last written — used for optimistic locking & watch
generationKubernetes object metadataIncremented only when spec changes (not status) — used by controllers to detect spec changes

Transactions & Optimistic Locking

The API server uses etcd transactions with a compare-and-swap on mod_revision to prevent lost updates. If two clients try to update the same object concurrently, one gets a 409 Conflict — the loser must re-fetch and retry.

# etcd transaction: only update if revision matches (CAS)
etcdctl txn <<EOF
compares:
  mod("my-key") = "42"
success:
  put my-key new-value
failure:
  get my-key
EOF
get my-key EOF

🛠️ Operations — Backup, Compaction & Defrag

Snapshot Backup

etcd snapshots capture the entire keyspace at a point in time. Take them regularly — this is your only recovery path if the cluster state is lost.

# Take a snapshot (run from a control-plane node)
ETCDCTL_API=3 etcdctl snapshot save /backup/etcd-$(date +%Y%m%d-%H%M%S).db \
  --endpoints=https://127.0.0.1:2379 \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  --cert=/etc/kubernetes/pki/apiserver-etcd-client.crt \
  --key=/etc/kubernetes/pki/apiserver-etcd-client.key

# Verify the snapshot
etcdctl snapshot status /backup/etcd-20240101-020000.db --write-out=table
# +----------+----------+------------+------------+
# |   HASH   | REVISION | TOTAL KEYS | TOTAL SIZE |
# +----------+----------+------------+------------+
# | abc12345 |   189423 |       3847 |     6.5 MB |
# +----------+----------+------------+------------+

Restore from Snapshot

# Stop kube-apiserver first (remove its static pod manifest or stop kubelet)
# Then restore on each member with a unique --name and --initial-advertise-peer-urls

ETCDCTL_API=3 etcdctl snapshot restore /backup/etcd-20240101-020000.db \
  --name=master-1 \
  --initial-cluster="master-1=https://10.0.0.1:2380,master-2=https://10.0.0.2:2380,master-3=https://10.0.0.3:2380" \
  --initial-cluster-token=etcd-cluster-1 \
  --initial-advertise-peer-urls=https://10.0.0.1:2380 \
  --data-dir=/var/lib/etcd-restore

# Update etcd's --data-dir to point to the restored directory, then restart
🚨 Restore wipes current etcd data Snapshot restore creates a brand-new etcd data directory. All changes after the snapshot are lost. Always stop the API server before restoring to prevent split-brain.

Compaction

Because MVCC retains all historical revisions, etcd grows unboundedly without compaction. Compaction discards all revisions older than a given revision number, freeing storage. The API server auto-compacts by default every 5 minutes.

# Get current revision
REV=$(etcdctl endpoint status --write-out=json | jq '.[0].Status.header.revision')

# Compact up to current revision (removes old MVCC versions)
etcdctl compact $REV

# Check remaining size
etcdctl endpoint status --write-out=table
# ENDPOINT              DB SIZE   ...
# https://127.0.0.1:2379  42 MB

Defragmentation

Compaction frees logical space but leaves fragmentation in the bbolt database file on disk. Defragmentation rewrites the file to reclaim physical disk space. It temporarily locks etcd — do it one member at a time.

# Defrag one member at a time (takes the member offline briefly)
etcdctl defrag --endpoints=https://10.0.0.1:2379 \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  --cert=/etc/kubernetes/pki/apiserver-etcd-client.crt \
  --key=/etc/kubernetes/pki/apiserver-etcd-client.key

# Then defrag the next member
etcdctl defrag --endpoints=https://10.0.0.2:2379 ...

# Verify size reduced
etcdctl endpoint status --write-out=table

Health Checks

# Check health of all cluster members
etcdctl endpoint health --cluster \
  --endpoints=https://10.0.0.1:2379,https://10.0.0.2:2379,https://10.0.0.3:2379 \
  --cacert=... --cert=... --key=...

# Check who is the leader
etcdctl endpoint status --cluster --write-out=table
# ENDPOINT              ID          IS LEADER   IS LEARNER   RAFT TERM   ...
# https://10.0.0.1:2379 abc123      false        false        14          ...
# https://10.0.0.2:2379 def456      true         false        14          ...
# https://10.0.0.3:2379 ghi789      false        false        14          ...

# Check member list
etcdctl member list --write-out=table
etcdctl member list --write-out=table

📐 Sizing, Tuning & Production Tips

Hardware Recommendations

Cluster sizeetcd membersCPURAMDiskNetwork
Small (<50 nodes)32 cores8 GB50 GB SSD1 Gbps
Medium (50–250)3–54 cores16 GB100 GB SSD1 Gbps
Large (250–1000)58 cores32 GB200 GB NVMe10 Gbps
🚨 Disk latency is the #1 etcd killer etcd's Raft leader must fsync the write-ahead log to disk before committing. Network disk (NFS, cloud HDD) with >10ms fsync latency causes leader timeouts and cluster instability. Use local NVMe or SSD. Check with fio --rw=write --ioengine=sync --fdatasync=1.

Key etcd Flags

# Election / heartbeat tuning
--heartbeat-interval=100       # ms between leader heartbeats (default 100)
--election-timeout=1000        # ms before follower starts election (default 1000)
# Rule: election-timeout should be 5-10x heartbeat-interval
# For high-latency networks (cross-region), increase both

# Storage
--quota-backend-bytes=8589934592   # 8 GB max DB size (default 2 GB)
--auto-compaction-mode=periodic
--auto-compaction-retention=1h     # compact revisions older than 1h

# Snapshot
--snapshot-count=10000             # entries between snapshots (default 10000)

# Security
--cert-file=/etc/etcd/etcd.crt
--key-file=/etc/etcd/etcd.key
--peer-cert-file=/etc/etcd/peer.crt
--peer-trusted-ca-file=/etc/etcd/ca.crt
--client-cert-auth=true
--peer-client-cert-auth=true

Production Best Practices

Dedicated Nodes

Run etcd on dedicated nodes or at minimum dedicated disks. Never share etcd storage with the OS or workload volumes.

Automated Backups

Schedule hourly snapshots via a CronJob or systemd timer. Store snapshots in object storage (S3/GCS) with versioning and cross-region replication.

Monitor DB Size

Alert when etcd_mvcc_db_total_size_in_bytes exceeds 70% of --quota-backend-bytes. A full etcd becomes read-only — emergency compaction required.

Separate etcd per Cluster

Don't share one etcd cluster across multiple Kubernetes clusters. Each cluster should have its own dedicated etcd ring.

Critical etcd Metrics

MetricAlert thresholdMeaning
etcd_server_leader_changes_seen_total>3/hourFrequent leader elections — network or disk issue
etcd_disk_wal_fsync_duration_seconds p99>10msDisk too slow for Raft WAL writes
etcd_disk_backend_commit_duration_seconds p99>25msbbolt page commit latency — defrag needed
etcd_mvcc_db_total_size_in_bytes>70% quotaGrowing toward quota limit — compact & defrag
etcd_network_peer_round_trip_time_seconds>150msPeer latency too high — may cause election timeouts

📝 Knowledge Check

Q1. A 3-node etcd cluster loses 2 members simultaneously. What happens?
  • A) The remaining member continues serving reads and writes normally
  • B) The cluster loses quorum — no writes are accepted, reads may still work
  • C) etcd automatically elects a new leader from the single remaining member
  • D) Kubernetes promotes a worker node to replace the lost etcd members
B) The cluster loses quorum. A 3-node cluster requires 2 members (quorum = (3/2)+1 = 2) to commit writes. With only 1 remaining member, no writes can be committed. The API server will return 5xx errors for mutating requests. Recovery requires restoring 2 members from snapshot.
Q2. You notice the etcd database file is 7 GB after running etcdctl compact, but the quota is 8 GB. The file hasn't shrunk. Why?
  • A) Compact requires a cluster restart to take effect
  • B) Compact frees logical space — you also need to run etcdctl defrag to reclaim physical disk space
  • C) The compact revision was too low — old data was not removed
  • D) The bbolt engine doesn't support size reduction
B) Compact + Defrag are two separate steps. compact marks old MVCC revisions as free in the bbolt B-tree but doesn't rewrite the file. defrag rewrites the bbolt database file from scratch, reclaiming the freed pages as actual disk space.
Q3. What is the primary reason etcd requires low-latency local SSD storage rather than network-attached disks?
  • A) etcd stores data as raw binary that NFS can't handle
  • B) Raft requires the leader to fsync the write-ahead log before committing — high fsync latency causes election timeouts
  • C) etcd uses memory-mapped files that only work on local disks
  • D) Kubernetes requires POSIX filesystem features unavailable on network storage
B) Raft WAL fsync latency. Before a leader can acknowledge a write to clients, it must durably persist the log entry (fsync). Network storage with high latency (NFS, cloud HDD) causes the leader to exceed its heartbeat window, triggering unnecessary elections and cluster instability.