All Products
Search
Document Center

Container Service for Kubernetes:Perform canary releases by using ALB Ingresses in an ACK cluster

Last Updated:Mar 26, 2026

ALB Ingress supports canary releases based on request headers, cookies, and weights. Use canary annotations to route a portion of traffic to a new Service version during an upgrade, validate the new version in production, and then promote it when ready.

Prerequisites

Before you begin, ensure that you have:

Canary annotations

All canary Ingresses require the alb.ingress.kubernetes.io/canary: "true" annotation. Without it, the canary Ingress conflicts with the production Ingress instead of routing alongside it.

The following annotations control how traffic is split:

Annotation Description
alb.ingress.kubernetes.io/canary Set to "true" to mark this Ingress as a canary. Required on every canary Ingress.
alb.ingress.kubernetes.io/canary-by-header The request header key to match. Requests carrying this header are routed to the canary Service.
alb.ingress.kubernetes.io/canary-by-header-value The header value to match. Must be used together with canary-by-header—has no effect if canary-by-header is not set.
alb.ingress.kubernetes.io/canary-weight The percentage of traffic (0–100) routed to the canary Service for requests that do not match a header or cookie rule. A value of 0 sends no traffic; a value of 100 sends all traffic.
alb.ingress.kubernetes.io/order Controls the order in which routing rules are evaluated. Valid values: 1–1000. Default: 10. A lower value means higher priority. By default, rule order follows the lexicographical order of Ingress namespace and name.

Rule priority: When multiple canary rules are in effect, they are evaluated in this order: header-based > cookie-based > weight-based.

Usage notes

  • Header-based and cookie-based rules cannot be combined with weight-based rules on the same Ingress. Configure them on separate Ingresses, or use custom routing rules for more complex conditions.

  • The host in a canary Ingress must match the host in the production Ingress exactly.

  • Use alb.ingress.kubernetes.io/order to set an explicit priority when multiple canary Ingresses share the same host, rather than relying on lexicographical ordering.

Step 1: Deploy the production Service

Deploy a Deployment, Service, and Ingress for the production version.

  1. Create tea-deploy.yaml with the following content and apply it:

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: tea
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: tea
      template:
        metadata:
          labels:
            app: tea
        spec:
          containers:
          - name: tea
            image: registry.cn-hangzhou.aliyuncs.com/acs-sample/old-nginx:latest
            ports:
            - containerPort: 80
    kubectl apply -f tea-deploy.yaml
  2. Create tea-svc.yaml with the following content and apply it:

    apiVersion: v1
    kind: Service
    metadata:
      name: tea-svc
    spec:
      ports:
      - port: 80
        targetPort: 80
        protocol: TCP
      selector:
        app: tea
      type: NodePort
    kubectl apply -f tea-svc.yaml
  3. Create tea-ingress.yaml with the following content and apply it:

    apiVersion: networking.k8s.io/v1
    kind: Ingress
    metadata:
      name: tea-ingress
    spec:
      ingressClassName: alb
      rules:
       - host: demo.domain.ingress.top  # Replace with your domain name, resolved to the load balancer IP.
         http:
          paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: tea-svc
                port:
                  number: 80
    kubectl apply -f tea-ingress.yaml

Step 2: Deploy the canary Service and routing rules

Deploy the new Service version alongside two canary Ingresses: one that routes requests with a specific header to the canary, and one that splits remaining traffic by weight.

