open atlas
↑ Back to track
CI/CD pipelines CICD · 04 · 03

Progressive delivery: canary, blue-green and flags

Deploying code is not releasing it. Decouple them: ship dark, then expose gradually — canary at 1-5% with metric-gated auto-rollback, blue-green for instant flip-back, feature flags as a redeploy-free kill switch. Contain the blast radius instead of betting on staging.

CICD Senior ◷ 18 min
Level
FoundationsJuniorMiddleSenior

The tag was cut, the pipeline was green, the artifact was published. The team did everything the last two lessons taught — clean SemVer, a reviewed tag, a signed release — and then they pushed the new version to 100% of production in one shot, because that is what the deploy script had always done. Within fifteen seconds the error rate went vertical. A serialization bug that only triggers under real concurrent writes — invisible in staging’s single-threaded smoke test — was now failing for every single user. They had no gate, no slice to pull back, just one lever, and it was already all the way down. The rollback was a frantic redeploy of the previous version while the incident channel screamed: minutes of a full outage, 100% of users hit. The bug was never the real failure. Shipping it to everyone at once was.

Deploy is not release

This is the framing the whole lesson hangs on. Deploying is putting new code on servers — the binary is running, the container is up. Releasing is directing user traffic to that code — deciding who actually hits it. The previous lessons got you to a published artifact; they did not say a single user should see it yet. The senior move is to treat those as two separate switches. Ship the code dark — deployed but receiving no traffic — and then turn the traffic dial up gradually while you watch the signals, ready to turn it back down the instant they degrade. Everything below is a different mechanism for that one idea: separate “the code is here” from “users are on it”, and put a blast-radius limiter on the second switch.

The four rollout strategies

# big-bang (recreate): stop everything, start the new version — downtime + 100% blast radius
strategy:
  type: Recreate          # old pods die, new pods start: a gap where nothing serves

# rolling: replace instances batch by batch — no downtime, but two versions run at once
strategy:
  type: RollingUpdate
  rollingUpdate:
    maxSurge: 25%
    maxUnavailable: 0      # k8s default-ish: incremental, but NO explicit traffic gate
  • Big-bang (recreate / all-at-once): stop the old version, start the new one. There is a downtime gap, and the moment it is up the blast radius is 100% — every user is on untested-in-prod code at once. This is the Hook’s failure. Acceptable only for tiny or internal apps where a blip is fine.
  • Rolling: replace instances batch by batch (the Kubernetes default). No downtime, which is why it feels safe — but two versions run simultaneously, so they must be backward/forward compatible, and a bad version still reaches users incrementally with no explicit gate watching whether it is healthy. Rolling controls availability, not risk.
  • Blue-green: stand up two full environments. Deploy to GREEN (idle), smoke-test it in isolation, then flip the load balancer / router from BLUE to GREEN atomically. The cutover is instant, and so is rollback — flip the router back to BLUE. The cost is roughly 2x infra during the switch, and the hard part is shared state: both versions usually talk to one database, so a schema change must be compatible with both BLUE and GREEN across the flip.
  • Canary: route a small percentage of real traffic (1-5%) to the new version, watch its error rate, latency, and business metrics against the old version, then ramp 5 → 25 → 50 → 100% if it stays healthy, or abort if it does not. The canary catches problems with real production traffic at a tiny blast radius — which is exactly the bug class staging missed.
Why this works

Why decouple deploy from release with flags and canaries instead of just testing harder in staging? Because staging structurally cannot reproduce production’s real traffic mix, concurrency, data shape, and scale — and those are precisely where the expensive bugs live. Race conditions, hot-key contention, a serializer that only breaks under simultaneous writes, the one malformed payload that exists in a real users table — none of them show up against eight seeded rows and a single test thread. Progressive delivery accepts this and treats production as the final test, but with a limiter bolted on: expose the change to a thin slice, measure it against the baseline, and if it is bad you have contained the damage to that slice instead of betting your entire user base on staging having been representative. Testing harder is good; it just runs out before the bugs that take you down.

Feature flags: the release switch that needs no redeploy

A canary moves traffic at the infrastructure layer. A feature flag moves the release decision into the application layer: wrap the new behaviour in a conditional, deploy it with the flag OFF, and the code ships dark while users keep getting the old path. Then you flip the flag — for 1% of users, then 10%, then everyone — independently of any deploy. The new code was already on every server; you are just changing a config value.

// the new code is deployed everywhere, but dark until the flag opens it
if (flags.isEnabled("new-checkout-pipeline", { userId })) {
  return runNewCheckout(order);   // exposed to 1% -> 10% -> 100% via config, no redeploy
}
return runLegacyCheckout(order);  // everyone else, unchanged

