> ## Documentation Index
> Fetch the complete documentation index at: https://notes.kodekloud.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Demo Zero Downtime Canary Deployment

> Guide demonstrating zero downtime canary deployments using Gateway API with NGINX Gateway to split traffic via HTTPRoute weights and progressively shift traffic between app versions.

In this hands‑on demo you'll learn how to perform a zero‑downtime canary (progressive) rollout using the Gateway API. We use the NGINX Gateway implementation for the demo, but the Gateway API resources and concepts apply across implementations (for example HAProxy).

<Frame>
  <img src="https://mintcdn.com/kodekloud-c4ac6d9a/QZ7pWzRtYdnRAGco/images/Gateway-API-with-NGINX-Fabric-Gateway/Advanced-Traffic-Management/Demo-Zero-Downtime-Canary-Deployment/zero-downtime-canary-deployment-demo.jpg?fit=max&auto=format&n=QZ7pWzRtYdnRAGco&q=85&s=06aed066cc014bd1d165f30d6e23dc3e" alt="A presentation slide titled &#x22;Zero-Downtime Canary Deployment&#x22; with a large turquoise curved shape on the right that says &#x22;Demo.&#x22; The slide also shows a small copyright notice for KodeKloud in the corner." width="1920" height="1080" data-path="images/Gateway-API-with-NGINX-Fabric-Gateway/Advanced-Traffic-Management/Demo-Zero-Downtime-Canary-Deployment/zero-downtime-canary-deployment-demo.jpg" />
</Frame>

Overview

* Deploy two versions of the same application: `coffee` (v1) and `coffeev2` (v2).
* Create an `HTTPRoute` attached to the Gateway that splits traffic between the two Services using `weight` to simulate canary rollouts.
* Drive requests to the route and observe how the Gateway distributes traffic as you change weights.

Why use Gateway API for canaries?

* Gateway API provides first‑class routing primitives (`HTTPRoute`, `TCPRoute`, etc.) that support weighted backend splitting.
* Weighted routing enables gradual rollouts without changing client endpoints — zero downtime and safer deployments.

Initial cluster state (before starting)

* Only v1 (`coffee`) is running and no `HTTPRoute` exists yet. The gateway is already deployed.

Example checks (initial):

```bash theme={null}
# No HTTPRoute yet
kubectl get httproute
# Pods currently running (example output)
kubectl get pods
# NAME                                READY   STATUS    RESTARTS   AGE
# coffee-5b9c74f9d9-9rf2f             1/1     Running   0          138m
# gateway-nginx-5f9d4c4ff-2d7zp       1/1     Running   0          136m
```

Quick reference: Kubernetes resources used

| Resource Type | Purpose | Example |
| - | - | - |
| Deployment | Run application replicas | `kubectl apply -f coffeev2-app.yaml` |
| Service | Expose pods to other cluster components | `service/coffeev2` |
| HTTPRoute | Attach host/path routing and weight-based backend traffic splits to a Gateway | `httproute.gateway.networking.k8s.io/splitroute` |
| Gateway (existing) | Entry point for external traffic | `gateway` (sectionName: `http`) |

Step 1 — Deploy the v2 application
Create a Deployment and Service for `coffeev2` (v2 of the same app). This manifest uses the same image as v1, exposes port `8080` in the pod and Service port `80`.

coffeev2-app.yaml:

```yaml theme={null}
apiVersion: apps/v1
kind: Deployment
metadata:
  name: coffeev2
  labels:
    app: coffeev2
spec:
  replicas: 1
  selector:
    matchLabels:
      app: coffeev2
  template:
    metadata:
      labels:
        app: coffeev2
    spec:
      containers:
      - name: coffee
        image: nginxdemos/nginx-hello:plain-text
        ports:
        - containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: coffeev2
spec:
  ports:
  - port: 80
    targetPort: 8080
    protocol: TCP
    name: http
  selector:
    app: coffeev2
```

Apply the manifest and confirm pods/services:

```bash theme={null}
kubectl apply -f coffeev2-app.yaml
# deployment.apps/coffeev2 created
kubectl get pods
# NAME                                READY   STATUS    RESTARTS   AGE
# coffee-5b9c74f9d9-9rf2f             1/1     Running   0          140m
# coffeev2-6bb54b9bc7-v4wt9           1/1     Running   0          10s
kubectl get svc
# NAME            TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)         AGE
# coffee          ClusterIP   10.96.197.88     <none>        80/TCP          140m
# coffeev2        ClusterIP   10.96.83.74      <none>        80/TCP          8s
# gateway-nginx   NodePort    10.96.56.153     <none>        80:31437/TCP    138m
# kubernetes      ClusterIP   10.96.0.1        <none>        443/TCP         162m
```

Step 2 — Create an HTTPRoute that splits traffic
Create an `HTTPRoute` named `splitroute` that attaches to the existing `gateway` (sectionName `http`) and splits requests for host `cafe.example.com` on the `/coffee` path between two backend Services using `weight`.

Initial canary route (75/25):

```yaml theme={null}
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: splitroute
spec:
  parentRefs:
  - name: gateway
    sectionName: http
  hostnames:
  - "cafe.example.com"
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /coffee
    backendRefs:
    - name: coffee
      port: 80
      weight: 75
    - name: coffeev2
      port: 80
      weight: 25
```

Apply the route:

```bash theme={null}
kubectl apply -f canary-route.yaml
kubectl get httproute
# NAME       HOSTNAMES                 AGE
# splitroute ["cafe.example.com"]      38s
```

Testing traffic splitting

* Use `curl` with `--resolve` to send requests to the gateway address while setting the `Host` header to `cafe.example.com`. In these examples we use `localhost:8080`. If your Gateway is exposed as a NodePort (e.g., `31437`) or you prefer to `port-forward`, update the port accordingly.
* The app response includes `Server name` (pod name) and `Server address` so you can identify which backend served each request.

Tip: ensure the gateway is reachable on the chosen port and that DNS Host header is set via `--resolve`.

<Callout icon="lightbulb" color="#1CB2FE">
  If your Gateway is exposed on a NodePort (for example `31437`) or through a load balancer, replace `8080` in the examples with the Gateway’s listening port, or use `kubectl port-forward` to map the gateway to `localhost:8080`.
</Callout>

Representative requests with initial weights (mostly v1):

```bash theme={null}
curl --resolve cafe.example.com:8080:127.0.0.1 http://cafe.example.com:8080/coffee
# Server address: 10.244.0.7:8080
# Server name: coffee-5b9c74f9d9-9rf2f
# Date: 11/Apr/2026:22:16:10 +0000
# URI: /coffee
curl --resolve cafe.example.com:8080:127.0.0.1 http://cafe.example.com:8080/coffee
# Server address: 10.244.0.7:8080
# Server name: coffee-5b9c74f9d9-9rf2f
curl --resolve cafe.example.com:8080:127.0.0.1 http://cafe.example.com:8080/coffee
# Server address: 10.244.0.7:8080
# Server name: coffee-5b9c74f9d9-9rf2f
curl --resolve cafe.example.com:8080:127.0.0.1 http://cafe.example.com:8080/coffee
# Server address: 10.244.0.12:8080
# Server name: coffeev2-6bb54b9bc7-v4wt9
# Request ID: c01682b0a71c65bd6a2bc129b655ec4b
```

<Callout icon="lightbulb" color="#1CB2FE">
  Weighted traffic splitting is probabilistic: a 75/25 weight is an approximate distribution. With a small sample of requests you may see variance; the distribution converges with higher request volume.
</Callout>

Step 3 — Adjust weights to simulate a progressive rollout
Update the `HTTPRoute` weights to gradually shift traffic toward `coffeev2`. Each change is a single manifest edit and `kubectl apply`.

Example: change weights to 90/10