Before applying the canary Ingresses, note the following requirements:

  • Set alb.ingress.kubernetes.io/canary: "true" on every canary Ingress. Without this annotation, the canary Ingress conflicts with the production Ingress.

  • The host field must match the production Ingress host exactly (demo.domain.ingress.top in this example).

  • Header-based and weight-based rules must be on separate Ingresses.

  1. Create canary-deploy.yaml with the following content and apply it:

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: canary
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: canary
      template:
        metadata:
          labels:
            app: canary
        spec:
          containers:
          - name: canary
            image: registry.cn-hangzhou.aliyuncs.com/acs-sample/new-nginx:latest
            ports:
            - containerPort: 80
    kubectl apply -f canary-deploy.yaml
  2. Create canary-svc.yaml with the following content and apply it:

    apiVersion: v1
    kind: Service
    metadata:
      name: canary-svc
    spec:
      ports:
      - port: 80
        targetPort: 80
        protocol: TCP
      selector:
        app: canary
      type: NodePort
    kubectl apply -f canary-svc.yaml
  3. Create a header-based canary Ingress. All requests carrying location: hz are routed to canary-svc. Requests with other headers fall through to the next matching rule. Create canary-header-ingress.yaml with the following content and apply it:

    apiVersion: networking.k8s.io/v1
    kind: Ingress
    metadata:
      annotations:
        alb.ingress.kubernetes.io/canary: "true"
        alb.ingress.kubernetes.io/canary-by-header: "location"
        alb.ingress.kubernetes.io/canary-by-header-value: "hz"
      name: canary-header-ingress
      namespace: default
    spec:
      ingressClassName: alb
      rules:
        - host: demo.domain.ingress.top  # Must match the production Ingress host exactly.
          http:
            paths:
              - backend:
                  service:
                    name: canary-svc
                    port:
                      number: 80
                path: /
                pathType: Prefix
    kubectl apply -f canary-header-ingress.yaml
  4. Create a weight-based canary Ingress. Requests that do not match the header rule are split 50/50 between the production and canary Services. Create canary-weight-ingress.yaml with the following content and apply it:

    apiVersion: networking.k8s.io/v1
    kind: Ingress
    metadata:
      annotations:
        alb.ingress.kubernetes.io/canary: "true"
        alb.ingress.kubernetes.io/canary-weight: "50"
      name: canary-weight-ingress
      namespace: default
    spec:
      ingressClassName: alb
      rules:
        - host: demo.domain.ingress.top  # Must match the production Ingress host exactly.
          http:
            paths:
              - backend:
                  service:
                    name: canary-svc
                    port:
                      number: 80
                path: /
                pathType: Prefix
    kubectl apply -f canary-weight-ingress.yaml

Verify the canary release

  1. Get the ALB instance address:

    kubectl get ing

    Expected output:

    NAME                    CLASS   HOSTS                     ADDRESS                                              PORTS   AGE
    canary-header-ingress   alb     demo.domain.ingress.top   alb-ny3ute4r8yevni****.cn-chengdu.alb.aliyuncs.com   80      8m23s
    canary-weight-ingress   alb     demo.domain.ingress.top   alb-ny3ute4r8yevni****.cn-chengdu.alb.aliyuncs.com   80      8m16s
    tea-ingress             alb     demo.domain.ingress.top   alb-ny3ute4r8yevni****.cn-chengdu.alb.aliyuncs.com   80      7m5s
  2. Verify that requests carrying location: hz always reach the canary:

    for i in $(seq 1 10); do curl -s -H "Host:demo.domain.ingress.top" -H "location:hz" http://alb-ny3ute4r8yevni****.cn-chengdu.alb.aliyuncs.com; done

    All 10 responses return new. The header rule has the highest priority and routes 100% of matching requests to the canary.

  3. Verify that requests without a matching header are split ~50/50:

    for i in $(seq 1 10); do curl -s -H "Host:demo.domain.ingress.top" http://alb-ny3ute4r8yevni****.cn-chengdu.alb.aliyuncs.com; done

    The responses alternate between new (canary) and old (production), roughly half each. Requests carrying an unmatched header (for example, location: bj) follow the same weight-based split.

Step 3: Promote the new version

After the canary runs stably, redirect all production traffic to the new Service and clean up the canary Ingresses.

  1. Update tea-ingress.yaml to point to canary-svc:

    apiVersion: networking.k8s.io/v1
    kind: Ingress
    metadata:
      name: tea-ingress
    spec:
      ingressClassName: alb
      rules:
       - host: demo.domain.ingress.top
         http:
          paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: canary-svc  # Changed from tea-svc.
                port:
                  number: 80
    kubectl apply -f tea-ingress.yaml
  2. Delete the canary Ingresses:

    kubectl delete ing canary-weight-ingress canary-header-ingress
  3. Verify that all traffic reaches the new version:

    for i in $(seq 1 10); do curl -s -H "Host:demo.domain.ingress.top" http://alb-ny3ute4r8yevni****.cn-chengdu.alb.aliyuncs.com; done

    All 10 responses return new. The old Service version is fully deprecated.

What's next

References