Skip to content

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.

Terminal window
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.

Option Default Meaning
--image the chart’s appVersion with -core the image under test
--chart deploy/helm/faucet-stream a chart directory or an oci:// reference
--replicas 2 clustered replicas
--only every scenario comma-separated scenario names
--out ./chaos-report where chaos-report.md, chaos-report.json and one log per scenario go
--keep-cluster off leave 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.

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.

Scenario Failure Passes when
membership pods start together on a fresh history database every pod reports ready, history_ok, the full member count, and none started degraded
kill-owner the pod running a job is force-deleted another pod reclaims the run (attempt ≥ 1) and completes it with exactly 600 rows
cross-cancel a run is cancelled through a pod that does not own it it ends cancelled, no rows arrive after that status, and records_written matches the table
idempotency one idempotency key is sent to both pods both get the same run_id; the key with a different body is 409
node-stop the node under the owning pod is stopped the survivor completes the run with 600 rows, long before Kubernetes evicts the dead pod
rolling-restart every pod is rolled mid-run the run completes with 600 rows
all-pods every pod is killed with runs queued the interrupted run and every queued run complete
history-outage the history database refuses connections every 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
burst 40 runs submitted at once across pods 40 are accepted and each completes exactly once
poison the pod running the same job is killed again and again the run ends failed as a poison run instead of looping
schedule everything above, while a trigger fires every minute each minute produced one run; a tick missed during the outage fired once as catch-up
single-kill the only pod dies mid-run the run ends failed with the expired-lease reason
single-upgrade the single pod with a volume is restarted the Deployment uses Recreate, and two servers never run at once on the SQLite file
job-upgrade an upgrade changes the one-shot Job helm upgrade replaces the Job instead of failing on its immutable template

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.

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.