Implementing Canary Release for Prod

Author: Gourav Shah
Publisher: School of Devops
Version : v2024.06.03.01


Prepare Prod Environment

Create and switch to prod namespace

kubectl create ns prod

kubectl get ns

kubectl config set-context --current --namespace=prod

validate

kubectl config get-contexts

[sample output]

CURRENT   NAME        CLUSTER     AUTHINFO    NAMESPACE
*         kind-kind   kind-kind   kind-kind   prod

Create a copy of the kustomization code to set up prod with:

cd argo-labs
cp -r staging prod

update NodePort for prod

File : prod/service.yaml

apiVersion: v1
kind: Service
metadata:
  name: vote
spec:
  ports:
  - name: "80"
    nodePort: 30200
    port: 80
    protocol: TCP
    targetPort: 80
  type: NodePort

create previw service kustomization overlay

File: prod/preview-service.yaml

apiVersion: v1
kind: Service
metadata:
  name: vote-preview
spec:
  ports:
  - name: "80"
    nodePort: 30300
    port: 80
    protocol: TCP
    targetPort: 80
  type: NodePort

update kustomization with

  • namespace set to prod
  • path for preview-service.yaml added to patches section

File: prod/kustomization.yaml

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../base
namespace: prod
commonAnnotations:
  supported-by: sre@example.com
labels:
- includeSelectors: false
  pairs:
    project: instavote
patches:
- path: service.yaml
- path: preview-service.yaml

check

kustomize build prod

apply with

kubectl apply -k prod/

validate as

kubectl get all

Create Canary Release

Create prod/rollout.yaml with the patch configurations to update

  • Replicas Count
  • Strategy

as

Filename: prod/rollout.yaml

apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: vote
spec:
  replicas: 5
  strategy:
    blueGreen: null
    canary:
      steps:
      - setWeight: 20
      - pause:
          duration: 10s
      - setWeight: 40
      - pause:
          duration: 10s
      - setWeight: 60
      - pause:
          duration: 10s
      - setWeight: 80
      - pause:
          duration: 10s
      - setWeight: 100

add this rollout overlay spec to prod/kustomization.yaml in patches section as:

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../base
namespace: prod
commonAnnotations:
  supported-by: sre@example.com
labels:
- includeSelectors: false
  pairs:
    project: instavote
patches:
- path: service.yaml
- path: preview-service.yaml
- path: rollout.yaml

If you have kustomize installed, verify the configs from argo-labs as

kustomize build prod

apply

kubectl apply -k prod/

validate

kubectl get all,ro

If you have the Argo Rollouts Dashboard, switch the namespace from top right corner to prod

and check the Canary Release as

Before starting rollout start wathing for the following in 3 different terminals

[Termina 1]

watch kubectl get ro,all --show-labels

[Terminal 2]

watch kubectl describe svc vote

[Terminal 3]

watch kubectl describe svc vote-preview

You could also keep monitoring the Argo Rollouts Dashboard. Launch it if required as

kubectl argo rollouts dashboard -p 3100

Trigger a new rollout by modifying base/rollouts.yaml file with new image tag as

spec:
  containers:
  - image: schoolofdevops/vote:v2

and apply

kubectl apply -k prod
kubectl argo rollouts status vote

Here you could see the progressive canary in action, implementing it step by step, ultimately rolling out the new version.

  • A new replicaset is created to maintain the canary deployment during the rollout.
  • Based on the weightage set in the strategy, proportionate number of pods are maintained for each replicaSet.
  • Gradually, all pods are replaced with new version, shifting 100% traffic to it.

here is the output of the rollout status command above

[sample output]

Progressing - more replicas need to be updated
Paused - CanaryPauseStep
Progressing - more replicas need to be updated
Paused - CanaryPauseStep
Progressing - more replicas need to be updated
Paused - CanaryPauseStep
Progressing - more replicas need to be updated
Paused - CanaryPauseStep
Progressing - more replicas need to be updated
Progressing - updated replicas are still becoming available
Progressing - waiting for all steps to complete
Healthy

Getting Ready to add Traffic Management - Set up Nginx Ingress Controller

Install helm to setup Nginx Ingress Controller. To install helm version 3 on Linux or MacOS, you can follow following instructions.

curl https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash

You could further refer to Official HELM Install Instructions for alternative options.

Verify the installtion is successful,

helm --help
helm version

Launch Nginx Ingress controller using helm as :

helm upgrade --install ingress-nginx ingress-nginx \
  --repo https://kubernetes.github.io/ingress-nginx \
  --namespace ingress-nginx --create-namespace \
  --set controller.hostPort.enabled=true \
  --set controller.service.type=NodePort \
  --set controller.hostPort.ports.http=80 \
  --set-string controller.nodeSelector."kubernetes\.io/os"=linux \
  --set-string controller.nodeSelector.ingress-ready="true"

