Lesson 35 of 66 · 16 min read · Updated
Canary and Blue/Green Releases with HTTPRoute Weights
On this page
In the previous article you learned how HTTPRoute picks a rule by path, header, query and method, and how filters change a request on the way through. One part of a rule was always trivial: backendRefs had a single entry, so every matched request went to one Service.
A rule can list several backends, each with a weight. The Gateway then splits requests between them in proportion to those weights. That one field is enough to run a canary release, a blue/green switch and an instant rollback, all without touching a Deployment.
You have seen canary and blue/green releases before in this course, in the articles Kubernetes Canary Deployments and Kubernetes Blue/Green Deployments. There, the traffic split came from Pod counts and Service selectors. This article does the same at the Gateway and shows why the result is more precise.
We will cover the following topics:
- How
backendRefsweights split traffic, and what weight0means - Why a weighted split is different from a replica-ratio canary
- A progressive canary: 90/10, then 50/50, then 0/100
- Header-based canaries, so testers reach the new version before anyone else
- Blue/green: two full environments and one weight flip
- Measuring the real split with a
curlloop - Rolling back in one
kubectl apply
Technical Requirements
- The minikube cluster with Envoy Gateway v1.9.2 and the
storefront-gatewayGateway (GatewayClasseg, listenerhttpon port 80) in thestorefrontnamespace. - The
kiosk-api:1.0image from the Services series. - Docker and the .NET 10 SDK to build
kiosk-api:2.0. kubectl,curl, and a shell withsortanduniqfor counting responses.
Core Concepts
Weighted backendRefs
The rules for weight are short:
| Rule | Consequence |
|---|---|
| Weights are relative, not percentages | 9 and 1 split exactly like 90 and 10. Using numbers that add up to 100 just makes them easier to read |
The default weight is 1 | Two backends without weights split 50/50 |
weight: 0 sends no traffic | The backend stays in the route, ready to receive traffic again with a one-number change |
| Weight support is Core | Every conformant implementation must honour it |
The split is decided per request. Each new request draws again, so over a few hundred requests you see roughly the configured ratio, and over a handful you may not.
Replica-Ratio Canary vs Gateway Weights
In the Kubernetes Canary Deployments article, two Deployments shared one label, and one Service selected both. With 9 stable Pods and 1 canary Pod, about 10% of traffic hit the canary. It works, but the traffic share is a side effect of the Pod count.
| Replica-ratio canary | HTTPRoute weights | |
|---|---|---|
| Smallest step | 1 / total Pods. 1% needs 100 Pods | Any ratio, with 2 Pods |
| Scaling the canary | Changes the traffic share | Does not change the traffic share |
| Target specific users | Not possible | Header, cookie or query matches |
| Where the split is decided | kube-proxy, per connection | The Gateway, per request |
| Rollback | Scale the canary to 0 and wait for Pods to terminate | Set its weight to 0; effective in seconds |
| Works for traffic between Pods inside the cluster | Yes | Only for traffic that goes through the Gateway |
The last row is the honest limit. A weighted HTTPRoute on a Gateway controls north-south traffic, from outside the cluster in. A Pod calling kiosk-api-v1 directly by its Service name bypasses the Gateway and the split. Splitting east-west traffic is the job of a service mesh, which can use the same HTTPRoute API through the GAMMA initiative.
Progressive Canary
A canary release moves traffic in stages and checks health between them:
| Stage | v1 weight | v2 weight | What you check before moving on |
|---|---|---|---|
| Start | 100 | 0 | v2 Pods are Ready |
| Canary | 90 | 10 | Error rate and latency of v2 match v1 |
| Half | 50 | 50 | v2 handles real load; resource usage is acceptable |
| Done | 0 | 100 | Nothing; v1 can be removed later |
Both Deployments run at full size during the whole release. That costs more than a replica-ratio canary, but it means any stage can take 100% of traffic immediately, in either direction.
Header-Based Canary
Weights are random. Sometimes you want specific people, such as your QA team, to reach v2 before any real user does. That is a separate rule with a header match:
From the previous article you know why this works: for the same path, a match with a header condition ranks higher than a match without one. Requests carrying x-canary: true always go to v2, whatever the weights of the default rule are.
Blue/Green
Blue/green is the canary with only two stages: 100/0 and 0/100. The blue environment serves everyone. The green environment is deployed in full beside it, tested privately through the header rule, and then all traffic flips at once. Blue keeps running until you are confident, so the rollback is the same flip in reverse.
In the Kubernetes Blue/Green Deployments article the flip was a change to a Service selector. Here it is a change of two weights, with the same effect and two advantages: the header route lets you test green through the real Gateway first, and the change does not touch any Service that other workloads may use.
What Weights Do Not Give You
- Stickiness. A user may hit v1, then v2, then v1 again. If v2 changes the shape of a response or a cookie, design for both versions coexisting. Gateway API has an experimental
sessionPersistencefield, and Envoy Gateway offers consistent hashing through its ownBackendTrafficPolicy, but neither is part of the Standard channel. - Automation. Something still has to change the weights and watch the metrics. Tools such as Argo Rollouts (with its Gateway API plugin) and Flagger automate exactly this loop by editing the HTTPRoute for you. Doing it by hand first, as in this article, is the best way to understand what they do.
Hands-On: Kubernetes Commands
See the current weights
jsonpath prints each backend with its weight. A missing weight means 1.
Change weights without editing a file
A JSON patch on the two weight fields of the default rule. Handy in a pipeline.
Count which version served 200 requests
Each response contains the Pod name, and the Pod name starts with the Deployment name.
Confirm the route was accepted after a change
A typo in a Service name shows up as ResolvedRefs=False with reason BackendNotFound.
Check both versions are ready before shifting traffic
Never give weight to a Deployment that is not fully available.
Watch Pods of both versions
The version label is added as a column.
Step-by-Step Example
The Scenario
The storefront team has built kiosk-api 2.0. It adds a version field to every response. The release plan:
- Deploy v2 next to v1 without sending it any real traffic.
- Let the QA team test v2 through the real Gateway with an
x-canary: trueheader. - Send 10% of real traffic to v2, then 50%, then 100%.
- Practise a rollback.
- Run the next release as a blue/green flip.
Step 1 — Build kiosk-api 2.0
Open the KioskApi project from the Services series and add one field. APP_VERSION comes from the Deployment, so the image is the only thing that changes between releases.
Build it against minikube's Docker daemon, as before:
On Docker Desktop's built-in kind cluster a plain docker build is enough; on a plain kind cluster add kind load docker-image kiosk-api:2.0. A new tag is exactly right here: had you rebuilt kiosk-api:1.0 under the old tag, nodes that already cached it would keep running the old image.
seenClientAddress will show an IPv4-mapped IPv6 address such as ::ffff:10.244.0.18. That is the Envoy Pod's address as Kestrel reports it, not your machine's: the Gateway opens a new connection to the backend.
Step 2 — Two Deployments, Two Services
Both Deployments carry app: kiosk-api, and a second label, version, tells them apart. Each Service selects exactly one version. This is the key difference from the replica-ratio approach: the Gateway, not a shared selector, decides who gets traffic.
The original kiosk-api Deployment from earlier articles is replaced by kiosk-api-v1. Scale it down so its Pods do not confuse the counts:
Before going on, look at what the old kiosk-api Service now does. Its selector is only app: kiosk-api, so it selects all six Pods, v1 and v2 alike. That is precisely the replica-ratio canary from the earlier article, at 50/50, and nobody chose that ratio. It is a good reminder of why each version gets its own Service here. Nothing in this article routes to the old Service.
Step 3 — The Route: v2 Deployed, Dark
Remove any routes left from earlier articles, then apply a route with two rules. Rule 0 is the header canary for testers. Rule 1 is the default for everyone else, with v2 at weight 0.
Start the port-forward in a second terminal if it is not running:
Count 200 requests:
v2 is running, Ready and attached to the route, and receives nothing. That is what "deployed dark" means.
Step 4 — Testers Reach v2 First
Every request with the header goes to v2, every request without it to v1. QA tests the real image, through the real Gateway, on the real host name, and no customer is involved. If v2 is broken, you delete it and nobody outside the team ever knew it existed.
Step 5 — Canary: 90/10
Change the two weights in rule 1:
Close to 90/10, not exact, because each request is an independent draw. Run it again and the numbers move by a few either way.
Now compare with the replica-ratio approach. You have 3 Pods of each version, yet traffic is 90/10. To get 10% with Pod counts you would need 9 v1 Pods for every v2 Pod. Here the Pod count and the traffic share are independent: you can scale v2 for capacity without changing who sees it.
In a real release this is where you stop and watch. Compare v2's error rate and latency with v1's in your monitoring for long enough to see real traffic patterns before moving on.
Step 6 — Half and Half
The JSON patch from the Hands-On section does the same job without editing the file. Rule 1 is index 1 in spec.rules, and its backends are indexes 0 and 1.
If you patch, remember that the file in Git no longer matches the cluster. In a GitOps setup, change the file and let the pipeline apply it.
Step 7 — Roll Back
Suppose the dashboards show v2's error rate climbing at 50%. One patch sends everything back to v1:
Nothing was scaled, restarted or rescheduled. Envoy Gateway pushed new configuration to Envoy, and the next request already went to v1. The v2 Pods are still running, so the team can debug them with the header route while customers are safe on v1. Compare that with the replica-ratio rollback, where you scale the canary to zero and wait for Pods to terminate.
Step 8 — Complete the Release: 0/100
The fix is in and v2 has passed the canary stages again. Finish:
Leave v1 running for a while at weight 0. It is your instant rollback until you are confident. Then scale it down or delete it.
Step 9 — The Next Release as Blue/Green
For the next release, the team prefers a single switch. The names change to describe the roles: blue is what serves customers now, green is the new release. In this cluster, blue is kiosk-api-v2 and green would be a new kiosk-api-v3 Deployment and Service built the same way as in Step 2.
The route has the same shape as before. The header rule points at green, and the default rule has two backends at 100 and 0:
Testing works exactly as in Step 4. The switch is one patch that swaps the two numbers, 100/0 to 0/100, and the rollback is the same patch in reverse. There are no intermediate stages, which is the whole difference between blue/green and canary. Blue/green is simpler to reason about; a canary limits how many users a bad release can hurt. Both use the same field.
You do not need to build v3 to finish this article. You have already seen the flip itself in Steps 7 and 8, which were a blue/green switch in both directions between v1 and v2.
Step 10 — Clean Up
Remove the route and both versioned Deployments and Services, and bring the original kiosk-api Deployment back for the next article.
Summary
- A rule can list several
backendRefs, each with a weight. Weights are relative, default to1, and0means no traffic. Weight support is Core. - The split is decided per request at the Gateway, so the traffic share no longer depends on Pod counts. 10% with 2 Pods is fine.
- Give each version its own Deployment and its own Service with a
versionlabel in the selector. A shared selector silently turns back into a replica-ratio split. - A canary moves through stages such as 100/0, 90/10, 50/50 and 0/100, with a health check between them.
- A second rule with a header match, such as
x-canary: true, lets testers reach the new version through the real Gateway before any customer does. It wins because a header match outranks the same path without one. - Blue/green is the same mechanism with only two states. The switch and the rollback are one weight change each, effective within seconds, with no Pod restarts.
- Weights only control traffic that passes through the Gateway, and they give no session stickiness. Tools like Argo Rollouts and Flagger automate the weight changes for you.
In the next article we move beyond HTTP and expose .NET gRPC services through the same Gateway with GRPCRoute.