ЁЯПл The SchoolтА║ЁЯУо CI/CDтА║ЁЯФБ рдзрдбрд╛ 09 тАФ Deploy strategies & rollback: рдПрдХреЗрдХ рдХрд░реВрди pins рдмрджрд▓рд╛
ЁЯЦ╝я╕П See the drawing + lab ЁЯПа Course home ЁЯМ┐ Branch on GitHub тЬПя╕П View source
ЁЯЦ╝я╕П рдЖрдХреГрддреА рдЖрдгрд┐ labThe drawing + lab рдкреВрд░реНрдг рдкрд╛рдирд╛рд╡рд░ рдЙрдШрдбрд╛ тЖЧOpen full page тЖЧ

ЁЯФБ рдзрдбрд╛ 09 тАФ Deploy strategies & rollback: рдПрдХреЗрдХ рдХрд░реВрди pins рдмрджрд▓рд╛

ЁЯУН рддреБрдореНрд╣реА рдЗрдереЗ рдЖрд╣рд╛рдд: 12 рдкреИрдХреА рдзрдбрд╛ 09 ┬╖ рдорд╛рдЧреАрд▓: lesson-08-environments-gates ┬╖ рдкреБрдвреАрд▓: lesson-10-deploy-to-kubernetes


ЁЯУж рдпрд╛ рдмреНрд░рдБрдЪрдордзреНрдпреЗ рдХрд╛рдп рдЖрд╣реЗ

рдзрдбреЗ 01тАУ08, рдЖрдгрд┐ рддреНрдпрд╛рд╢рд┐рд╡рд╛рдп рд╡реНрд╣реЕрди рдкреЛрд╣реЛрдЪрд▓реНрдпрд╛рд╡рд░ рдлрд▓рдХрд╛рд╡рд░ рдХрд╛рдп рдШрдбрддреЗ: рд╡рд╛рдЪрдгрд╛рд▒реНрдпрд╛рд▓рд╛ рдХрдзреАрд╣реА рд░рд┐рдХрд╛рдореА рднрд┐рдВрдд рди рджрд┐рд╕рддрд╛ рдирд╡реА рд╕реВрдЪрдирд╛ рдЬреБрдиреА рдХрд╢реА рдмрджрд▓рддреЗ, рдЖрдгрд┐ рдХрд╛рд▓рдЪреА рд╕реВрдЪрдирд╛ рдкрд░рдд рдХрд╢реА рд▓рд╛рд╡рд╛рдпрдЪреА. рдЦрд▒реНрдпрд╛ files:

ЁЯзТ 5 рд╡рд░реНрд╖рд╛рдВрдЪреНрдпрд╛ рдореБрд▓рд╛рд▓рд╛ рд╕рдордЬрд╛рд╡рд▓реНрдпрд╛рд╕рд╛рд░рдЦреЗ

рдореБрдЦреНрдп рд╕реВрдЪрдирд╛ рдлрд▓рдХрд╛рд╡рд░ ЁЯУМ рдЖрдЬрдЪреНрдпрд╛ рд╕реВрдЪрдиреЗрдЪреНрдпрд╛ рджреЛрди copies рдЕрд╕рддрд╛рдд, рдореНрд╣рдгрдЬреЗ рдЧрд░реНрджреА рджреЛрдиреНрд╣реА рдмрд╛рдЬреВрдВрдиреА рд╡рд╛рдЪреВ рд╢рдХрддреЗ. рдирд╡реА рдЖрд╡реГрддреНрддреА рдпреЗрддреЗ. рддреА рдХрд╢реА рдмрджрд▓рд╛рдпрдЪреА?

