// i-ikigai · the rollout

pipeline: field-guide.platform  ·  run #142

Boring deploys are a feature.

An interactive field guide to production platforms. Infrastructure as code, pipelines with real gates, GitOps, zero-downtime rollouts, and the autoscaling that keeps event systems honest.

The pipeline is live. Click it to push a commit. Some builds fail the gate, and that's the gate working.

commit / build shipped stopped at gate
stage 000 · commit

Everything is code

A platform you can't recreate from a repository is a platform you don't fully have. Infrastructure, workloads, dashboards, alert rules: all of it declared in code, reviewed, applied by machines. The test is simple. Could you rebuild the whole environment from git alone, on an ordinary Tuesday?

Anatomy of a plan

# terraform plan — review the diff, not the console
Plan: 3 to add, 1 to change, 0 to destroy

+ aws_sqs_queue.orders_dlq
    message_retention_seconds = 1209600
+ aws_lambda_event_source_mapping.orders
    batch_size                = 25
    maximum_concurrency       = 40
+ kubernetes_horizontal_pod_autoscaler.consumer
    min_replicas              = 2
    max_replicas              = 12
~ aws_lambda_function.enricher
    timeout: 30  60  # the actual change under review
  • The plan is the review artifact. Nobody approves "I'll bump the timeout." They approve this exact diff against real state, and what you review is what gets applied, byte for byte.
  • State is the second source of truth. The world drifts: a console hotfix at 2am, a quota change, a deleted tag. Scheduled plan runs surface drift as a diff instead of a surprise.
  • Terraform or CDK, by team and workload. Terraform's declarative HCL reads like an inventory and diffs cleanly. CDK gives application teams real types and loops in the language they already write. Both beat the console, and mixing them by layer (Terraform for foundations, CDK or serverless frameworks for app stacks) is common and fine.
  • Separate by blast radius. Network and identity change monthly; workloads change hourly. Different repos, different pipelines, different approvers, so a routine app deploy can never take the VPC with it.
stage 001 · build

One artifact, many environments

The pipeline's job is to produce one immutable, pinned artifact and prove things about it. Environments differ by configuration, never by build. The image that passed staging is bit-for-bit the image in production.

gates What the pipeline proves

Lint → test → build → scan, in that order, cheapest first. Every gate is automated and blocking: a failed contract test stops the artifact the same way a failed compile does. Human review is for design; machines gate correctness. If a check can be argued past, it's a suggestion, not a gate.

Pinned and reproducible. Base images by digest, dependencies locked, build args recorded. "Works on my machine" ends where the lockfile begins, and a CVE announcement becomes a grep instead of an archaeology project.

artifacts What comes out

An image and its provenance. The tag maps to a commit; the commit maps to a review; the review maps to a plan. Any pod in production traces back to a human decision in under a minute.

Config stays out of the artifact. Endpoints, feature flags, and limits arrive at deploy time; secrets never live in git or in images. They're injected from a secrets manager the runtime trusts. If you have to rebuild to change a URL, the boundary is drawn wrong.

stage 002 · deploy

Git is the deploy button

GitOps inverts the push. Nothing deploys to the cluster; the cluster pulls what git says should run, continuously reconciling reality against the declared state. A deploy is a merged PR, a rollback is a revert, the audit trail is git log. Run one below, then ship a broken build and watch the health checks catch it.

strategyrolling
healthy capacity100%
min capacity this deploy
statussteady

Rolling replaces pods a few at a time behind the load balancer: cheap, with steady capacity. Two versions serve traffic at once, so changes have to stay backward-compatible for the duration. Try the broken build. Readiness probes fail, the rollout halts, the surge pods are torn down, and capacity never drops.

stage 003 · shape

Two shapes of platform

The same event-driven system runs in two very different bodies: managed serverless primitives, or a Kubernetes cluster you run yourself. What separates them is where your operational effort goes. Your workload and your team decide which body fits. The two panels lay out the trade, so you can find yours in it.

shape a Serverless on AWS

Lambda consumers, EventBridge routing, SQS buffering, DynamoDB state: the broker patterns from the EDA guide, run as managed services. Scaling, patching, and failover sit with the provider. You pay per request, so idle costs nothing and spiky traffic stays cheap.