Check the pod for Nginx Ingress, if its running

kubectl get pods -n ingress-nginx

You may see the pod in pending state. Check why its pending by describing it.

Once you describe, you will see that its pending because it has a nodeSelector defined which is looking for a node with label set to ingress-ready="true".

Check the label on the current nodes

kubectl get nodes --show-labels

Add this lable to first of the worker nodes as


kubectl label node kind-worker ingress-ready="true"

validate

kubectl get nodes --show-labels

This time you should see the label added, and nginx ingress controller running, which can be validated using

kubectl get pods -n ingress-nginx --watch

Wait for the container for nginx ingress controller to be up. You could also validate by connecting to the IPADDRESS of your node where the cluster is beeng setup on port 80, where you should see **404 Not Found** error. This is the sign that nginx is set up as a ingress controller and looking for a request with hostname/path defined.

Add Ingress Rule with Host based Routing

Once you have the ingress controller working, add the following ingress rule

File : prod/ingress.yaml

---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: vote
  namespace: instavote
spec:
  ingressClassName: nginx
  rules:
  - host: vote.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: vote
            port:
              number: 80

add this new manifest resources section of kustomization.yaml so that it gets applied as

File: prod/kustomization.yaml

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../base
- ingress.yaml

and apply with

kubectl apply -k prod/

At this time, you would have ingress rule applied.

You could validate it using

kubectl get ing

kubectl describe ing vote

Also add the host file configuration as per this lab guide and validate you are able to use http://vote.example.com/ to access vote service via ingress.

If you browse to the nginx ingress URI, you should see the app as

With this you have successfully set up Nginx Ingress Controller in front of your prod app and are ready to use Traffic Management features of Argo Rollouts.

Canary with Traffic Routing

Read this document to understand the need for traffic routing. You could set up the traffic routing rules with Nginx by modifying the rollout spec as

File : prod/rollout.yaml

apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: vote
spec:
  replicas: 5
  strategy:
    blueGreen: null
    canary:
      canaryService: vote-preview
      stableService: vote
      trafficRouting:
        nginx:
          stableIngress: vote
      steps:
      - setCanaryScale:
          replicas: 3
      - setWeight: 20
      - pause:
          duration: 10s
      - setWeight: 40
      - pause:
          duration: 10s
      - setWeight: 60
      - pause:
          duration: 10s
      - setWeight: 80
      - pause:
          duration: 10s
      - setWeight: 100

You could refer to Nginx Ingress Controller for Traffic Routing document to understand this spec.

and apply as

kubectl apply -k prod/

Once the new configuration is applied, you could try rolling out a few times by updating the image tag in base/rollout.yaml.

You could watch using the same commands as earlier as well as using Argo Dashboard. You could also watch for a new ingress created for canary service created during rollout as

kubectl describe ing vote-vote-canary

where you will see the weight changing as the release progresses.

e.g.

when weight is set to 20%

Every 2.0s: kubectl describe ing vote-vote-canary                                                     argo-01: Tue Jun  4 08:08:10 2024

Name:             vote-vote-canary
Labels:           <none>
Namespace:        prod
Address:
Ingress Class:    nginx
Default backend:  <default>
Rules:
  Host              Path  Backends
  ----              ----  --------
  vote.example.com
                    /   vote-preview:80 (10.244.1.18:80)
Annotations:        nginx.ingress.kubernetes.io/canary: true
                    nginx.ingress.kubernetes.io/canary-weight: 20
Events:
  Type    Reason  Age               From                      Message
  ----    ------  ----              ----                      -------
  Normal  Sync    20s (x2 over 2m)  nginx-ingress-controller  Scheduled for sync


after weight changed to 40%

Annotations:        nginx.ingress.kubernetes.io/canary: true
                    nginx.ingress.kubernetes.io/canary-weight: 40

While you are rolling our a Canary with traffic routing this time, you will observe that

  • While the release is in progress, unlike earlier, the stable/main replicaSet does not reduce proportionate to step capacity percentage.
  • Ingress Controller/Service Mesh, in this case Nginx, does the job of routing between stable and canary versions, not tied to the proportationte number of pods.
  • This is to make sure that, any time there is a need to abort and roll back, 100% capacity is available with the stable version.

Try rolling out a few times to understand the nuances of how canary works with nginx ingress controller and traffic routing rules.

Publish Changes to Repo

Commit all the changes that you have made so far to the repo as:

git status
git add base/*.yaml
git add prod/*.yaml
git status
git commit -am "added canary releases for prod"
git push origin main

Cleaning Up

Once you are done with this lab, clean up the environment with

kubectl delete -k prod/