Rolling ЁЯФБ: рдЬреБрдиреНрдпрд╛ рджреЛрдШреАрдВрдЪреНрдпрд╛ рд╢реЗрдЬрд╛рд░реА рдПрдХ рдирд╡реА copy рд▓рд╛рд╡рд╛ (рдХреНрд╖рдгрднрд░ рдлрд▓рдХрд╛рд╡рд░ рддреАрди), рддреА рдЦрд░реЛрдЦрд░ рд╡рд╛рдЪрддрд╛ рдпреЗрддреЗ рдпрд╛рдЪреА рдХреЛрдгреА рдЦрд╛рддреНрд░реА рдХрд░реЗрдкрд░реНрдпрдВрдд рдерд╛рдВрдмрд╛ ("рддрдпрд╛рд░ рдЖрд╣реЗрд╕ рдХрд╛?" тАФ рд╣рд╛рдЪ readiness probe), рдордЧ рдПрдХ рдЬреБрдиреА copy рдХрд╛рдврд╛. рдкреБрдиреНрд╣рд╛ рддрд╕реЗрдЪ рдХрд░рд╛. рдкреВрд░реНрдг рд╡реЗрд│ рдлрд▓рдХ рд╡рд╛рдЪрддрд╛ рдпреЗрддреЛ. рдпрд╛ repo рдЪрд╛ Deployment рд╣реЗрдЪ рдХрд░рддреЛ.

Recreate ЁЯз╣ рджреЛрдиреНрд╣реА рдЬреБрдиреНрдпрд╛ copies рдлрд╛рдбреВрди рдХрд╛рдврддреЛ, рдордЧ рдирд╡реНрдпрд╛ рд▓рд╛рд╡рддреЛ тАФ рд╕реЛрдкреЗ, рдкрдг рдХрд╛рд╣реА рд╕реЗрдХрдВрдж рдлрд▓рдХ рд░рд┐рдХрд╛рдорд╛ рд░рд╛рд╣рддреЛ. Blue/green ЁЯФ╡ЁЯЯв рджреЛрди рдкреВрд░реНрдг рдлрд▓рдХ рдареЗрд╡рддреЛ: рдирд╡реА рд╕реВрдЪрдирд╛ рд░рд┐рдХрд╛рдореНрдпрд╛ рдлрд▓рдХрд╛рд╡рд░ рд▓рд╛рд╡рд╛, рддрдкрд╛рд╕рд╛, рдордЧ "рдЗрдереЗ рд╡рд╛рдЪрд╛ тЖТ" рдкрд╛рдЯреА рддрд┐рдХрдбреЗ рдлрд┐рд░рд╡рд╛. Undo = рдкрд╛рдЯреА рдкрд░рдд рдлрд┐рд░рд╡рд╛.

Canary ЁЯРд: рдирд╡реА рд╕реВрдЪрдирд╛ рдЖрдзреА рдПрдХрд╛рдЪ рд╡рд░реНрдЧрд╛рд▓рд╛ рджрд╛рдЦрд╡рд╛. рддреНрдпрд╛рдВрдиреА рддрдХреНрд░рд╛рд░ рдХреЗрд▓реА рдирд╛рд╣реА рддрд░ рд╕рдЧрд│реНрдпрд╛рдВрдирд╛ рджрд╛рдЦрд╡рд╛. Argo Rollouts рдЖрдгрд┐ Flagger рд╕рд╛рд░рдЦреА tools "рддреНрдпрд╛рдВрдиреА рддрдХреНрд░рд╛рд░ рдХреЗрд▓реА рдирд╛рд╣реА рддрд░" рдпрд╛рдЪреЗ рдЖрдкреЛрдЖрдк рдирд┐рд░реНрдгрдпрд╛рдд рд░реВрдкрд╛рдВрддрд░ рдХрд░рддрд╛рдд тАФ рддреА рдЕрд╕реНрддрд┐рддреНрд╡рд╛рдд рдЖрд╣реЗрдд, рдЖрдкрдг рдЗрдереЗ рдлрдХреНрдд рддреНрдпрд╛рдВрдЪреА рдирд╛рд╡реЗ рдШреЗрддреЛ.

рдЖрдгрд┐ rollback тПк рдореНрд╣рдгрдЬреЗ рдЬрд╛рджреВ рдирд╛рд╣реА: рдХрд╛рд▓рдЪреА рд╕реВрдЪрдирд╛ рдЕрдЬреВрдирд╣реА рд▓реЙрдХрд░рдордзреНрдпреЗ рдЖрд╣реЗ, рдХрд╛рд▓рдЪреНрдпрд╛ commit рдЪреЗ label рд▓рд╛рд╡реВрди. рддреА рдкреБрдиреНрд╣рд╛ рд▓рд╛рд╡рд╛.

