Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Chaos testing faucet serve

The reliability suites prove the engine’s ordering guarantees in-process. This page covers the layer above: a real deployment of faucet serve from the Helm chart, put through the failures a production cluster sees: pods and nodes dying, the history database going away, upgrades, and load. scripts/chaos/run.sh runs every scenario on a throwaway kind cluster and writes a pass/fail report.

Running it

scripts/chaos/run.sh                                   # every scenario, ~30 minutes
scripts/chaos/run.sh --only kill-owner,history-outage  # a subset
scripts/chaos/run.sh --image ghcr.io/faucet-hq/faucet-stream:<version>-core

It needs Docker, kind, kubectl, helm, jq and curl, about 8 GB of free disk and 6 GB of memory for the Docker VM. It writes its own kubeconfig, so your current kubectl context is never changed, and deletes the cluster when it finishes (--keep-cluster leaves it running for inspection). The exit status is the number of failed scenarios.

OptionDefaultMeaning
--imagethe chart’s appVersion with -corethe image under test
--chartdeploy/helm/faucet-streama chart directory or an oci:// reference
--replicas2clustered replicas
--onlyevery scenariocomma-separated scenario names
--out./chaos-reportwhere chaos-report.md, chaos-report.json and one log per scenario go
--keep-clusteroffleave the kind cluster running

On an arm64 machine an image published for amd64 only (releases up to 1.14.0) runs under the Docker VM’s emulation: slower, but every timing the scenarios assert is derived from the lease TTL, not wall-clock speed.

The setup

A three-node cluster: Postgres on the control-plane node (so stopping a worker never takes the databases down), and the faucet pods spread one per worker. Every scenario’s data lands in Postgres tables through an upsert sink, so row counts prove whether a re-run duplicated anything. A “slow” pipeline reads 600 rows at 0.15 s each (about 90 s), long enough to fail something mid-run.

The clustered deployment is --replicas pods with cluster.enabled, Postgres run history, token auth, a 30 s lease TTL and a trigger that fires every minute. The single-pod deployment is one replica with SQLite history on a volume and a one-shot Job.

What each scenario proves

ScenarioFailurePasses when
membershippods start together on a fresh history databaseevery pod reports ready, history_ok, the full member count, and none started degraded
kill-ownerthe pod running a job is force-deletedanother pod reclaims the run (attempt ≥ 1) and completes it with exactly 600 rows
cross-cancela run is cancelled through a pod that does not own itit ends cancelled, no rows arrive after that status, and records_written matches the table
idempotencyone idempotency key is sent to both podsboth get the same run_id; the key with a different body is 409
node-stopthe node under the owning pod is stoppedthe survivor completes the run with 600 rows, long before Kubernetes evicts the dead pod
rolling-restartevery pod is rolled mid-runthe run completes with 600 rows
all-podsevery pod is killed with runs queuedthe interrupted run and every queued run complete
history-outagethe history database refuses connectionsevery pod’s /readyz turns history_ok: false within the lease window, the owner cancels its own run before its lease expires (no concurrent execution), and after recovery the run completes with 600 rows
burst40 runs submitted at once across pods40 are accepted and each completes exactly once
poisonthe pod running the same job is killed again and againthe run ends failed as a poison run instead of looping
scheduleeverything above, while a trigger fires every minuteeach minute produced one run; a tick missed during the outage fired once as catch-up
single-killthe only pod dies mid-runthe run ends failed with the expired-lease reason
single-upgradethe single pod with a volume is restartedthe Deployment uses Recreate, and two servers never run at once on the SQLite file
job-upgradean upgrade changes the one-shot Jobhelm upgrade replaces the Job instead of failing on its immutable template

In CI

The Chaos (faucet serve) workflow builds a Postgres-only image from the commit, runs every scenario nightly, and can be started by hand with a scenario subset. The report is in the run summary and the chaos-report artifact. It is not a required check: a run takes about 30 minutes.

Adding a scenario

A scenario is a s_<name> shell function in scripts/chaos/run.sh that returns non-zero on failure and prints its evidence as its last line; register it with run_scenario <name> s_<name> in the clustered or single-pod block, and add a row to the table above. Assert outcomes a user would notice (row counts, run status, /readyz), and derive waits from LEASE_TTL rather than fixed sleeps.