Module 5: Gitops With Argocd

7. Deployment strategies: rolling, blue/green, and canary

Description

cicd-and-gitops-on-aws-guide M7.5 named you, with verified syntax but with nothing executed, the three standard strategies for shifting real user traffic between two versions of an application — and closed that lesson with a sentence pointing straight at this module: "kubernetes-and-eks-in-production-guide — where these three strategies actually get implemented, with a real cluster (...), probably alongside ArgoCD/Flux (...), forming the complete picture of pull-based GitOps + application deployment this guide only names." This lesson receives that textual delegation. andes-cargo-status-api has already been using RollingUpdate since Module 2 — you're going to confirm it with real evidence from your own cluster — blue/green and canary, on the other hand, need a piece this lab names but doesn't install.

Connection to the module

This is the module's last conceptual lesson before the final project. Lesson 8 uses exactly the scaling mechanism this lesson precisely distinguishes from a real RollingUpdate — a distinction worth having clear before you get there.


RollingUpdate: already configured, confirmed with real evidence

deployment.yaml has declared this strategy since Module 2, with no later lesson touching it:

spec:
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0

Confirm it yourself, against the real Deployment ArgoCD has managed since lesson 6:

kubectl get deployment andes-cargo-status-api -n andes-cargo -o jsonpath='{.spec.strategy}' | python3 -m json.tool

What to expect (literal, executed):

{
    "rollingUpdate": {
        "maxSurge": 1,
        "maxUnavailable": 0
    },
    "type": "RollingUpdate"
}

maxSurge: 1 — Kubernetes can create one extra replica, above the desired count, while the transition lasts. maxUnavailable: 0 — Kubernetes can never have any replica out of service during the transition; there always has to be, at minimum, the full desired replica count responding. This combination is an explicit decision, stricter than the generic example cicd-and-gitops-on-aws-guide showed (maxUnavailable: 1): for a service Andes Cargo's logistics partners query all day, zero unavailability during a deployment is the priority, at the cost of one extra Pod running briefly during the transition.


The detail that matters: RollingUpdate governs version replacements, not count changes

Here's the technical precision this lesson needs before you reach the final project. maxSurge and maxUnavailable only come into play when the Deployment's Pod template changes — for example, a new image tag, a new environment variable, a different resource limit. A change that only modifies spec.replicas (raising or lowering the desired replica count, without touching anything in the Pod template) is a scaling event, handled directly by the existing ReplicaSet — it triggers no RollingUpdate logic, because there's no "new version" to introduce gradually.

Confirm it with this Deployment's real history:

kubectl rollout history deployment/andes-cargo-status-api -n andes-cargo
kubectl get replicaset -n andes-cargo

What to expect (literal, executed, at this point of the module):

deployment.apps/andes-cargo-status-api
REVISION  CHANGE-CAUSE
1         <none>

NAME                                DESIRED   CURRENT   READY   AGE
andes-cargo-status-api-548966dd97   2         2         2       94m

A single REVISION, a single ReplicaSet (548966dd97), despite every replica change this module has already made. That's exactly the confirmation of the distinction: every time spec.replicas changed — in lesson 5 (selfHeal, replicas 5→2→5), in lesson 8's project (replicas 3→5) — Kubernetes adjusted the same ReplicaSet's size, without creating a new one. A real RollingUpdate — the one that gradually replaces old replicas with new ones, respecting maxSurge/maxUnavailable — would only happen if something changed inside spec.template (the Pod definition itself): that would create a new ReplicaSet, with a different hash, and REVISION would go up to 2.

        SCALING (what this module runs)          ROLLING UPDATE (what an image
                                                    or template change would trigger)

  ReplicaSet 548966dd97                            ReplicaSet 548966dd97 (old)
  replicas: 3 ──────▶ replicas: 5                   3 replicas ──▶ 2 replicas ──▶ 0
  (same hash,                                               │            │
   same ReplicaSet,                                         ▼            ▼
   no new REVISION)                               ReplicaSet a1b2c3d4e (new)
                                                     0 replicas ──▶ 1 ──▶ 3
                                                     (new hash, REVISION 2,
                                                      maxSurge/maxUnavailable
                                                      DO govern this transition)

This doesn't take anything away from lesson 8's project — "a Git change, reflected on its own, with no kubectl apply" is still this whole module's central proof — but it's worth knowing, precisely, which exact mechanism you're seeing: GitOps convergence over a scaling event, not a version RollingUpdate. deployment.yaml keeps declaring the RollingUpdate strategy for the day something in the Pod template does change — an image update, for example — and that transition would indeed respect maxSurge: 1/maxUnavailable: 0, exactly as you declared them since Module 2.