ЁЯЧ║я╕П рдЖрдХреГрддреА

flowchart LR
    old["ЁЯУМ board: old, old<br/>2 replicas ready"]
    surge["ЁЯУМ old, old, NEW<br/>maxSurge 1 тЖТ three pins"]
    probe{"тЭУ GET /healthz<br/>readiness probe"}
    step["ЁЯУМ old, NEW<br/>one old pin comes down"]
    done["ЁЯУМ NEW, NEW<br/>rollout complete"]
    stall["тП╕я╕П stalls тАФ old pins stay up<br/>maxUnavailable 0"]
    old -->|"1 apply new image"| surge
    surge -->|"2 asks the new pod"| probe
    probe -->|"3 ok"| step
    step -->|"4 repeat once more"| done
    probe -.->|"5 not ok"| stall

тЭУ рдХрд╛рдп

ЁЯза рдЬреБрдиреЗ рдЖрдгрд┐ рдирд╡реЗ рдПрдХрд╛рдЪ рд╡реЗрд│реА рдЪрд╛рд▓рддрд╛рдд тАФ рддреНрдпрд╛рдЪреЗ рдирд┐рдпреЛрдЬрди рдХрд░рд╛

rolling update ┬╖ replicas 2 ┬╖ maxSurge 1 ┬╖ maxUnavailable 0

t0   old old          2 ready                 apply the new image
t1   old old NEW      2 ready + 1 starting    probe asks NEW: GET /healthz
t2   old NEW          2 ready                 one old pod gone, second NEW starting
t3   NEW NEW          2 ready                 rollout status exits 0 тЖТ the van reports тЬЕ

t1 рдЖрдгрд┐ t3 рджрд░рдореНрдпрд╛рди рджреЛрдиреНрд╣реА versions traffic serve рдХрд░рддрд╛рдд. рдореНрд╣рдгреВрдирдЪ database рдмрджрд▓рд╛рд╕рд╛рдареА expand / contract рд▓рд╛рдЧрддреЗ: рдЖрдзреА рджреЛрдиреНрд╣реА versions рдирд╛ рдЪрд╛рд▓реЗрд▓ рдЕрд╕реЗ schema рдкрд╛рдард╡рд╛ (рдирд╡рд╛ column рдЬреЛрдбрд╛, рдЬреБрдирд╛ рдареЗрд╡рд╛), рдордЧ рддреЛ рд╡рд╛рдкрд░рдгрд╛рд░рд╛ code рдкрд╛рдард╡рд╛, рдЖрдгрд┐ рдЬреБрдиреНрдпрд╛ code рд▓рд╛ рдЬреЗ рд▓рд╛рдЧрдд рд╣реЛрддреЗ рддреЗ рдирдВрддрд░рдЪ рдХрд╛рдврд╛. Rolling update рдЪрд╛рд▓реВ рдЕрд╕рддрд╛рдирд╛ рдПрдХрдЪ "column rename рдХрд░рд╛" migration рдЬреЗ version рдЕрдЬреВрди рдЪрд╛рд▓реВ рдЕрд╕реЗрд▓ рддреЗ рдореЛрдбрддреЗ.

ЁЯдФ рдХрд╛

