LinuxLXIII · Cluster Time, DNS and Identity DependenciesTesting
Testing dependency failures - injecting the outage safely
What you'll learn
- Design a dependency failure test with a bounded blast radius
- Arm an automatic rollback before injecting a fault
- Inject grey failures as well as hard failures
- Record findings that change the design rather than reassure
Prerequisites
Verified against Ubuntu 24.04 LTS · Debian 12 (Bookworm) · RHEL 9.x · Rocky Linux 9.x · AlmaLinux 9.x · Linux kernel 6.1 LTS / 6.6 LTS · systemd 255+ · OpenSSH 8.7p1 (RHEL 9) / 9.6p1 (Ubuntu 24.04) · nftables 1.0.x · chrony 4.x · Pacemaker 2.1.x · Corosync 3.1.x · 2026-08-11
Every mitigation in linux-designing-around-dependency-failures
is a hypothesis. /etc/hosts might have a stale address.
cache_credentials might be set on two nodes out of three. The
break-glass account might have expired.
None of that shows up in a configuration review, because the configuration says what you intended. Only injecting the failure says what happens.
The safety envelope, before anything else
A dependency test is a deliberate outage. It is safe only inside an envelope you built first:
- One node, then the fleet. A fault on one node tests whether the mitigation works. A fault on all nodes tests your incident response. Do them in that order, on different days. Most teams skip straight to the second and learn nothing about the first.
- A timebox with an armed rollback, described below. Not a reminder, not a person watching a clock.
- Written abort criteria. “Abort if user-facing error rate exceeds X” or “abort if the cluster loses quorum”. Decided before, not during.
- An observer who is not the operator. The person typing is looking at the fault. Somebody else has to be looking at the service.
- A change record. Half the value of a game day is that the next unexplained alert is correctly attributed within ten seconds.
Arm the rollback before you inject
This is the single most important mechanic in the lesson. Schedule the undo first, so that losing your session, your network or your nerve still restores the system.
# systemd-run --on-active=600 --unit=depfail-rollback /usr/sbin/nft delete table inet depfailRunning timer as unit: depfail-rollback.timer
Will run service as unit: depfail-rollback.serviceIllustrative output
Ten minutes from now, the fault is removed whether or not anybody is still connected. Then inject:
# All injection lives in one table, so removal is one atomic operation
sudo nft add table inet depfail
sudo nft add chain inet depfail out '{ type filter hook output priority 0; policy accept; }'
sudo nft add rule inet depfail out udp dport 53 drop
sudo nft add rule inet depfail out tcp dport 53 drop
# Confirm exactly what is in force
sudo nft list table inet depfail
Everything about that shape is deliberate. A dedicated table
means the rollback is nft delete table inet depfail - one
command that cannot partially succeed and cannot touch the
production ruleset. Compare it with adding rules to the main
filter table, where the undo is a list of handles you have to
find again while the system is misbehaving.
To finish early:
sudo nft delete table inet depfail
sudo systemctl stop depfail-rollback.timer
Hard failures are the easy half
Latency and loss are injected with tc netem. The hazard from
the callout above applies directly - a root qdisc affects
everything leaving that interface, corosync included:
# Safe only if the dependency is reached over an interface the
# cluster links do NOT use. Verify first.
ip route get 192.0.2.30
sudo corosync-cfgtool -s
# 400 ms of added latency towards that dependency
sudo tc qdisc add dev eth1 root netem delay 400ms
# Remove
sudo tc qdisc del dev eth1 root
Where the dependency shares an interface with the cluster links, do not use a root qdisc. Inject on the dependency’s own server, on a test client, or in a lab.
A test per dependency
| Dependency | Hard injection | Grey injection | Expected outcome |
|---|---|---|---|
| DNS | Drop 53 outbound | 400 ms latency; a resolver returning NXDOMAIN | Cluster membership unaffected; fencing unaffected; named services degrade as designed |
| Identity (LDAP) | Drop 389 and 636 to the directory | Latency on the bind | Cached logins still work; break-glass works; sudo does not hang |
| Time | Drop 123 outbound | Point one node at a deliberately wrong source in a lab | Clock free-runs; the offset alert fires before anything breaks |
| Certificates | Not injectable safely in production | Issue a deliberately short-lived cert in a lab | Renewal happens; the expiry alert fires at the configured lead time |
| Log shipping | Drop the shipper’s port | Latency until buffers grow | Service unaffected; buffer growth is bounded and alerted |
Two entries there are honest limits rather than gaps. You cannot safely expire a production certificate, and you cannot safely skew a production cluster’s clock. What you can test in production is the detection: does the expiry alert fire at the right lead time, does the offset alert fire at the right threshold. Test the mitigation in a lab and the alarm in production.
What to measure
A test that produces “it worked” has produced nothing. Record numbers:
- Time from injection to the first observable symptom anywhere - including "no symptom for the full ten minutes", which is a result.
- Whether the alert on the dependency itself fired, and how long it took. A cache that hid the outage completely is a finding, not a pass.
- Whether an operator could still log in, run sudo, and read logs on the affected node.
- What the service did: served normally, degraded as designed, or degraded in a way nobody had described.
- Time from removing the fault to full recovery, with no human intervention.
- Anything that did not recover by itself.
The last two matter more than people expect. Recovery is where latent bugs live: a daemon that caches a negative result forever, a connection pool that never re-resolves a name, a service that needed a restart nobody realised was required. If removing the fault does not restore the system on its own, you have found a second outage hiding inside the first - one that would have doubled the length of the real incident.
Recording the finding
A finding is a sentence with a subject and a change:
With DNS unreachable,
node2could not be fenced: the fence device is configured withbmc-node2.example.com. Fencing is the last defence against corruption and it depends on a resolver. Change: put BMC addresses in/etc/hostson every node, managed by configuration management, with a weekly check that they match the inventory.
Compare with “DNS test passed, no issues found”, which is what gets written when nobody measured anything.
Knowledge check
Knowledge check · 5 questions
Q1. What should be done first when running a dependency failure test?
Q2. Blocking the port a dependency listens on is a sufficient test of resilience to that dependency.
Q3. Which fault injections are unsafe on a production cluster node? Select all that apply.
Q4. After a DNS outage test, the dependency alert never fired and no service showed any symptom for the full ten minutes. What is the correct conclusion?
Q5. Removing the injected fault restores connectivity, but one service only recovers after a restart. How should this be treated?
Passing score: 75%. Answers are checked in this browser.