Where it costs you: cold starts show up on latency-sensitive paths, execution caps out at 15 minutes, and per-request pricing crosses over expensive once throughput stays high. You also inherit one vendor's semantics: event replay, DLQ redrive, and concurrency limits all behave AWS's way.

Fits when: traffic is spiky or modest, the team is small, nobody's dedicated to ops, and getting to production matters more than staying portable.

shape b Kubernetes and GitOps

Long-running consumers, Kafka clients with real rebalancing control, GPU workloads, anything stateful or latency-critical. One deployment model covers every workload, and the same manifests run on EKS, AKS, or bare metal. Processes that hold connections open, like agents and stream processors, live here comfortably, where Lambda's execution ceiling would cut them off.

Where it costs you: the cluster becomes a product your team runs, with upgrades, node pools, ingress, certificates, and capacity planning attached. GitOps automates the toil, not the responsibility, and the baseline bill never reaches zero even at idle.

Fits when: throughput is sustained, workloads run long or hold state, multi-cloud is a real requirement rather than a someday, or many teams share the same rails.

In practice most systems end up running both: serverless for the spiky, glue-shaped edges, a cluster for the sustained core. The expensive mistake isn't picking one over the other. It's leaving each team to improvise per service, with no shared path anyone agreed on. Worth settling where your own split falls before the codebase settles it for you.

stage 004 · scale

Scale on the queue, not the CPU

CPU-based autoscaling answers the wrong question for event systems. A consumer sitting at 40% CPU can still be hours behind. The signal that matters is queue depth and consumer lag: scale out when the backlog grows, scale in when it drains. Drive the load below and watch the autoscaler chase it.

pods
backlog
consumer lag
verdict

Same physics as the backpressure playground, one layer up: the queue absorbs the spike, the autoscaler adds consumers, the backlog drains. Watch the lag between spike and scale-out; that window is what the queue is for. Serverless does this dance implicitly, with SQS driving Lambda concurrency. On Kubernetes you configure it, with KEDA or HPA reading queue metrics. Either way, the signal is the backlog.

stage 005 · fail

Rehearsed failure is just operations

Every failure mode below has a boring, practiced answer on a healthy platform. You don't avoid these. You rehearse them until each one is a runbook rather than an incident.

The recurring five

  • The bad deploy. Readiness probes fail, the rollout pauses itself, capacity holds on the old version. Fix forward or git revert; both are one merge. If a rollback needs a meeting, the platform isn't finished.
  • The crash loop. A pod restarts forever on a poison config or missing secret. Backoff caps the churn; alerts fire on restart rate, not on the third page of logs.
  • Config drift. Someone "fixed" production by hand, and now git and reality disagree. GitOps reconciliation reverts it automatically. The console change becomes a PR, or it doesn't survive.
  • The stuck rollout. New version healthy, old version won't drain, usually a long-lived connection or a PodDisruptionBudget nobody remembers writing. Set drain timeouts and connection budgets before the deploy, not during it.
  • The thundering restart. A node dies; thirty consumers rejoin at once; the rebalance storm hits the broker harder than the outage did. Staggered restarts and cooldowns belong in the manifests.

What makes them boring

  • Health checks that tell the truth. Readiness gates traffic; liveness restarts the wedged. A readiness probe that just returns 200 is a decorative gate.
  • Rollback as a reflex. Revert-merge-done, under five minutes, rehearsed on purpose in daylight. The first time you roll back shouldn't be during an outage.
  • Blast-radius budgets. One service's deploy can't consume the cluster: quotas, PodDisruptionBudgets, and per-namespace limits are set before they're needed.
  • Idempotent everything. Replays, retries, and restarts are the platform's weather. Consumers and jobs that tolerate duplicates (see the EDA guide) turn that weather into noise.
  • Game days. Kill a node, expire a cert, break a build, on a Tuesday afternoon with coffee rather than a Saturday night with adrenaline.
stage 006 · trust

The best secret is no secret

The perimeter of a modern platform is identity, not network. The ranking is strict: a credential you never issue beats one you rotate, and one you rotate beats one somebody remembers. Federation moves almost everything into the first category.

Anatomy of a keyless deploy

# ci deploy job — no cloud keys stored anywhere
permissions:
  id-token: write          # mint an OIDC token for this run