Blue/green: two complete environments, instant switch

Per AWS documentation, already cited in cicd-and-gitops-on-aws-guide M7.5: "the blue/green deployment strategy is a type of immutable deployment which also requires creation of another environment. Once the new environment is up and passed all tests, traffic is shifted to this new deployment." The difference from RollingUpdate: there's never a partial mix of traffic between versions — it's the old version or the new one, never both serving at once — and rolling back is as simple as redirecting traffic back to the old environment, which never got destroyed.

Plain Kubernetes (the native Deployment you already know) doesn't include blue/green — there's no strategy: type: BlueGreen in its official documentation. Implementing it for real requires an additional piece that manages two complete Deployments and decides which one the Service points to at any given moment — typically Argo Rollouts, from the same CNCF project as ArgoCD, with its own Rollout resource that replaces Deployment:

apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: andes-cargo-status-api
spec:
  strategy:
    blueGreen:
      activeService: status-api-service
      previewService: status-api-service-preview

activeService is the Service receiving real traffic today; previewService points to the new environment, still with no production traffic, available for testing before deciding the cutover. None of this YAML ran against andes-cargo-cluster — Argo Rollouts (most recent stable version, v1.9.1, verified against its official repository) isn't installed in this lab; it's named, with verified syntax, not installed.


Canary: a small slice, first

The name comes from the real mining practice: a canary, more sensitive to toxic gases than a human, alerted early. A canary deployment applies the same logic to an application's traffic: the new version first receives a small slice (5%, 1%) of real traffic, while most stays on the stable version; if that slice's metrics stay healthy, the percentage rises gradually.

Like blue/green, plain Kubernetes doesn't include it natively — Argo Rollouts, per its own documentation, "provide[s] advanced deployment capabilities such as blue-green, canary, canary analysis, experimentation, and progressive delivery features to Kubernetes":

apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: andes-cargo-status-api
spec:
  strategy:
    canary:
      steps:
        - setWeight: 5
        - pause: { duration: 10m }
        - setWeight: 25
        - pause: { duration: 10m }
        - setWeight: 100

