Configure a canary deployment¶
Canary deployments release applications incrementally to a subset of users, which allows for the gradual deployment of new application versions without any downtime.
NGINX Ingress Controller supports traffic-splitting policies based on header, cookie, and weight. Whereas header- and cookie-based policies serve to provide a new service version to a subset of users, weight-based policies serve to divert a percentage of traffic to a new service version.
NGINX Ingress Controller uses the following annotations to enable canary deployments:
nginx.ingress.kubernetes.io/canary-by-header
nginx.ingress.kubernetes.io/canary-by-header-value
nginx.ingress.kubernetes.io/canary-by-header-pattern
nginx.ingress.kubernetes.io/canary-by-cookie
nginx.ingress.kubernetes.io/canary-weight
Canary rules are evaluated in the following order:
canary-by-header
canary-by-cookie
canary-weight
Canary deployments require that you create two ingresses: one for regular traffic and one for alternative traffic. Be aware that you can apply only one canary ingress.
You enable a particular traffic-splitting policy by setting the associated
canary annotation to true
in the Kubernetes Ingress resource, as in the
following example:
nginx.ingress.kubernetes.io/canary-by-header: "true"
Refer to Ingress Canary Annotations in the NGINX Ingress Controller documentation for more information.
Example canary setup¶
Deploy two services,
echo-v1
andecho-v2
, using either the MKE web UI or kubectl.apiVersion: v1 kind: Service metadata: name: echo-v1 spec: type: ClusterIP ports: - port: 80 protocol: TCP name: http selector: app: echo version: v1 --- apiVersion: apps/v1 kind: Deployment metadata: name: echo-v1 spec: replicas: 1 selector: matchLabels: app: echo version: v1 template: metadata: labels: app: echo version: v1 spec: containers: - name: echo image: "docker.io/hashicorp/http-echo" args: - -listen=:80 - --text="echo-v1" ports: - name: http protocol: TCP containerPort: 80
apiVersion: v1 kind: Service metadata: name: echo-v2 spec: type: ClusterIP ports: - port: 80 protocol: TCP name: http selector: app: echo version: v2 --- apiVersion: apps/v1 kind: Deployment metadata: name: echo-v2 spec: replicas: 1 selector: matchLabels: app: echo version: v2 template: metadata: labels: app: echo version: v2 spec: containers: - name: echo image: "docker.io/hashicorp/http-echo" args: - -listen=:80 - --text="echo-v2" ports: - name: http protocol: TCP containerPort: 80
Create an Ingress to route the traffic for the regular service:
apiVersion: networking.k8s.io/v1 kind: Ingress metadata: annotations: ingress.kubernetes.io/rewrite-target: / name: ingress-echo spec: ingressClassName: nginx-default rules: - host: canary.example.com http: paths: - path: /echo pathType: Exact backend: service: name: echo-v1 port: number: 80
Verify that traffic is successfully routed:
curl -H "Host: canary.example.com" http://<IP_ADDRESS>:<NODE_PORT>/echo
Expected output:
echo-v1
Canary deployment use cases¶
To provide a subset of users with a new service version using a request header:
Create a canary ingress that routes traffic to the
echo-v2
service using the request headerx-region: us-east
:apiVersion: networking.k8s.io/v1 kind: Ingress metadata: annotations: ingress.kubernetes.io/rewrite-target: / nginx.ingress.kubernetes.io/canary: "true" nginx.ingress.kubernetes.io/canary-by-header: "x-region" nginx.ingress.kubernetes.io/canary-by-header-value: "us-east" name: ingress-echo-canary spec: ingressClassName: nginx-default rules: - host: canary.example.com http: paths: - path: /echo pathType: Exact backend: service: name: nginx-v2 port: number: 80
Verify that traffic is properly routed:
curl -H "Host: canary.example.com" -H "x-region: us-east" \ http://<IP_ADDRESS>:<NODE_PORT>/echo curl -H "Host: canary.example.com" -H "x-region: us-west" \ http://<IP_ADDRESS>:<NODE_PORT>/echo curl -H "Host: canary.example.com" \ http://<IP_ADDRESS>:<NODE_PORT>/echo
Expected output:
echo-v2 echo-v1 echo-v1
To provide a subset of users with a new service version using a cookie:
Create a canary ingress that routes traffic to the
echo-v2
service using a cookie:apiVersion: networking.k8s.io/v1 kind: Ingress metadata: annotations: ingress.kubernetes.io/rewrite-target: / nginx.ingress.kubernetes.io/canary: "true" nginx.ingress.kubernetes.io/canary-by-cookie: "my-cookie" name: ingress-echo-canary-cookie spec: ingressClassName: nginx-default rules: - host: canary.example.com http: paths: - path: /echo pathType: Exact backend: service: name: echo-v2 port: number: 80
Verify that traffic is properly routed:
curl -s -H "Host: canary.example.com" --cookie "my_cookie=always" \ http://<IP_ADDRESS>:<NODE_PORT>/echo curl -s -H "Host: canary.example.com" --cookie "other_cookie=always" \ http://<IP_ADDRESS>:<NODE_PORT>/echo curl -s -H "Host: canary.example.com" \ http://<IP_ADDRESS>:<NODE_PORT>/echo
Expected output:
echo-v2 echo-v1 echo-v1
To route a segment of traffic to a new service version:
Create a canary ingress that routes 20% of traffic to the
echo-v2
service using thenginx.ingress.kubernetes.io/canary-weight
annotation:apiVersion: networking.k8s.io/v1 kind: Ingress metadata: annotations: ingress.kubernetes.io/rewrite-target: / nginx.ingress.kubernetes.io/canary: "true" nginx.ingress.kubernetes.io/canary-weight: "20" name: ingress-echo-canary spec: ingressClassName: nginx-default rules: - host: canary.example.com http: paths: - path: /echo pathType: Exact backend: service: name: echo-v2 port: number: 80
Verify that traffic is properly routed:
for i in {1..10}; do curl -H "Host: canary.example.com" \ http://<IP_ADDRESS>:<NODE_PORT>/echo
Example output:
"echo-v1" "echo-v2" "echo-v2" "echo-v1" "echo-v1" "echo-v1" "echo-v1" "echo-v1" "echo-v1" "echo-v1"