Rolling updates are good. But what if the new version has a subtle bug that only appears under load? Progressive delivery shifts traffic gradually and monitors metrics — auto-rolling back if things degrade. This is how Netflix, Intuit, and Shopify deploy.

Rolling Update vs Canary

Rolling Update (K8s default) Replace pods one by one If new pod is healthy → continue replacing ⚠️ No traffic analysis. Pod "healthy" ≠ "behaving correctly" Canary (Argo Rollouts) Shift 5% traffic → monitor metrics → 25% → 50% → 100% At EACH step: query Prometheus for error rate ✅ If error rate > threshold → automatic rollback Impact of a Bug Rolling: 100% users hit bug (once rollout completes) Canary: only 5% hit bug → auto-rollback → 95% never affected

How Argo Rollouts Works

Canary Rollout Steps Step 1 setWeight: 5% pause: 2m Step 2 analysis: check error rate < 5%? Step 3 setWeight: 25% pause: 5m Step 4 setWeight: 50% pause: 5m Step 5 setWeight: 100% 🎉 Full promotion ❌ Analysis fails → ROLLBACK █ = canary traffic (5%) | rest = stable version

🏋️ Install Argo Rollouts

kubectl create namespace argo-rollouts
kubectl apply -n argo-rollouts -f \
  https://github.com/argoproj/argo-rollouts/releases/latest/download/install.yaml

# Install kubectl plugin (optional but useful)
# macOS: brew install argoproj/tap/kubectl-argo-rollouts
# Linux: curl -LO https://github.com/argoproj/argo-rollouts/releases/latest/download/kubectl-argo-rollouts-linux-amd64 && chmod +x kubectl-argo-rollouts-linux-amd64 && sudo mv kubectl-argo-rollouts-linux-amd64 /usr/local/bin/kubectl-argo-rollouts

# Verify
kubectl argo rollouts version

🏋️ Canary Rollout Manifest

Replace your Deployment with a Rollout:

apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: cicd-mastery-api
  namespace: production
spec:
  replicas: 5
  selector:
    matchLabels: { app: cicd-mastery-api }
  template:
    metadata:
      labels: { app: cicd-mastery-api }
    spec:
      containers:
        - name: api
          image: acrcicdmastery.azurecr.io/cicd-mastery-api:sha-abc1234
          ports: [{ containerPort: 3000 }]
          readinessProbe:
            httpGet: { path: /health, port: 3000 }
          resources:
            requests: { cpu: 100m, memory: 128Mi }

  strategy:
    canary:
      canaryService: cicd-mastery-canary   # traffic to canary pods
      stableService: cicd-mastery-stable   # traffic to stable pods

      steps:
        - setWeight: 5
        - pause: { duration: 2m }
        - setWeight: 25
        - pause: { duration: 5m }
        - setWeight: 50
        - pause: { duration: 5m }
        - setWeight: 100       # full promotion

Create two Services (stable + canary):

apiVersion: v1
kind: Service
metadata:
  name: cicd-mastery-stable
spec:
  selector: { app: cicd-mastery-api }
  ports: [{ port: 80, targetPort: 3000 }]
---
apiVersion: v1
kind: Service
metadata:
  name: cicd-mastery-canary
spec:
  selector: { app: cicd-mastery-api }
  ports: [{ port: 80, targetPort: 3000 }]

Adding Automated Analysis

The real power: automated validation at each step using Prometheus metrics.

apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
  name: success-rate
spec:
  metrics:
    - name: http-success-rate
      provider:
        prometheus:
          address: http://prometheus-server.monitoring:80
          query: |
            sum(rate(http_requests_total{service="cicd-mastery-canary",status!~"5.."}[5m]))
            /
            sum(rate(http_requests_total{service="cicd-mastery-canary"}[5m]))
      successCondition: result[0] >= 0.95   # 95% success rate
      failureCondition: result[0] < 0.90    # Abort below 90%
      interval: 60s
      count: 3

Then add analysis to your Rollout steps:

      steps:
        - setWeight: 5
        - pause: { duration: 2m }
        - analysis:
            templates:
              - templateName: success-rate
        - setWeight: 50
        - pause: { duration: 5m }
        - setWeight: 100
This is the production gold standard: Deploy to 5% of traffic → run automated analysis against real metrics → if success rate drops below 95%, automatically rollback. No human needed. No 3 AM pages.

Watching a Rollout

# Watch live progress
kubectl argo rollouts get rollout cicd-mastery-api -n production --watch

# Output:
# Name:            cicd-mastery-api
# Status:          ◌ Progressing
# Strategy:        Canary
#   Step:          2/6
#   SetWeight:     5
#   ActualWeight:  5
# Images:
#   acrXXX/api:sha-old (stable)
#   acrXXX/api:sha-new (canary)
# Replicas:
#   Desired: 5, Current: 5, Updated: 1, Ready: 5, Available: 5

🧠 Recall Check

  1. What's the key difference between a K8s Deployment and an Argo Rollout?
  2. What does setWeight: 5 mean in a canary strategy?
  3. What happens when an AnalysisTemplate's failureCondition is met?
  4. Why do you need two Services (stable + canary)?
Reveal answers
  1. A Rollout supports progressive traffic shifting (canary/blue-green) with automated analysis. A Deployment only does rolling updates (replace pods sequentially, no traffic control).
  2. Route 5% of traffic to the new (canary) pods, 95% to the old (stable) pods. Argo Rollouts configures the Service mesh or ingress controller to split traffic.
  3. The rollout is immediately aborted and all traffic returns to the stable version. Automatic rollback — no human intervention needed.
  4. Traffic splitting works at the Service level. The stableService always points to stable pods; canaryService points to canary pods. The Rollout controller adjusts traffic weights between them.
Progressive delivery is the most sophisticated deployment strategy. Combined with GitOps (ArgoCD manages the Rollout resource), you get: Git commit → canary deploys → automated validation → full promotion or auto-rollback. Fully hands-off production deployments.

Next lesson: Secrets in GitOps — handling sensitive data when everything is in Git.