YAML apply рдХрд░рдгрд╛рд░реА pipeline рддреНрдпрд╛ YAML рдордзрд▓реНрдпрд╛ strategy рдЗрддрдХреАрдЪ рд╕реБрд░рдХреНрд╖рд┐рдд рдЕрд╕рддреЗ. maxUnavailable: 0 рдЖрдгрд┐ probe рдЕрд╕реЗрд▓ рддрд░ рддреБрдЯрд▓реЗрд▓реА image site рдмрдВрдж рдкрд╛рдбрдгреНрдпрд╛рдРрд╡рдЬреА рдЕрдбрдХреВрди рдерд╛рдВрдмрддреЗ тАФ rollout status рд╡рд╛рдЯ рдкрд╛рд╣рдд рдЕрд╕рддрд╛рдирд╛ рдЬреБрдиреЗ pods serve рдХрд░рдд рд░рд╛рд╣рддрд╛рдд рдЖрдгрд┐ рд╢реЗрд╡рдЯреА job fail рд╣реЛрддреЛ. Probe рдирд╕реЗрд▓ рддрд░ process рд╕реБрд░реВ рд╣реЛрддрд╛рдХреНрд╖рдгреА Kubernetes рдирд╡реНрдпрд╛ pod рд▓рд╛ рдареАрдХ рдорд╛рдиреЗрд▓ рдЖрдгрд┐ рдЬреБрдиреЗ рдирд┐рд╡реГрддреНрдд рдХрд░реЗрд▓. Kubernetes рд╢рд╛рд│рд╛ rollout рдЪреА рдпрдВрддреНрд░рдгрд╛ L11 рдордзреНрдпреЗ рд╢рд┐рдХрд╡рддреЗ; рдЗрдереЗ рдЖрдкрд▓реНрдпрд╛рд▓рд╛ рд╡реНрд╣реЕрдирд▓рд╛ рдХрд╛рдп рджрд┐рд╕рддреЗ рддреНрдпрд╛рдЪреА рдХрд╛рд│рдЬреА рдЖрд╣реЗ: рдкрд╛рд╕ рдХрд┐рдВрд╡рд╛ fail рд╣реЛрдгрд╛рд░реЗ рдПрдХ рдЧреЗрдЯ.

ЁЯФз рдХрд╕реЗ (рдпрд╛ repo рдордзреНрдпреЗ)

Strategy manifest рдордзреНрдпреЗ рд░рд╛рд╣рддреЗ, рдХреЛрдгрддреНрдпрд╛рд╣реА pipeline file рдордзреНрдпреЗ рдирд╛рд╣реА тАФ рдЪрд╛рд░рд╣реА рдмреЛрд▓реА рддреАрдЪ k8s/deployment.yaml apply рдХрд░рддрд╛рдд:

spec:
  replicas: 2
  selector:
    matchLabels: { app: hello-courier }
  strategy:
    type: RollingUpdate                 # lesson 09: replace the pins one at a time
    rollingUpdate: { maxSurge: 1, maxUnavailable: 0 }
  template:
    metadata:
      labels: { app: hello-courier }
    spec:
      containers:
        - name: web
          image: IMAGE_PLACEHOLDER      # CI swaps in ACCOUNT.dkr.ecr.REGION.amazonaws.com/hello-courier:<sha>
          ports: [{ containerPort: 3000 }]
          readinessProbe:               # the rollout only proceeds when the new pod answers /healthz
            httpGet: { path: /healthz, port: 3000 }
            initialDelaySeconds: 2
            periodSeconds: 5

рд╡реНрд╣реЕрди рдмрджрд▓ рдкреВрд░реНрдг рд╣реЛрдгреНрдпрд╛рдЪреА рд╡рд╛рдЯ рдкрд╛рд╣рддреЗ рдЖрдгрд┐ timeout рдореНрд╣рдгрдЬреЗ рдЕрдпрд╢рд╕реНрд╡реА delivery рдорд╛рдирддреЗ (deploy.yml, production job):

          kubectl apply -n production -f k8s/
          kubectl rollout status -n production deployment/hello-courier --timeout=180s
      # rollback = kubectl rollout undo deployment/hello-courier -n production   (lesson 09)
      # тАжor, better, re-run this workflow with yesterday's image tag: the tag IS the version.

Blue/green рдпрд╛ repo рдордзреНрдпреЗ рдирд╛рд╣реА; рддреНрдпрд╛рдЪрд╛ рдЖрдХрд╛рд░ рдореНрд╣рдгрдЬреЗ рджреЛрди Deployments (pod labels рдордзреНрдпреЗ color: blue, color: green) рдЖрдгрд┐ Service рдордзрд▓рд╛ рдПрдХрд╛ рдУрд│реАрдЪрд╛ рдмрджрд▓:

# illustrative тАФ k8s/service.yaml with a color in the selector
spec:
  selector: { app: hello-courier, color: blue }   # flip to green once green passes its checks
  ports: [{ port: 80, targetPort: 3000 }]