Two properties make this the senior’s favourite tool. First, it is an instant kill switch: if new-checkout-pipeline misbehaves, you flip the flag back to OFF and every user is on the old path in seconds — no pipeline run, no image rebuild, no redeploy. Second, it does per-user / percentage targeting, which is what powers A/B tests and dark launches (run the new path, discard its output, just measure its load). When you own a flag that’s been “temporarily enabled” for six months, you’re already in the cost column. The cost is real: flag debt. Every flag is a branch in your code and a row in your config; stale flags that were never cleaned up become a combinatorial mess nobody dares delete. Flags are a release lever, not a permanent home — open them, learn, then remove them.

Automated rollback: the gate decides, not a human

Everything above is just safer if a human is watching. The senior version is automated. A progressive-delivery controller — Argo Rollouts, Flagger, or logic in your deploy pipeline — ramps the canary on a schedule and, at each step, runs an analysis query against your metrics: error rate, p99 latency, SLO burn rate. If the query breaches a threshold, the controller auto-aborts and rolls back — shifts traffic back to the stable version — with no human in the loop. That is the key number to internalise: the rollback decision is gated on metrics, not on vibes and not on someone happening to be staring at a dashboard at 4pm.

# Argo Rollouts canary with a metric-gated analysis step (sketch)
strategy:
  canary:
    steps:
      - setWeight: 5            # 5% of traffic to the new version
      - analysis:               # query Prometheus; auto-abort if it fails
          templates: [{ templateName: error-rate }]
      - setWeight: 25
      - analysis: { templates: [{ templateName: error-rate }] }
      - setWeight: 50
      - setWeight: 100          # full promotion only if every gate passed

Run the Hook’s incident through this. The bad version goes to 5% of traffic. The serialization bug fires under real concurrency and the analysis query sees the error rate on the canary spike past its threshold within seconds. The controller aborts at 5% and shifts traffic back to stable — automatically. Blast radius: 5% of users for a few seconds, instead of 100% of users for minutes. The bug still happened; progressive delivery just made it a non-event.

StrategyDowntimeBlast radius of a bad versionRollbackCost / catch
Big-bangYes (gap)100% instantlyRedeploy old (slow)No gate; only ok for tiny/internal
RollingNoGrows incrementally, no gateRoll back the batchesTwo versions live; must be compatible
Blue-greenNo100% at the flip (but smoke-tested first)Instant: flip router back2x infra; shared DB is the hard part
CanaryNo1-5%, contained by the gateAuto-abort on metric breachNeeds good metrics + automation
Pick the best fit

You must ship a risky change to a high-traffic service with the minimum possible blast radius and instant rollback. How do you roll it out?

Quiz

In progressive delivery, what is the difference between deploying and releasing, and what does a feature flag decouple?

Quiz

A serialization bug only manifests under real concurrent writes; a 100% big-bang deploy of it took the whole site down. Which rollout would have contained it, and on what signal?

Recall before you leave
  1. 01
    Explain the deploy-vs-release framing and contrast the four rollout strategies (big-bang, rolling, blue-green, canary) by downtime, blast radius, and rollback.
  2. 02
    What does a feature flag decouple and what is its cost, and how does automated metric-gated rollback turn the Hook's incident into a non-event?
Recap

Deploying code is not releasing it: deploying puts the binary on servers, releasing directs user traffic to it, and the senior move is to treat them as two switches — ship dark, then expose gradually while watching signals, ready to pull back fast. The four rollout strategies trade off downtime against blast radius: big-bang stops the old and starts the new with a downtime gap and 100% blast radius (only ok for tiny/internal apps); rolling replaces instances batch by batch with no downtime but two compatible versions live and no explicit gate, controlling availability not risk; blue-green stands up two full environments and flips the router from BLUE to GREEN atomically for instant cutover and instant flip-back, at ~2x infra and the hard problem of a shared database; canary routes 1-5% of real traffic to the new version, watches error rate / p99 / business metrics against the baseline, and ramps 5/25/50/100% if healthy or aborts if not, catching the bugs staging cannot reproduce at a tiny blast radius. Feature flags decouple release from deploy in the application layer — deploy with the flag OFF, then flip it per-user or per-percentage with no redeploy — giving an instant kill switch and A/B/dark-launch targeting, at the cost of flag debt you must clean up. The senior version is automated: a controller ramps the canary and runs an analysis query on error rate / p99 / SLO burn, auto-aborting and rolling back to stable on a metric breach with no human in the loop — the decision is gated on metrics, not vibes. A 5% canary with that gate would have caught the Hook’s serialization bug at 5% blast radius for seconds, instead of a 100% big-bang taking the whole site down for minutes. Now when you see a deploy script that pushes to 100% in one shot, you’ll know which lever is missing — and what it would have cost to have it.

Practice

Start at the top. Tasks go easiest → hardest: recall a fact, apply it to a case, then a senior-level stretch. Open one, attempt it, then reveal.

recallapplystretch0 of 5 done

Something unclear?

Ask a question about this lesson. Questions are anonymous and go straight to the author to make the lesson better.

shortcuts expand
search
K
prev piece
k
next piece
j
cycle tier
t
this menu
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.