Architecture One-Pager
Solution Architecture v1.0 · Google Cloud · Reliability Architecture · 2026-09 · 21 views · 16 architecture decision records
Chaos Engineering Platform · Solution Architecture v1.0 · Google Cloud · Reliability Architecture · 2026-09 · 21 views · 16 architecture decision records
Injection authority is a lease held in the data plane, not a command issued from the control plane.
Outages are almost never the failure that was rehearsed. They are the retry storm behind a slow recommendation call, the cache tier that was supposed to be optional, the failover that worked in the runbook and not in the region. A chaos engineering platform exists to find those failures on a Tuesday afternoon instead of on a Friday night — which means deliberately breaking production, in front of the people who own the code, without breaking it for customers. The hard part is not creating a fault. Any script can add packet loss. The hard part is guaranteeing that the fault goes away: when the service starts hurting, when the operator says stop, when the control plane crashes, when the network partitions, and when the telemetry that was supposed to notice goes dark.
The control plane decides that an experiment may run, computes its blast radius from the dependency graph, and issues a signed, target-bound, time-bound lease. An in-cluster agent is the only component that touches a target, and it holds the fault only while that lease is valid and renewed every five seconds. A dedicated high-resolution evaluator watches the target's own SLIs on ten-second windows and, on a breach, simply stops confirming steady state — the renewal is withheld and the fault expires. Nothing in the system tells a target to stop being broken; the permission to be broken runs out.
What it is, and what it is not
- A lease the data plane holds and lets expire — not a revert command the control plane must successfully deliver, which is exactly the delivery it cannot guarantee during the failures that matter most
- A blast radius computed from the dependency graph — not the replica percentage the author typed, which is a lie whenever the affected replicas all talk to one shared cache tier
- Evaluation against the target's own production SLIs — not a metric invented for the experiment, which can pass while the thing customers actually feel is failing
- INCONCLUSIVE as a first-class verdict — not treating an unobserved run as a pass, which converts missing telemetry into false confidence
- An abort path deployed separately, with fewer dependencies than the injection path — not one service that both starts and stops experiments, where a bad rollout removes the brake and the accelerator together
- Findings tracked to remediation as the unit of value — not runs executed as the unit of value, which produces experiments designed not to fail
- A promotion ladder from attended to scheduled to continuous — not cron from day one, which is how an unattended fault meets a peak-traffic window
- A consumer of the service catalogue, dependency graph and SLO store — not a second place where ownership, dependencies and SLIs are defined and then drift
The decisions that are the architecture
- Authority is leased, not commanded (ADR-01) — A signed 15-second lease, renewed every 5 seconds, is the only thing that keeps a fault alive. Every failure of the platform ends in expiry.
- The abort path is simpler than the injection path (ADR-02) — Lease issuer and kill switch are a separate deployment with their own identity, their own store and a 60-second RTO against the control plane's 30 minutes.
- Blindness aborts (ADR-03) — If the platform cannot evaluate the hypothesis, it cannot hold the fault. The verdict is INCONCLUSIVE, never a pass.
- Blast radius is computed (ADR-05) — Three measures — replica share, request-volume share, downstream closure — and a veto on any run reaching a shared single point of failure.
- Chaos spends a real budget (ADR-07) — Ten per cent of the service's own monthly SLO error budget, debited per run, refused at exhaustion.
- No adapter without a proven revert (ADR-09) — Inject, verify, revert, report_state. An adapter whose revert has not been exercised in the last 30 days is ineligible for production.
- Real traffic, bounded slice (ADR-12) — Shadow and synthetic traffic systematically miss retry storms and cache stampedes, which is the class of failure worth finding.
- A pass needs a positive observation (ADR-14) — Where a mitigation can be observed — breaker opened, fallback served, retry budget held — the hypothesis must assert it, not merely the absence of harm.
- The agent verifies its own permission (ADR-15) — Signature and scope are checked in the data plane, so a compromised control plane cannot widen a blast radius.
Why this should still be right in ten years
A chaos platform is unusual in that its central guarantee is about what happens when everything else is broken. That guarantee is not a technology choice, which is why most of this design should outlive the services it tests and all of the Google Cloud products named in it.
- Fail-closed is not a feature, it is the shape. The lease is what makes every failure mode converge on the same safe outcome. No new fault class, cloud migration or scale change alters that, and nothing in the lease mechanism depends on Google Cloud.
- The untested set is the durable product. Coverage — which dependencies have never been exercised — stays useful long after any individual verdict expires. Teams change, services are rewritten, and the gap report keeps answering the same question.
- Adapters are where churn lives, and it is contained. Injection mechanisms will change: eBPF will displace traffic control, meshes will gain and lose features. The capability-declaring adapter contract means that churn never reaches the control plane.
- The hardest part is organisational and the design respects it. Chaos error budgets, ownership-based authorisation and findings tracked to closure exist because the real constraint is remediation capacity, not injection capacity. That will still be true.
- What would break it. A feature request for a fault that cannot be reverted — a destructive data operation, an irreversible schema change — would put the first hole in the fail-closed guarantee. The adapter contract exists so that request has to be refused explicitly rather than accommodated quietly.
Non-functional targets
Every number here is a stated assumption from the requirement document, chosen to be defensible and arguable rather than measured. The view column points at the page where the number is visible in the design rather than merely asserted.
| Quality | Target | How it is met | View |
|---|---|---|---|
| Abort path availability | ≥ 99.99% monthly, failing closed to "no fault" | Separate deployment, own service account and Spanner instance, regional partitioning, 60 s RTO; unavailability means leases stop being renewed and faults expire | 16 |
| Control plane availability | ≥ 99.9% monthly | Cloud Run and GKE across three zones in the control region, warm replica in a second region with a 30 min RTO | 16 |
| Abort latency | p99 ≤ 10 s, hard ceiling 30 s | 10 s evaluation windows, 5 s renewal interval, 15 s lease TTL, no cross-region hop in the abort path; measured on the canary as a release gate | 13 |
| Kill switch reach | All injection halted fleet-wide ≤ 30 s | Separately authorised endpoint reaching every agent's watchdog directly, exercised on a schedule of its own | 20 |
| Signal freshness | p95 ≤ 15 s at the evaluator | Dedicated high-resolution collection path over Cloud Monitoring, windowed to baseline and injection only | 18 |
| Blast radius cap | ≤ 5% of healthy replicas, ≤ 1 zone, ≤ 1% of request volume | Three independently computed measures plus a dependency closure veto, re-checked during injection on scale events | 14 |
| Chaos error budget | ≤ 10% of a service's monthly error budget | Debited at lease issue against the SLO store's budget, settled to actual spend at abort, refused at exhaustion | 14 |
| Concurrency | 200 runs fleet-wide, 50 in one game day | Radius reservations in Spanner as the contended resource; intersecting runs queue rather than fail | 14 |
| Lease renewal throughput | ~2,000/s expected, 40,000/s at saturation | Regionally partitioned lease store, renewal as the only hot write path, agents batching per node | 16 |
| Durability of the record | RPO 0 for runs, findings and audit | Synchronous commit to Spanner before acknowledgement; evidence bundles sealed to Cloud Storage and indexed in BigQuery | 10 |
| Signal durability | RPO 60 s for high-resolution series | Loss degrades a verdict to INCONCLUSIVE rather than corrupting it — the one place where losing data is an accepted outcome | 10 |
| Retention | Runs 24 months, signals 90 days, audit 7 years | BigQuery partition expiry for signals with downsampled summaries retained for the life of the run record | 10 |
| Agent footprint | ≤ 1% node CPU, ≤ 64 MB resident when idle | DaemonSet in a dedicated namespace with explicit limits, so the envelope is visible to node autoscaling | 07 |
| Reversion verification | 100% verified; any failure is an incident | Re-read of target state after revert, bounded retry, then out-of-band reverter, a page and fleet-wide suspension of that fault class | 13 |
| Platform cost | ≤ 0.4% of fleet production compute spend | High-resolution collection only inside baseline and injection windows, early termination once the hypothesis is decided, per-team attribution | 18 |
Scope
In scope
- Declarative, versioned experiment definitions with a mandatory falsifiable hypothesis, abort conditions and radius limits.
- Six fault classes: network, dependency response, resource, lifecycle, clock and stateful dependency.
- Computed blast radius with reservation-based mutual exclusion, exclusion windows, chaos error budgets and a global kill switch.
- Lease-based injection with watchdog revert, verified reversion and an out-of-band reverter.
- Steady-state evaluation against the target's own SLIs, five verdict classes and sealed evidence bundles.
- Ad-hoc, scheduled, canary-gated, continuous and game-day execution modes with a promotion ladder between them.
- Findings tracked to remediation, and per-service resilience coverage reporting including the untested set.
Explicitly out of scope
- Load and performance testing. Injecting load and injecting failure need different guardrails and different verdicts, and sharing a platform between them weakens both.
- Security red-teaming. The adversary model is different and the authorisation model would have to be, too.
- Functional test automation and CI unit testing, which belong to the deployment pipeline.
- Owning the service catalogue, dependency graph, SLO definitions or on-call rotations — all read, none authored here.
- Incident management. The platform files findings and receives suspension signals; it does not run the incident.
What a four-week prototype should prove
Four claims in this architecture are cheap to test now and very expensive to discover wrong after thirty teams depend on the platform. Everything else can be built incrementally against a working answer to these.
- The fail-closed guarantee end to end: kill the lease issuer mid-experiment and measure, from outside, how long the fault survives against the 15-second TTL.
- The abort budget: from a deliberately induced SLI breach, measure breach-to-reverted at p99 against the 10-second target, including evaluation lag.
- The dependency closure: compute the downstream reachable set for twenty real services and check, by hand, how often it is wrong and in which direction.
- Reversion verification: run every adapter a thousand times and count how many reverts need the bounded retry, and how many need the out-of-band reverter.
- Partition the agent from the control plane mid-injection and confirm the fault clears within the TTL with no command delivered.
- Degrade the metrics pipeline during an injection window and confirm the run aborts and records INCONCLUSIVE rather than passing.
- Start two experiments whose computed radii intersect and confirm the second queues rather than running alongside the first.
- Open a real incident touching a downstream service mid-run and confirm injection suspends fleet-wide within 30 seconds.
Open risks, carried rather than hidden
| Risk | If it lands | Response |
|---|---|---|
| The abort budget is not achievable on the target's real SLI pipeline | Ten seconds from breach to fault-cleared assumes p95 signal freshness of 15 s at the evaluator. If the underlying metrics for most services are only available at 60-second resolution, the abort budget becomes a minute and every safety claim in this package loosens by the same factor — including the blast-radius caps, which were chosen on the assumption that a breach is caught quickly. | Measure it first, per SLI family, before committing to the number. The lever is ADR-13: if the fast path cannot be fast, the honest response is to widen the abort budget in the requirement and shrink the default blast radius to compensate, not to keep the 10 s figure and hope. |
| The dependency graph is wrong often enough to make closures useless | ADR-05 makes blast radius depend on a mesh-derived graph. If the graph misses edges, the platform under-reports reach and a compliant run touches a shared tier. If it invents edges, over-conservative closures refuse legitimate runs and teams route around the platform — the quieter and more damaging failure. | Validate twenty real closures by hand in the prototype and record the direction of each error. If the graph is unreliable, fall back to the declared selector plus an explicit shared-tier registry, and say so in the requirement rather than shipping a guardrail nobody trusts. |
| Remediation capacity, not injection capacity, is the real constraint | The loop on view 19 stalls at remediation. An assumed 61% of findings closed means four in ten weaknesses stay open, and a platform that produces more findings than the organisation can fix converts into a backlog generator that teams learn to ignore. | Report findings closed and time-to-close from day one, alongside runs executed. If closure rates fall, the correct response is to run fewer, better-targeted experiments from the untested-dependency set rather than to increase throughput. |
| The agent's node-level privilege becomes the largest unreviewed capability in the estate | ADR-10 puts traffic-control and node-level actions in a DaemonSet on all 40,000 pods' nodes. A vulnerability in the agent, or a lease-verification regression, is a fleet-wide capability. The failure would not look like a chaos experiment; it would look like an unexplained multi-service degradation. | Treat privileged adapters as a separate authorisation with their own review cadence, count scope refusals as a monitored signal (view 18), and keep the agent's release gated on the revert-proof and lease-expiry suites in ADR-11 and ADR-17's pipeline. |
| Unattended and continuous runs make the aggregate cost invisible | Per-run cost is small and the total is not. Without per-team attribution, a thousand low-value scheduled experiments arrive as a central telemetry bill that looks like a platform problem, and the only available control is a cap that penalises the teams using the platform well. | Ship attribution with the first scheduled run, not after continuous verification lands. ADR-16 makes ingestion a function of concurrency rather than fleet size; attribution is what makes concurrency somebody's decision. |
The reasoning behind every component and technology choice is in the Architecture Decision Record: 16 records across 5 areas, each with the alternatives that lost and what the choice costs.