ЁЯФБ рд╣реЗрдЪ CircleCI рдордзреНрдпреЗ
            kubectl apply -n << parameters.namespace >> -f k8s/
            kubectl rollout status -n << parameters.namespace >> deployment/hello-courier --timeout=180s
ЁЯжК рд╣реЗрдЪ GitLab CI рдордзреНрдпреЗ
    - kubectl apply -n "$NAMESPACE" -f k8s/
    - kubectl rollout status -n "$NAMESPACE" deployment/hello-courier --timeout=180s
ЁЯОй рд╣реЗрдЪ Jenkins рдордзреНрдпреЗ
      kubectl apply -n ${namespace} -f k8s/
      kubectl rollout status -n ${namespace} deployment/hello-courier --timeout=180s

ЁЯзк рдХрд░реВрди рдкрд╛рд╣рд╛

AWS рдЪреА рдЧрд░рдЬ рдирд╛рд╣реА тАФ рд╕реНрдерд╛рдирд┐рдХ cluster рд╡рд░ pins рд╣рд▓рддрд╛рдирд╛ рджрд┐рд╕рддрд╛рдд.

# 0) a local cluster: kind (below) or Docker Desktop тЖТ Settings тЖТ Kubernetes тЖТ Enable
kind create cluster --name school
kubectl create namespace staging

# 1) photocopy v1 with a tag (CI uses the full commit SHA; any tag except "latest" works locally)
docker build --build-arg APP_VERSION=v1 -t hello-courier:v1 app
kind load docker-image hello-courier:v1 --name school     # kind only; Docker Desktop shares its daemon

# 2) pin it: the same placeholder swap as deploy.yml, piped so your working tree stays clean
sed "s|IMAGE_PLACEHOLDER|hello-courier:v1|" k8s/deployment.yaml | kubectl apply -n staging -f -
kubectl apply -n staging -f k8s/service.yaml
kubectl rollout status -n staging deployment/hello-courier --timeout=180s

# 3) v2: new tag, same manifest тАФ watch the pins move one at a time
docker build --build-arg APP_VERSION=v2 -t hello-courier:v2 app
kind load docker-image hello-courier:v2 --name school
kubectl get pods -n staging -w &                          # leave the watcher running
sed "s|IMAGE_PLACEHOLDER|hello-courier:v2|" k8s/deployment.yaml | kubectl apply -n staging -f -
kubectl rollout status -n staging deployment/hello-courier --timeout=180s
kill %1
kubectl run -n staging smoke --rm -i --restart=Never --image=curlimages/curl -- -fsS http://hello-courier/
#    тЖТ ЁЯУо hello from hello-courier-тАж ┬╖ version v2

# 4) rollback, both ways
kubectl rollout undo -n staging deployment/hello-courier          # the emergency lever
kubectl rollout history -n staging deployment/hello-courier
sed "s|IMAGE_PLACEHOLDER|hello-courier:v1|" k8s/deployment.yaml | kubectl apply -n staging -f -   # preferred: redeploy yesterday's tag

# cleanup
kind delete cluster --name school

тЪая╕П рдиреЗрд╣рдореАрдЪреНрдпрд╛ рдЪреБрдХрд╛

тПня╕П рдкреБрдвреЗ

рд╡реНрд╣реЕрди рдлрд▓рдХрд╛рд╡рд░ рдХрд╛рдп рдХрд░рддреЗ рддреЗ рддреБрдореНрд╣реА рдкрд╛рд╣рд┐рд▓реЗ. рдЖрддрд╛ рд╡реНрд╣реЕрди рд╕реНрд╡рддрдГ: pipeline рд▓рд╛ cluster рдордзреНрдпреЗ day pass рдХрд╕рд╛ рдорд┐рд│рддреЛ, рддреА placeholder рдХрд╕рд╛ рдмрджрд▓рддреЗ, рдЖрдгрд┐ рдШрд░реА рдкрд░рдд рдХреЗрд╡реНрд╣рд╛ рдЬрд╛рдпрдЪреЗ рд╣реЗ рддрд┐рд▓рд╛ рдХрд╕реЗ рдХрд│рддреЗ.

git checkout lesson-10-deploy-to-kubernetes

