Canary deploys that actually catch problems: Argo Rollouts and Flagger

Shifting 10 percent of traffic to a new version is not a canary if nothing is watching. The metrics, the analysis window and the abort rule that make progressive delivery worth the complexity.

A rolling update already gives you a gradual rollout: pods are replaced a few at a time, and if the new ones fail their readiness probe the rollout stops. That covers crashes and startup failures, which is most of the value, for free.

Progressive delivery tools earn their place for the other failure mode: the version that starts fine, passes health checks, and is quietly returning 500s on one endpoint or doubling p99 latency. A readiness probe cannot see that. An analysis step can.

Rollouts or Flagger

Argo Rollouts replaces the Deployment with a Rollout resource that owns the strategy. It works with plain Kubernetes services, an ingress controller, or a mesh, and it has a UI that shows exactly which step a rollout is on. If you already run Argo CD, this is the natural choice.

Flagger leaves your Deployment alone and drives the rollout from the outside, creating the canary objects it needs. It leans harder on a service mesh or ingress with traffic-splitting support. If you run Flux, it is the natural choice.

The difference in day-to-day use is smaller than the documentation makes it look. Pick the one that matches your GitOps controller.

The analysis is the product

Everything else is plumbing. A canary without analysis is a slower deploy with more moving parts.

apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata: { name: success-rate }
spec:
  args: [{ name: service }]
  metrics:
    - name: success-rate
      interval: 1m
      count: 5
      successCondition: result[0] >= 0.99
      failureLimit: 1
      provider:
        prometheus:
          address: http://prometheus.monitoring:9090
          query: |
            sum(rate(http_requests_total{service="{{args.service}}",status!~"5.."}[2m]))
            /
            sum(rate(http_requests_total{service="{{args.service}}"}[2m]))

referenced from the rollout strategy:

strategy:
  canary:
    analysis:
      templates: [{ templateName: success-rate }]
      args: [{ name: service, value: orders }]
    steps:
      - setWeight: 5
      - pause: { duration: 5m }
      - setWeight: 25
      - pause: { duration: 10m }
      - setWeight: 50
      - pause: { duration: 10m }

Three things to get right in that file.

Measure the canary, not the service. The query above is the mistake everyone makes first: it averages canary and stable traffic together. At 5 percent weight, a canary returning errors on every request moves the blended success rate from 100 to 95 percent, which passes a >= 0.99 threshold check by hiding under the stable version's volume. Label your metrics by version or pod and filter to the canary.

Give it enough traffic to be significant. At 5 percent of 20 requests per minute, a five-minute window is 5 requests. That measures nothing. Either start at a higher weight, hold longer, or accept that for low-traffic services a canary is theatre — use blue/green with a manual smoke test instead.

Pick metrics that fail when users suffer. Success rate and latency at p95 or p99, measured at the ingress. Not CPU, not memory, not pod restarts — those are already covered by the readiness probe and they are not what "bad deploy" means to a customer.

Rollback and the database

Automatic abort shifts traffic back to stable in seconds, which is the fastest rollback you will ever have. It only works if the new version can be removed without consequences, and that is a schema question, not a deployment question.

While a canary runs, two versions of your code are reading and writing the same database. That means every migration must be backward-compatible for at least one release: add columns, never rename or drop in the same deploy as the code change; make new columns nullable or defaulted; write to both old and new fields during the transition; drop the old one a release later.

Teams that adopt canaries without adopting expand-and-contract migrations discover this when an automatic rollback restores a version that cannot read rows the new version wrote. The migration discipline is the prerequisite, not an optimisation.

Where it is worth the complexity

Progressive delivery pays off for services with real traffic, a meaningful error budget and deploys frequent enough that reviewing each one manually has stopped being sensible. A service getting three deploys a quarter does not need it.

Start with one high-traffic service, one metric (success rate), and a conservative step ladder. Run it for a month and look at how many rollouts aborted. If the answer is zero and you also had no incidents, the analysis window is probably too short or the metric is wrong — a canary that has never caught anything has not been tested. Deliberately deploy a broken build to staging and confirm it aborts.

Combined with Argo CD, the end state is that a merge to main becomes a deploy that watches itself and undoes itself, and the human involvement moves from approving deploys to approving changes.

ConsultorIA

Want this done on your cloud?

A ten-day read-only assessment is free, and Skyline lets you see your estate on a map before you write to us.

Related articles

Modules people reuse instead of copying

The two failures are a module that wraps one resource and adds nothing, and a module that does everything and nobody dares change. A minimal interface, safe defaults and honest versioning are what separate them.