setWeight: 5 sends 5% of real traffic to the new version; pause: { duration: 10m } stops the automatic advance for ten minutes — time for a monitoring system (outside this guide's scope, sre-and-incident-response-guide territory) to confirm metrics stay healthy before raising the percentage further. This didn't run against this cluster either.


The three, in direct contrast

Rolling (executed, M2-M8)Blue/green (named)Canary (named)
How many complete environments?One, in gradual transitionTwo, complete, in parallelOne, with a separate traffic slice
Traffic mix between versions?Yes, with no fine controlNo — instant switchYes, with fine control (setWeight)
Rollback speedMediumInstantFast (setWeight to 0)
Native Deployment supportYes (strategy.type)No — requires Argo RolloutsNo — requires Argo Rollouts
Did it run in this lab?Yes — deployment.yaml since Module 2No — YAML shown, verified, not installedNo — YAML shown, verified, not installed

Why this lab doesn't install Argo Rollouts

The same honesty discipline as this entire ecosystem: installing an additional tool just to name two strategies with no real business scenario that needs them — Andes Cargo never had, in this ecosystem, two versions of andes-cargo-status-api competing for real traffic at the same time — would be adding weight with no verifiable learning added. RollingUpdate fully resolves the only scenario this use case has: replacing one version with another, with no downtime, for a read-only service. Argo Rollouts stays named, with its real version (v1.9.1) and its syntax verified against official documentation, for whenever a reader needs blue/green or canary in a use case that actually justifies it.


Common mistakes

Thinking lesson 8's project is going to "show a RollingUpdate" in the sense of a version replacement (the mistake this very lesson prevents). What happens: someone arrives at lesson 8 expecting to see two separate ReplicaSets, one gradually replacing the other. How to spot it: if, after lesson 8, you look for a REVISION 2 in kubectl rollout history and don't find it. How to fix it: lesson 8 changes replicas, not the Pod template — it's a scaling event on the same ReplicaSet, as this lesson already demonstrated with real evidence. Lesson 8's proof is about GitOps (convergence with no kubectl apply), not about RollingUpdate as a version-replacement mechanism.

Believing strategy: type: BlueGreen is a valid value inside a native Deployment (technical expectation, already warned about by cicd-and-gitops-on-aws-guide M7.5, reinforced here with the real Rollout resource). What happens: someone tries to change deployment.yaml to use blue/green without installing anything extra. How to spot it: if you look for that value in Deployment's official documentation and don't find it. How to fix it: Deployment.spec.strategy.type only accepts RollingUpdate or Recreate (full replacement, with no gradual transition at all, outside this guide's scope because it implies downtime). Blue/green and canary require replacing Deployment entirely with Argo Rollouts' Rollout resource, an additional controller.

Confusing this lesson with cicd-and-gitops-on-aws-guide's Module 6 rollback (layer confusion, the same mistake that guide already warned about). What happens: someone thinks blue/green is "a more advanced form of git revert." How to spot it: if you think you could replace an andes-cargo-k8s git revert with "using blue/green." How to fix it: they're different-layer problems — Git rollback reverts a declarative commit (there are never two versions of infrastructure running in parallel at any point); blue/green resolves how to shift real traffic between two versions of an application running simultaneously, a problem that only exists when there are active replicas serving users.


Exercises

Exercise 1 — Distinguish scaling from RollingUpdate, unassisted. Without re-reading this lesson, explain in two sentences the difference between "changing spec.replicas from 3 to 5" and "changing a Deployment's image" — which of the two triggers maxSurge/maxUnavailable logic, and why?

See solution

Changing spec.replicas is a scaling event: the existing ReplicaSet simply grows or shrinks, with no new ReplicaSet created, and maxSurge/maxUnavailable don't come into play because there's no "new version" to introduce gradually. Changing the image (or any other spec.template field) does trigger a real RollingUpdate: Kubernetes creates a new ReplicaSet with a different hash, and replaces replicas of the old one with replicas of the new one, respecting maxSurge/maxUnavailable at every step of the transition.

Exercise 2 — Choose the correct strategy for a new scenario. Andes Cargo wants to test an experimental version of andes-cargo-status-api with a different caching algorithm, measuring the latency impact with a small slice of real traffic before deciding whether to fully adopt it. Which strategy from this lesson fits, and why do neither of the other two?

See solution

Canary — the explicit goal is to measure impact on a small slice before deciding, exactly what setWeight with incremental steps is designed to do. RollingUpdate doesn't work for this because it gives no fine-grained control over what percentage of traffic sees the new version at any given moment — it simply replaces replicas without distinguishing which Pod gets which request. Blue/green doesn't fit well either: it's an instant switch from 0% to 100%, with no intermediate step where only a small slice sees the new version.

Exercise 3 — Explain, in your own words, why this lab doesn't install Argo Rollouts. In two or three sentences, justify this lesson's decision to name Argo Rollouts without installing it, using this whole ecosystem's honesty criterion.

See solution

A reasonable explanation: "Installing an additional tool just to show syntax, with no real business scenario that needs it, would add weight to the lab with no verifiable learning added — the same criterion this guide already used to not install cert-manager/external-dns in Module 4. andes-cargo-status-api never had, in this use case, two versions competing for real traffic at the same time, so RollingUpdate already fully solves the problem this specific case has."


Summary and next step

This lesson received cicd-and-gitops-on-aws-guide M7.5's textual delegation and confirmed, with real evidence from your own cluster, that RollingUpdate has been configured since Module 2 (maxSurge: 1, maxUnavailable: 0) — and established a precise technical distinction the next project needs: a replicas change is a scaling event on the same ReplicaSet, not a version-replacement RollingUpdate, which only triggers when the Pod template changes. Blue/green and canary stayed named, with verified syntax and their real version (Argo Rollouts v1.9.1), without getting installed — an additional controller this use case, until today, never needed.

Before moving on you should be able to: explain the difference between scaling and RollingUpdate using this lesson's kubectl rollout history as evidence; name the resource Argo Rollouts uses instead of Deployment; and choose the correct strategy for a given business scenario.

Next lesson: the module's final project. There you push replicas: 3 → 5 with git push, with no kubectl apply — this whole module's central proof, now with this lesson's technical precision already installed: what you're going to see is GitOps converging a scaling event, not a version RollingUpdate.

Resources

  1. Kubernetes — Deployments, Rolling Update strategy — official documentation for RollingUpdate, maxSurge, and maxUnavailable, confirmed against this lesson's real deployment.yaml.
  2. Argo Rollouts — Documentation — official documentation for the tool that extends Kubernetes with blue/green and canary, the source for this lesson's Rollout YAML.
  3. Argo Rollouts — Releases — confirmation of the most recent stable version (v1.9.1) cited in this lesson.
  4. AWS Whitepaper — Practicing CI/CD on AWS, Deployment methods — the source for the blue/green definition, already cited in cicd-and-gitops-on-aws-guide M7.5.
  5. cicd-and-gitops-on-aws-guide (NIEVA), Module 7, lesson 5 — the complete textual source for the delegation this lesson receives.