steps:
  - uses: aws-actions/configure-aws-credentials
    with:
      role-to-assume: arn:aws:iam::…:role/deploy-web

# the role's trust policy pins exactly who may assume it:
condition:
  sub: repo:org/web:environment:production

# credentials live for minutes and are scoped to one role —
# there is nothing to leak, nothing to rotate
  • Federation kills the stored key. CI proves who it is with a short-lived identity token; the cloud exchanges it for minutes-long credentials. A leaked token from a fork can't assume the production role, because the trust policy pins repository, branch, and environment.
  • Workloads get identities too. Pods assume roles natively (IRSA, pod identity, workload identity); Lambda has execution roles. The key file that used to sit in the image simply doesn't exist.
  • Real secrets go in a manager. Database passwords and third-party API keys live in a secrets manager and are injected at runtime. Git and images stay clean, rotation becomes a config change, and access is itself audited.
  • Scope like you mean it. The deploy role can deploy and nothing else; namespaces get RBAC budgets like they get resource quotas. Least privilege isn't paperwork here, it's blast-radius engineering (see stage 005 ↑).
stage 007 · observe

Alert on symptoms, debug with causes

Users experience latency, errors, and staleness, never CPU. Pages fire on symptoms tied to service-level objectives; everything else is a dashboard for the debugging that follows.

The signals that page

slo checkout-events
  indicator   p95 end-to-end delivery < 2s
  objective   99.9% over 30d
  budget      43m of breach per month

page  error-budget burn > 14×   // wake a human
page  consumer lag growing 15m  // see stage 004
warn  deploy freq ↓, lead time ↑ // platform health
dash  cpu, memory, gc, restarts  // causes, not pages
  • Error budgets make reliability negotiable. 99.9% means 43 minutes of breach per month is allowed. Spend it on risky deploys or save it for bad weather. Burn it too fast and the budget freezes the release train, no manager required.
  • Lag is the platform's heartbeat. For event systems, consumer lag is the one metric that unifies app health, broker health, and capacity. It pages before customers notice, which is the whole point.
  • Deploy metrics are health metrics. Deployment frequency and lead time falling is the platform quietly failing its own users, the engineers. Watch them like uptime.
  • Correlate by trace, not by timestamp. One trace ID from HTTP edge through broker to consumer (the agent guide extends this to model calls) turns "what happened at 3:07" from a meeting into a query.
stage 008 · steady-state

Field glossary

Blast radius
Everything a single change can break. Good platforms shrink it structurally, with separate state, scoped credentials, and per-namespace quotas, before any human judgment is involved.
Blue-green
Two full environments; traffic flips atomically from blue to green and can flip back as fast. Costs double capacity for the duration; buys instant, total rollback.
Canary
The new version serves a small slice of real traffic first; metrics decide whether the rest follows. The safest strategy, and the one that most needs good observability.
Cold start
Latency of a serverless function's first invocation after idleness, from runtime boot plus init code. The tax serverless pays for scale-to-zero.
Drift
The gap between declared state (git) and actual state (the world). Detected by plan runs, reverted by reconciliation, caused by humans in consoles.
Error budget
The allowed unreliability inside an SLO, the currency risky changes spend. When it's gone, the release train stops without a debate.
GitOps
Deployment model where the cluster continuously pulls and reconciles the state declared in git. Deploy = merge, rollback = revert, audit = log.
HPA / KEDA
Kubernetes autoscalers. HPA reads resource metrics, KEDA reads external signals like queue depth. For event systems, backlog beats CPU as the trigger.
Immutable artifact
A build output that never changes after creation, with pinned digests and locked deps. Environments vary by config, never by rebuild.
OIDC federation
Exchanging a workload's short-lived identity token for short-lived cloud credentials, so CI and services authenticate with nothing stored and nothing to rotate.
Readiness / liveness
The two truths a pod tells the platform: "route traffic to me" and "I'm not wedged." Rollout safety is only as honest as these probes.
Reconciliation
The control loop that makes reality match declaration, continuously. The reason a console hotfix quietly un-happens.
SLO
Service-level objective: a user-visible target (latency, availability, freshness) that turns "is it reliable?" into a number with a budget.
Workload identity
Pods and functions assuming IAM roles natively (IRSA, pod identity, execution roles). The key file that doesn't exist can't leak.