ЁЯФБ Lesson 09 тАФ Deploy strategies & rollback: replace the pins one by one

ЁЯУН You are here: Lesson 09 of 12 ┬╖ Previous: lesson-08-environments-gates ┬╖ Next: lesson-10-deploy-to-kubernetes


ЁЯУж What's in this branch

Lessons 01тАУ08, plus what happens at the board once the van arrives: how a new notice replaces the old one without a reader ever facing a blank wall, and how to put yesterday's notice back. Real files:

ЁЯзТ Explain like I'm 5

The main notice board ЁЯУМ holds two copies of today's notice, so a crowd can read from both sides. A new version arrives. How do you swap it?

Rolling ЁЯФБ: pin ONE new copy next to the old two (three on the board for a moment), wait until someone confirms it is actually readable ("are you ready?" тАФ the readiness probe), then take one old copy down. Repeat. The board stays readable the whole time. That is what this repo's Deployment does.

Recreate ЁЯз╣ rips both old copies down, then pins the new ones тАФ simple, but the board is blank for a few seconds. Blue/green ЁЯФ╡ЁЯЯв keeps TWO whole boards: pin the new notice on the empty one, check it, then swing the "read here тЖТ" sign over. Undo = swing it back.

Canary ЁЯРд: show the new notice to one class first. If they do not complain, show everyone. Tools such as Argo Rollouts and Flagger turn "if they do not complain" into an automatic decision тАФ they exist, we only name them here.

And rollback тПк is not magic: yesterday's notice is still in the locker, labeled with yesterday's commit. Pin it again.

ЁЯЧ║я╕П Diagram

flowchart LR
    old["ЁЯУМ board: old, old<br/>2 replicas ready"]
    surge["ЁЯУМ old, old, NEW<br/>maxSurge 1 тЖТ three pins"]
    probe{"тЭУ GET /healthz<br/>readiness probe"}
    step["ЁЯУМ old, NEW<br/>one old pin comes down"]
    done["ЁЯУМ NEW, NEW<br/>rollout complete"]
    stall["тП╕я╕П stalls тАФ old pins stay up<br/>maxUnavailable 0"]
    old -->|"1 apply new image"| surge
    surge -->|"2 asks the new pod"| probe
    probe -->|"3 ok"| step
    step -->|"4 repeat once more"| done
    probe -.->|"5 not ok"| stall

тЭУ What

ЁЯза Old and new run at the same time тАФ plan for it

rolling update ┬╖ replicas 2 ┬╖ maxSurge 1 ┬╖ maxUnavailable 0

t0   old old          2 ready                 apply the new image
t1   old old NEW      2 ready + 1 starting    probe asks NEW: GET /healthz
t2   old NEW          2 ready                 one old pod gone, second NEW starting
t3   NEW NEW          2 ready                 rollout status exits 0 тЖТ the van reports тЬЕ

Between t1 and t3 both versions serve traffic. That is why a database change needs expand / contract: first ship a schema both versions accept (add the new column, keep the old one), then ship the code that uses it, and only later remove what the old code needed. A single "rename the column" migration during a rolling update breaks whichever version is still running.

ЁЯдФ Why

A pipeline that applies YAML is only as safe as the strategy inside that YAML. With maxUnavailable: 0 and a probe, a broken image stalls instead of taking the site down тАФ the old pods keep serving while rollout status waits and finally fails the job. Without the probe, Kubernetes would treat the new pod as fine the instant the process starts and retire the old ones. The Kubernetes school covers the rollout mechanics in L11; here we care about what the van sees: a gate that passes or fails.

ЁЯФз How (in this repo)

The strategy lives in the manifest, not in any pipeline file тАФ all four dialects apply the same k8s/deployment.yaml:

spec:
  replicas: 2
  selector:
    matchLabels: { app: hello-courier }
  strategy:
    type: RollingUpdate                 # lesson 09: replace the pins one at a time
    rollingUpdate: { maxSurge: 1, maxUnavailable: 0 }
  template:
    metadata:
      labels: { app: hello-courier }
    spec:
      containers:
        - name: web
          image: IMAGE_PLACEHOLDER      # CI swaps in ACCOUNT.dkr.ecr.REGION.amazonaws.com/hello-courier:<sha>
          ports: [{ containerPort: 3000 }]
          readinessProbe:               # the rollout only proceeds when the new pod answers /healthz
            httpGet: { path: /healthz, port: 3000 }
            initialDelaySeconds: 2
            periodSeconds: 5