```yaml theme={null}
# Edit canary-route.yaml: set coffee weight to 90, coffeev2 weight to 10
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: splitroute
spec:
  parentRefs:
  - name: gateway
    sectionName: http
  hostnames:
  - "cafe.example.com"
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /coffee
    backendRefs:
    - name: coffee
      port: 80
      weight: 90
    - name: coffeev2
      port: 80
      weight: 10
```

Apply the change:

```bash theme={null}
kubectl apply -f canary-route.yaml
# httproute.gateway.networking.k8s.io/splitroute configured
```

Example: change to 50/50 (even split)

```yaml theme={null}
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: splitroute
spec:
  parentRefs:
  - name: gateway
    sectionName: http
  hostnames:
  - "cafe.example.com"
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /coffee
    backendRefs:
    - name: coffee
      port: 80
      weight: 50
    - name: coffeev2
      port: 80
      weight: 50
```

Apply and test (repeat curl requests). You should see a more even distribution.

Representative 50/50 sequence:

```bash theme={null}
curl --resolve cafe.example.com:8080:127.0.0.1 http://cafe.example.com:8080/coffee
curl --resolve cafe.example.com:8080:127.0.0.1 http://cafe.example.com:8080/coffee
curl --resolve cafe.example.com:8080:127.0.0.1 http://cafe.example.com:8080/coffee
curl --resolve cafe.example.com:8080:127.0.0.1 http://cafe.example.com:8080/coffee
# Server name: coffeev2-6bb54b9bc7-v4wt9
```

Step 4 — Promote v2 to 100% (complete cutover)
When `coffeev2` is validated and healthy, update the route so all traffic goes to v2 and v1 receives none.

Final route for 0/100:

```yaml theme={null}
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: splitroute
spec:
  parentRefs:
  - name: gateway
    sectionName: http
  hostnames:
  - "cafe.example.com"
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /coffee
    backendRefs:
    - name: coffee
      port: 80
      weight: 0
    - name: coffeev2
      port: 80
      weight: 100
```

Apply and verify:

```bash theme={null}
kubectl apply -f canary-route.yaml
# Now all requests should land on coffeev2
curl --resolve cafe.example.com:8080:127.0.0.1 http://cafe.example.com:8080/coffee
# Server name: coffeev2-6bb54b9bc7-v4wt9
```

Best practices for safe canary rollouts

* Start small: begin with a low percentage (for example `1%` → `5%` → `25%`) and increase as confidence grows.
* Monitor errors, latency, logs, and business metrics continuously during each step.
* Automate and track changes via GitOps (ArgoCD, FluxCD) for auditable rollouts and easy rollbacks.
* Remember weights are probabilistic. For precision testing use a sufficiently large request volume or a traffic generator.
* Consider session affinity or consistent hashing if your app requires sticky sessions during canaries.

Links and references

* Gateway API specification: [https://gateway-api.sigs.k8s.io/](https://gateway-api.sigs.k8s.io/)
* NGINX Gateway documentation: [https://www.nginx.com/products/nginx-kubernetes-gateway/](https://www.nginx.com/products/nginx-kubernetes-gateway/)
* Kubernetes documentation: [https://kubernetes.io/docs/](https://kubernetes.io/docs/)
* GitOps with ArgoCD: [https://argoproj.github.io/argo-cd/](https://argoproj.github.io/argo-cd/)
* FluxCD: [https://fluxcd.io/](https://fluxcd.io/)

That's it — you now have a practical zero‑downtime canary demo using Gateway API and the NGINX Gateway.

<CardGroup>
  <Card title="Watch Video" icon="video" cta="Learn more" href="https://learn.kodekloud.com/user/courses/gateway-api-with-nginx-fabric-gateway/module/b4f1d9ae-8b89-4650-a5e1-6665008f40f8/lesson/76fcc1d3-55b2-42e9-83f9-0f1f9c310c94" />

  <Card title="Practice Lab" icon="flask-conical" cta="Learn more" href="https://learn.kodekloud.com/user/courses/gateway-api-with-nginx-fabric-gateway/module/b4f1d9ae-8b89-4650-a5e1-6665008f40f8/lesson/bd0064a7-72e7-44e2-b432-8da60443a11e" />
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.