Audience: On-call engineers
Scope: All operational failures handled by Quorum CLI
Last reviewed: 2026-02-22
| Symptom | First Command | See Section |
|---|---|---|
| Cluster shows DEGRADED | cluster-manager.sh status --cluster-id <id> --verbose |
Degraded Cluster |
| Node not responding | chaos-engineering.sh kill-node --dry-run |
Node Failure |
| SSH ping fails | lib/network_checks.sh pre-flight |
SSH Failures |
| Quorum lost | cluster-manager.sh status + node math |
Quorum Loss |
| Script ran wrong thing | Check --dry-run first |
Dry-Run |
| Disk full on node | storage-ops.sh stats |
Disk Failure |
| Network partition | chaos-engineering.sh heal-partition |
Partition |
This CLI manages a distributed storage cluster.
Three core invariants must always hold:
floor(N/2)+1) must be reachable for writes.replication_factor synced replicas.LEADER role at any time.A violation of any invariant is an incident.
Definition: Fewer than floor(N/2)+1 nodes are UP. Writes are blocked.
Detection:
./bin/cluster-manager.sh status --cluster-id <id> --verbose
# Look for: Status: UNHEALTHY
# Look for: N/M nodes UP (where N < quorum threshold)
Immediate Actions:
# Step 1: Identify which nodes are down
./bin/cluster-manager.sh status --cluster-id <id> | grep "DOWN"
# Step 2: Check if it's a network partition (nodes running but unreachable)
./scripts/chaos-engineering.sh partition --target-node <ip> --dry-run
# (checks iptables rules without changing anything)
# Step 3: Attempt node recovery
./scripts/chaos-engineering.sh kill-node --cluster-id <id> --node-id <node> --auto-recover
# Step 4: If recovery fails, check logs
tail -100 logs/cluster/cluster-manager.log
tail -100 logs/cluster/cluster-manager.json.log | python3 -m json.tool
Escalation Criteria: Escalate to Tier 2 if UNHEALTHY persists >10 minutes or if 2+ nodes are permanently down.
Definition: Some nodes are DOWN but quorum is still held. Reads work; writes degrade.
Risk: One more failure pushes to UNHEALTHY. Do not ignore this.
Resolution:
# Identify the degraded node
./bin/cluster-manager.sh status --cluster-id <id>
# Check if the node is truly dead or just partitioned
ping <node-ip> # Basic connectivity
nc -zv <node-ip> 22 # SSH port open?
nc -zv <node-ip> 7001 # Cluster port open?
# If partitioned: heal the partition
./scripts/chaos-engineering.sh heal-partition --target-node <node-ip>
# If genuinely failed: recover via chaos script
./scripts/chaos-engineering.sh kill-node \
--cluster-id <id> \
--node-id <node-id> \
--auto-recover
# Verify recovery
./bin/cluster-manager.sh status --cluster-id <id>
# Expect: Status: HEALTHY
Definition: A single cluster node becomes unresponsive.
Playbook:
# --- ALWAYS DRY-RUN FIRST ---
./scripts/chaos-engineering.sh kill-node \
--cluster-id <id> \
--node-id <failing-node> \
--dry-run
# Review output. Confirm the right node and cluster ID.
# --- Execute if dry-run looks correct ---
./scripts/chaos-engineering.sh kill-node \
--cluster-id <id> \
--node-id <failing-node> \
--auto-recover
Post-recovery verification:
./bin/cluster-manager.sh status --cluster-id <id> --verbose
./bin/storage-ops.sh verify --volume-id <vol-id>
# All replicas should show: SYNCED
Definition: write operations fail because <= N/2 nodes are reachable.
Formula: quorum = floor(total_nodes / 2) + 1
| Total Nodes | Quorum Needed | Max Failures |
|---|---|---|
| 3 | 2 | 1 |
| 5 | 3 | 2 |
| 7 | 4 | 3 |
Emergency: Restore Quorum
Option A — Recover failed nodes (preferred):
./scripts/chaos-engineering.sh kill-node --cluster-id <id> --node-id <node> --auto-recover
Option B — Add emergency nodes to raise total (last resort):
# Preview first
./bin/cluster-manager.sh add-node --cluster-id <id> --dry-run
# Execute
./bin/cluster-manager.sh add-node --cluster-id <id>
Option C — Deploy Witness node for even-sized clusters:
# If you have exactly 2 or 4 nodes and 50/50 split is possible:
./bin/cluster-manager.sh create --name <name> --nodes 2 --force-quorum
# This adds a Witness (+1 vote, no data) to prevent 50/50 split
Symptom: ERROR: X is UNREACHABLE (SSH port closed or host down)
Triage:
# From the network_checks library — runs pre-flight checks
bash -c "
source lib/logger.sh
source lib/network_checks.sh
pre_flight_checks 192.168.1.101 192.168.1.102 192.168.1.103
"
# Manual checks
ping <node> # Layer 3 reachability
nc -zv <node> 22 # Layer 4 SSH port
ssh -vvv -o BatchMode=yes <node> # SSH auth debug
Common Causes & Fixes:
| Cause | Fix |
|---|---|
| SSH key not trusted | ssh-copy-id <user>@<node> |
| Firewall blocking port 22 | ufw allow 22/tcp or check iptables -L |
| Node is rebooting | Wait 2 minutes, retry |
| iptables DROP rule active | chaos-engineering.sh heal-partition --target-node <ip> |
| Node OOMKilled sshd | systemctl restart sshd on node |
Use --dry-run while SSH is down:
# You can still preview what WOULD happen
./scripts/chaos-engineering.sh partition --target-node <ip> --dry-run
# Output shows the iptables commands without connecting
Symptom: Write operations fail; node shows read-only filesystem errors.
Detection:
./bin/storage-ops.sh stats
# Look for: Usage near 100%, or IOPS suddenly drops to 0
./bin/storage-ops.sh verify --volume-id <vol-id>
# Look for: replica-N: ✗ OUT OF SYNC
Response:
# Preview chaos disk-failure scenario to understand impact
./scripts/chaos-engineering.sh disk-failure \
--cluster-id <id> \
--node-id <node> \
--dry-run
# Create immediate snapshot for safety (disaster recovery)
./bin/storage-ops.sh snapshot \
--volume-id <vol-id> \
--retention 30d
# Restore node data directory permissions
ssh <node> "sudo chmod +w /path/to/data && sudo systemctl restart cluster-node"
# Verify repair
./bin/storage-ops.sh verify --volume-id <vol-id>
Symptom: Some nodes are HEALTHY from one vantage point and DOWN from another. Split-brain warning appears in logs.
Detection:
# Check for active iptables DROP rules
bash -c "
source lib/logger.sh
source lib/network_checks.sh
list_active_partition_rules 192.168.1.0/24
"
# If rules are listed, a partition was deliberately or accidentally created.
Resolution:
# Preview heal operation
./scripts/chaos-engineering.sh heal-partition \
--target-node <partitioned-node-ip> \
--dry-run
# Heal the partition
./scripts/chaos-engineering.sh heal-partition \
--target-node <partitioned-node-ip>
# Re-elect leader after heal (may take up to 30s automatically)
./bin/cluster-manager.sh status --cluster-id <id>
# Verify: HEALTHY, single LEADER
How does this CLI limit the damage it can cause if something goes wrong?
--dry-run (Safest)Every script with side effects supports --dry-run. This mode:
Standard operating procedure: Always run --dry-run before any command
on a production cluster.
# WRONG (runs immediately)
./scripts/chaos-engineering.sh partition --target-node 192.168.1.102
# RIGHT (preview first)
./scripts/chaos-engineering.sh partition --target-node 192.168.1.102 --dry-run
# Read the output, confirm correct node/CIDR
./scripts/chaos-engineering.sh partition --target-node 192.168.1.102
set -euo pipefail)All scripts use set -euo pipefail. If any command in a pipeline fails:
Without strict mode, a failed ssh node2 in a loop would silently continue
to node3, creating asymmetric state that is hard to debug.
lib/network_checks.sh)Before any remote operation, pre_flight_checks() verifies that all target
nodes are SSH-reachable. If even one node fails:
--dry-run guidance is providedEvery operation emits a JSON log line:
{"timestamp":"2026-02-22T10:00:01Z","level":"INFO ","message":"Partitioning 192.168.1.102 from cluster..."}
Logs accumulate in logs/cluster/cluster-manager.json.log. In an incident,
you can reconstruct exactly what happened and when:
cat logs/cluster/cluster-manager.json.log | python3 -m json.tool | grep ERROR
The demo.sh script and recommended workflows always snapshot volumes before
running chaos scenarios:
./bin/storage-ops.sh snapshot --volume-id <vol-id> --retention 7d
# Only then:
./scripts/chaos-engineering.sh data-corruption --cluster-id <id> --volume-id <vol-id>
# Real-time dashboard
./bin/perf-monitor.sh dashboard --cluster-id <id> --interval 5
# Performance report (last 24h)
./bin/perf-monitor.sh report --cluster-id <id> --output /tmp/report-$(date +%Y%m%d).txt
# Analysis + recommendations
./bin/perf-monitor.sh analyze --cluster-id <id>
# Tail JSON logs (pipe to jq for pretty)
tail -f logs/cluster/cluster-manager.json.log | python3 -c "
import sys, json
for line in sys.stdin:
try:
obj = json.loads(line)
print(f\"[{obj['timestamp']}] {obj['level']}: {obj['message']}\")
except:
print(line, end='')
"
Run this command to verify the CLI itself is healthy before using it in an incident:
./tests/run_tests.sh
# Expect: 71/71 tests passed, 100% pass rate
./tests/bats-vendor/bin/bats tests/
# Expect: All BATS tests pass
If tests fail, do not use the CLI on production until failures are investigated.