The van waits for the swap and treats a timeout as a failed delivery (deploy.yml, production job):

          kubectl apply -n production -f k8s/
          kubectl rollout status -n production deployment/hello-courier --timeout=180s
      # rollback = kubectl rollout undo deployment/hello-courier -n production   (lesson 09)
      # тАжor, better, re-run this workflow with yesterday's image tag: the tag IS the version.

Blue/green is not in this repo; the shape is two Deployments (color: blue, color: green in their pod labels) and a one-line Service change:

# illustrative тАФ k8s/service.yaml with a color in the selector
spec:
  selector: { app: hello-courier, color: blue }   # flip to green once green passes its checks
  ports: [{ port: 80, targetPort: 3000 }]
ЁЯФБ The same thing in CircleCI
            kubectl apply -n << parameters.namespace >> -f k8s/
            kubectl rollout status -n << parameters.namespace >> deployment/hello-courier --timeout=180s
ЁЯжК The same thing in GitLab CI
    - kubectl apply -n "$NAMESPACE" -f k8s/
    - kubectl rollout status -n "$NAMESPACE" deployment/hello-courier --timeout=180s
ЁЯОй The same thing in Jenkins
      kubectl apply -n ${namespace} -f k8s/
      kubectl rollout status -n ${namespace} deployment/hello-courier --timeout=180s

ЁЯзк Try it

No AWS needed тАФ a local cluster shows the pins moving.

# 0) a local cluster: kind (below) or Docker Desktop тЖТ Settings тЖТ Kubernetes тЖТ Enable
kind create cluster --name school
kubectl create namespace staging

# 1) photocopy v1 with a tag (CI uses the full commit SHA; any tag except "latest" works locally)
docker build --build-arg APP_VERSION=v1 -t hello-courier:v1 app
kind load docker-image hello-courier:v1 --name school     # kind only; Docker Desktop shares its daemon

# 2) pin it: the same placeholder swap as deploy.yml, piped so your working tree stays clean
sed "s|IMAGE_PLACEHOLDER|hello-courier:v1|" k8s/deployment.yaml | kubectl apply -n staging -f -
kubectl apply -n staging -f k8s/service.yaml
kubectl rollout status -n staging deployment/hello-courier --timeout=180s

# 3) v2: new tag, same manifest тАФ watch the pins move one at a time
docker build --build-arg APP_VERSION=v2 -t hello-courier:v2 app
kind load docker-image hello-courier:v2 --name school
kubectl get pods -n staging -w &                          # leave the watcher running
sed "s|IMAGE_PLACEHOLDER|hello-courier:v2|" k8s/deployment.yaml | kubectl apply -n staging -f -
kubectl rollout status -n staging deployment/hello-courier --timeout=180s
kill %1
kubectl run -n staging smoke --rm -i --restart=Never --image=curlimages/curl -- -fsS http://hello-courier/
#    тЖТ ЁЯУо hello from hello-courier-тАж ┬╖ version v2

# 4) rollback, both ways
kubectl rollout undo -n staging deployment/hello-courier          # the emergency lever
kubectl rollout history -n staging deployment/hello-courier
sed "s|IMAGE_PLACEHOLDER|hello-courier:v1|" k8s/deployment.yaml | kubectl apply -n staging -f -   # preferred: redeploy yesterday's tag

# cleanup
kind delete cluster --name school

тЪая╕П Common mistakes

тПня╕П Next

You have seen what the van does at the board. Now the van itself: how a pipeline gets a day pass into the cluster, swaps the placeholder, and knows when to drive home.

git checkout lesson-10-deploy-to-kubernetes
тЖР Previousenvironments gatesNext тЖТdeploy to kubernetes

This page is the lesson's README from the lesson-09-deploy-strategies branch, shown here so the whole School stays on one site. Code files open on GitHub at the same branch.