
Mastering Progressive Delivery: Harness, Kubernetes, and Canary Phases
This course teaches how to implement progressive deployments using Harness Phases, Canary, and Primary workloads on Kubernetes. Lecture 1 explains why standard Kubernetes RollingUpdate is insufficient for high-scale systems and introduces the Canary as a small test deployment and the Primary as the stable version, with Harness Phases controlling each step. Lecture 2 details the Canary Phase mechanics: Harness creates a temporary Canary Deployment labeled track: canary, a dedicated Canary Service that routes a small traffic slice via selectors, while the Primary Service continues serving users on track: stable pods. Lecture 3 shows that Kubernetes readiness probes only confirm a port is open; Harness Verification instead pulls Prometheus or New Relic metrics, compares Canary error rates and latency against the Primary baseline, and stops the pipeline if thresholds are exceeded. Lecture 4 contrasts Canary with Blue-Green: Canary uses less infrastructure and limits blast radius to 1-25 % of traffic, but requires backward-compatible APIs and database schemas. Lecture 5 covers the Promotion Phase, where Harness updates the Primary Deployment image tag, waits for the stable pods to become healthy, then deletes the Canary workload and service. Lecture 6 explains automated rollback: on failure Harness shifts traffic back to the untouched Primary, removes the Canary, and requires additive schema changes plus proper SIGTERM handling in Go to avoid data corruption.
The Progressive Delivery Mindset: Beyond Rolling Updates
The Progressive Delivery Mindset: Beyond Rolling Updates
Your readiness probe passed. Every pod is green. The rollout finished in under two minutes. And three hours later, your Go API is silently returning malformed responses to roughly a quarter of your users. That is the scenario that keeps platform engineers up at night, and it is the exact scenario that a standard Kubernetes rolling update cannot prevent on its own.
So what actually happened there? A rolling update is a replacement mechanism. It scales down old ReplicaSets and scales up new ones, governed by two knobs: `maxUnavailable`, which limits how many pods can be down at once, and `maxSurge`, which limits how many extra pods can exist above your desired count. Kubernetes defaults both to twenty-five percent. The update finishes when the new pods pass their readiness probe, and that is, that is the critical gap. A readiness probe answers one question: is this container ready to accept traffic? It does not answer whether the new version of your API is actually behaving correctly under real production load. Think of it like a restaurant health inspection that checks whether the kitchen is open, not whether the food tastes right. Passing the inspection gets you customers. What happens after that is a different story entirely.
This is where the Google SRE team's concept of canarying becomes essential. Their definition is precise and worth sitting with: a canary is a partial, time-limited deployment of a change, followed by an evaluation step that decides whether to continue or stop. Evaluation is the operative word. You are not just deploying fewer replicas and hoping for the best. You are gathering evidence. Harness takes this model and structures it into two explicit groups inside a Kubernetes deployment stage. The first group is the Canary group. Harness creates a separate, temporary Deployment object for your new version, running a small number of pods alongside your existing stable workload. For your Go API, think of it as running, say, two pods of `api-v2` next to your ten pods of `api-v1`. Both versions sit behind the same Kubernetes Service, because the Service selector matches pods from both Deployments. That means a fraction of real traffic, proportional to the pod count, naturally reaches the canary. No special routing infrastructure required at this stage. The canary pods are real, they are serving real requests, and you can watch them. What gets me is how elegant that is — you are using the most basic Kubernetes primitive, a label selector, to split traffic without touching a load balancer config.
Now, the Canary group in Harness is explicitly temporary. After your verification steps run — health checks, log analysis, whatever you wire in — Harness executes a Canary Delete step, which removes that temporary Deployment entirely. Then, and only then, the Primary group begins. The Primary group is your main rolling update, using the replica count defined in your Service Definition manifests. The canary was never meant to stay; it was a controlled experiment with a hard end date. This two-phase structure is what separates Harness's approach from a naive "deploy fewer pods" strategy. Compare it to blue-green for a moment: blue-green runs two full environments simultaneously and flips all traffic at once, which means you need double the resources and your rollback is a traffic switch, not a pod deletion. A rolling update gives you no separate evaluation window at all. The Canary-Primary pattern sits between those two: limited resource overhead, a real evaluation gate, and a clean promotion path. The trade-off, and there is always a trade-off, is that you are running two versions of your API simultaneously during the canary window. For a Go service, that means you need to think carefully about database schema compatibility, gRPC contract versions, and any shared state. A migration that drops a column your old pods still read will cause failures the canary window cannot catch, because the old pods are still running.
Marco, you already know Kubernetes well enough to feel the gap between "the rollout succeeded" and "the release is healthy." That intuition is exactly right, and it is the foundation this entire pattern is built on. A green rollout is not evidence of a good release. It is evidence that your pods started. The Canary-Primary model forces you to treat deployment as a decision process, not a fire-and-forget command. You deploy a small slice, you observe it under real conditions, and you make a deliberate choice to promote or revert. That decision gate is the blast radius control. If your canary pods start returning five-hundred errors, you delete two pods, not ten. Your users barely notice. Without that gate, you are betting your entire production fleet on the same readiness probe that passed in staging. And Marco, that bet is one most senior engineers have lost at least once. The architecture we are going to build together is specifically designed so you never have to lose it again.
The Anatomy of the Canary Phase
Speaker 1
So here's what I keep getting stuck on. You can deploy a canary workload, and it still might not receive a single production request. Those two things can both be true at the same time.
Speaker 2
Yeah, and that tension is exactly where most people get confused. Deploying a new version and routing traffic to it are two completely separate acts.
Speaker 1
How separate are we talking?
Speaker 2
Completely separate. Think of it like opening a second checkout lane at a grocery store. The lane exists, the cashier is ready, but until the manager flips the sign to 'open', zero customers walk in.
Speaker 1
So the canary pods are just... standing there.
Speaker 2
Standing there, passing readiness probes, looking healthy. And that is the point Fowler makes: you deploy first, then you route selected users after confidence improves.
Speaker 1
Wait, so a green readiness probe doesn't mean traffic is flowing?
Speaker 2
Not automatically. A readiness probe answers one question: is this container ready to accept connections? It does not answer whether any Service selector is actually pointing at those pods.
Speaker 1
Okay, so walk me through the selector piece. That's, that's the part I want to nail down.
Speaker 2
Right. So in Harness's canary strategy, you end up with two Deployment objects. The stable one carries a label like track: stable, and the canary gets track: canary.
Speaker 1
And each Service selects one of those tracks.
Speaker 2
Exactly. Your Primary Service selector matches only track: stable. Your Canary Service selector matches only track: canary. Two Services, two distinct pod populations, zero overlap.
Speaker 1
So if the selectors overlap—
Speaker 2
Both get traffic. And you lose the controlled split entirely. Your canary is no longer a canary, it's just extra pods behind the same door.
Speaker 1
How would you even catch that? Like, how do you detect the overlap?
Speaker 2
The hard way, usually. You'd see canary error rates mixed into your primary metrics and wonder why your stable baseline looks degraded. The tell is checking EndpointSlices for each Service and verifying the pod sets don't intersect.
Speaker 1
That's a painful way to find out.
Speaker 2
It is. And it's why the label discipline matters before you touch traffic routing.
Speaker 1
Okay, so now I have two Services with clean selectors. How does a defined percentage of requests actually reach the canary?
Speaker 2
That's where Kubernetes Gateway API comes in. You define an HTTPRoute with two backends: the stable Service and the canary Service. You assign relative weights, say ninety and ten.
Speaker 1
And the controller normalizes those?
Speaker 2
Right, the weights are proportional, not absolute. Ninety and ten, nine hundred and one hundred, same result. Roughly ten percent of requests hit the canary backend.
Speaker 1
So the pod count alone doesn't determine the split.
Speaker 2
No, and that's the thing people assume. Running two canary pods next to ten stable pods gives you a rough split by pod count, but it's not precise. For a controlled percentage, you need the weighted route.
Speaker 1
What does Harness actually create on the Kubernetes side during the canary phase?
Speaker 2
It creates a separate Deployment object based on the manifests in your Service Definition. The image tag, the workload name, the replica count for the canary, those come from Harness expressions you set in the pipeline.
Speaker 1
And then what? Harness just waits?
Speaker 2
Three times, actually. It waits for the canary pods to become healthy before routing any traffic. It waits again after routing, while verification runs. And it waits a third time before starting the Primary rolling update.
Speaker 1
That third wait is the one I wouldn't have thought of.
Speaker 2
Most people don't. The Primary phase is a standard Kubernetes rolling update using the replica count from your manifests. But you don't want it starting while your canary is still being evaluated.
Speaker 1
So what does the Go API need to expose for those health checks to actually mean something?
Speaker 2
Two endpoints, minimum. A liveness endpoint that returns healthy as long as the process isn't deadlocked or corrupted. And a readiness endpoint that returns healthy only when the service is fully initialized and ready to handle requests.
Speaker 1
And those serve different purposes for Kubernetes.
Speaker 2
Readiness gates traffic; liveness triggers a container restart. If you mix them up and your readiness probe is too aggressive, Kubernetes pulls the pod from the Service during a slow startup, and your canary receives zero traffic before you've even evaluated it.
Speaker 1
Which brings us back to the original problem. Deployed but not serving.
Speaker 2
Exactly that image. The canary lane is open, the cashier is ready, but the sign is still flipped to closed because the readiness probe hasn't cleared. The mechanism is consistent all the way through.
Speaker 1
So the specific evidence I'd want to see, to know this is actually working, is what exactly?
Speaker 2
Check the EndpointSlices for both Services: the pod sets should be disjoint. Check the HTTPRoute weights are what you configured. And watch the canary pod's readiness status before you trust any traffic metrics. Those three things together tell you whether the isolation is real.
The Fallacy of 'Ready' vs. 'Healthy'
The alert fired at two forty-seven in the morning. The canary had already been deployed, isolated, and given its configured traffic share—the controlled topology from the previous lesson. Every pod was Running. Every readiness probe was returning two hundred. Harness had promoted the canary to Primary forty minutes earlier, and the deployment log showed nothing but green checkmarks. And yet, roughly one in five requests to the Go API was timing out. Not failing with a five-hundred. Timing out. Silently. The kind of failure that takes twenty minutes to surface in dashboards and another twenty to believe. The routing experiment worked as configured. The failure was in the promotion decision: the evidence used to declare the new version safe was insufficient.
Now, the belief that readiness probes are sufficient is not naive. It has a real, defensible version. A readiness probe, after all, is a live signal from inside the container. It runs throughout the pod's entire lifecycle, not just at startup. If the pod becomes overloaded, a well-designed readiness check can withdraw it from Service endpoints automatically, protecting users. That is genuinely powerful. And a startup probe can shield slow-starting applications, holding liveness and readiness checks back until the process is actually ready to be evaluated. So the argument goes: if you write your probes carefully, Kubernetes handles the rest.
That argument holds until you test it against what a readiness probe actually measures. A readiness check answers one question: should this container receive traffic right now? It does not ask whether your API routes are returning correct data. It does not ask whether your downstream database is responding within acceptable latency. It does not ask whether the, the new version introduced a regression in your payment processing logic. It is like a smoke detector tuned to a particular condition. The detector is working perfectly. The food on the stove has been burning for ten minutes. A successful readiness probe is evidence for the specific condition the check tests, and nothing more.
There is a second failure mode that compounds this, and it is, it's one I find genuinely alarming. Liveness and readiness probes have different consequences, and mixing them up causes real damage. A failed readiness probe removes the pod from Service endpoints. A failed liveness probe tells the kubelet to kill and restart the container. Google's SRE guidance documents a feedback loop where health-check-based restarts under load can worsen the overload, because each restart adds work to the remaining pods. Restarting a struggling pod can worsen the problem. Sometimes it is acceleration toward collapse. So making your readiness probe more comprehensive, more aggressive, is not automatically safer. A probe that performs expensive work adds load precisely when the service is already degraded.
So, what does a real canary gate look like? Harness first confirms that canary pods reach steady state: Running and passing readiness. Then its verification gate must go further. For your Go API, Marco, compare canary error rate, latency, and resource saturation against the Primary workload's baseline, rather than relying on absolute thresholds alone. That comparison is the, the critical piece. A canary returning two percent errors sounds alarming until you see the Primary is also at two percent. Conversely, a canary at one percent errors is a problem if Primary is at zero point one. The number alone tells you almost nothing. The delta tells you everything.
And this is where low canary traffic becomes its own hazard, Marco. Suppose your canary receives fifty requests during the verification window. A single slow response can skew a latency percentile dramatically. That is not a signal. That is noise wearing a signal's clothes. Make minimum request count a Harness promotion gate, not merely a time window. A five-minute window with three hundred requests is evidence; a five-minute window with forty requests is a guess. Define the inconclusive path in advance: hold the canary and extend collection when safe, or roll back when telemetry is unavailable, the window cannot be extended, or a safety limit is breached. Inconclusive is not healthy, and it is not an automatic promotion.
A green readiness status should not override failing user-facing metrics. That is the promotion rule. Define it before deployment: promote only when steady state passes, the minimum sample is met, and canary metrics remain within the agreed error-rate, latency, and saturation deltas from Primary. Roll back when a guardrail fails; hold when evidence is insufficient. Readiness, liveness, and startup probes protect different parts of the container lifecycle. Harness verification decides whether the release is safe for users. The routing mechanics got us a controlled experiment. These gates decide whether Harness promotes that experiment to Primary or stops it.
Strategy Wars: Canary vs. Blue-Green
Speaker 1
Two hundred pods. That's what Marco's team would need to provision just to keep the old environment alive.
Speaker 2
Right. A hundred stable pods, a hundred new ones, both fully provisioned, both serving nothing until the switch flips. That's the blue-green cost in one sentence.
Speaker 1
And then all traffic moves at once.
Speaker 2
Production traffic. One cutover. The new environment goes from zero to one hundred percent of production load in a single moment.
Speaker 1
That's a large blast radius.
Speaker 2
It is. And that's the tension. Blue-green's rollback is genuinely fast — you redirect traffic back to the retained environment. But the initial exposure is total, not gradual.
Speaker 1
So the rollback is fast, but the risk window is huge.
Speaker 2
Exactly. Canary changes the exposure model: a limited portion of real users encounters the new release while the stable version remains the reference. The blast radius is bounded by design.
Speaker 1
Okay, but I want to push on that. Someone could argue blue-green is safer because the new environment is fully tested before any real traffic touches it.
Speaker 2
I've heard that argument. And I'll give it its strongest version: blue-green lets you run integration tests, smoke tests, synthetic traffic, all against the new environment before the cutover. That's real.
Speaker 1
So where does it fail?
Speaker 2
Production load is not synthetic traffic. The Google SRE team is precise about this: a canary reveals capacity, performance, and functional defects under real user traffic. A staging environment, however thorough, is a model of production.
Speaker 1
The model isn't necessarily the real thing.
Speaker 2
Right. At a blue-green cutover, the model is tested against full production demand all at once. Canary instead limits real-user exposure while you gather evidence from production behavior.
Speaker 1
So what does Harness actually do differently between the two?
Speaker 2
In this course’s Harness pattern, Canary is the controlled exposure stage; Primary is the later stage that makes the approved release the main production version. The key comparison here is not the pipeline sequence, but the risk and capacity model each strategy creates.
Speaker 1
So where does rolling update sit in the decision?
Speaker 2
Think of this as a compact decision matrix. Canary gives limited real-user exposure and stronger evidence about uncertain behavior, but promotion is slower and the old and new versions must coexist safely. Blue-green enables a near-instant full-environment switch and fast reversal, but requires duplicate capacity and exposes all traffic at the moment of cutover. Rolling has the lowest operational complexity and capacity overhead, but it gives you no deliberate control-versus-experiment comparison.
Speaker 1
And coexistence is the database-migration trap.
Speaker 2
Exactly. If your Go API drops a column while an older version can still read it, the release is unsafe. API contracts, schemas, and message formats must remain compatible while versions overlap. Blue-green reduces that overlap, but does not eliminate it: draining, rollback, asynchronous consumers, and client retries can all extend the compatibility window.
Speaker 1
So what is the practical rule for this API?
Speaker 2
Use canary when latency, dependencies, or scalability behavior is uncertain. Use blue-green when you need a rapid full-environment switch and have the capacity for it. Use rolling for low-risk, well-understood changes when a controlled experiment is unnecessary.
The Promotion: From Scout to Sovereign
Once the verification gate from the previous lecture approves promotion, Harness enters the Primary phase. Now focus on the mechanics: what happens next, in what order do workloads and traffic move, and what breaks if that order is wrong? That operational sequence is the part many pipeline descriptions reduce to a single bullet point.
So, the canary has passed its gates. What Harness does next is not a simple label swap. The Primary Deployment group performs a Kubernetes rolling update of the actual Deployment object, targeting the replica count defined in your Deployment manifest. It is like a relay race: the canary tests the route, but the baton now passes to the Primary, which runs the full distance. The rolling update is governed by `maxSurge` and `maxUnavailable`, the same knobs you already know from standard Kubernetes rollouts. The Primary rollout must reach steady state before anything else is decommissioned. That sequencing is not optional.
Now, this is the part the polite version of this conversation usually skips. Deleting the canary workload before the new Primary pods are fully Ready creates a gap. During that gap, your routing layer may have no healthy backend for the new version, and requests can fail silently. In the newer Kubernetes Canary flow, canary capacity is typically scaled down as traffic shifts to Primary rather than being deleted before the rollout. Depending on the routing integration, weights may move from ten percent to twenty-five, fifty, and finally one hundred percent. The canary pods are reduced in parallel with those weight changes, not before them. Complete cleanup only after the Primary Service has one hundred percent of traffic and the new Primary pods are Ready.
Suppose you tried a shortcut: instead of running the Primary rolling update, you just relabeled the canary pods as stable. I have seen this proposed more than once. The problem is that canary pods were provisioned at canary scale, typically two or three replicas. Your production Deployment manifest specifies ten, or twenty, or whatever your capacity model requires. Relabeling gives you the right image on the wrong number of pods. Beyond replica count, the Primary rolling update also ensures that Kubernetes records a new rollout revision, which means `kubectl rollout undo` can revert to the previous stable image if something surfaces after promotion. Relabeling leaves no revision trail. That means your rollback story disappears.
Marco, here is where your Go API needs specific attention. During the rolling update, your Go API needs to handle termination signals gracefully. Your Go HTTP server must catch that signal and stop accepting new connections while finishing in-flight requests. The `terminationGracePeriodSeconds` field should be set longer than your API's maximum expected request duration plus any connection-draining time. If a background job or a message-consumer offset commit is in progress, that window needs to cover those too. A server that exits immediately on SIGTERM will drop keep-alive connections mid-stream, and those failures will appear in your canary metrics window as noise, or worse, as false positives that survive into the Primary rollout. The, the signal handling is not a deployment concern. It is a correctness concern that the deployment surface makes visible.
Configure a timeout for the Primary rollout and for each traffic-shift operation. If a Deployment stalls because pods cannot become Ready, an image cannot be pulled, or capacity is unavailable, Harness must fail within a known interval rather than wait indefinitely. Investigate the Deployment conditions, Events, ReplicaSets, and Service endpoints; then either correct the issue and retry or roll back with the retained Deployment revision. Do not leave a partially promoted workload consuming production traffic without an explicit operational decision. After completion, confirm that the Primary Service endpoints match the desired replica count, the rollout is complete, traffic is stable at one hundred percent, and the prior revision remains available for rollback.
When the Safety Net Breaks: Rollbacks
Speaker 1
Suppose the rollback runs, Harness reports success, and the team goes back to sleep. That's the failure right there, before anyone checks a single metric.
Speaker 2
Yeah, and that's the thing people get backwards. A rollback is a recovery action. It is not evidence that the system is safe again.
Speaker 1
So what does safe actually require?
Speaker 2
Confirm traffic is fully back on the known-good Primary. Confirm that Primary has its expected endpoints and replica count. Then inspect rollback-induced errors and every shared state change made by the failed version. The canary failed the previously defined promotion contract; now the question is whether recovery actually restored safety.
Speaker 1
Right.
Speaker 2
And Harness is explicit about this: rollback targets the last successful workload version, but it does not automatically undo every other object or operation that ran in the stage.
Speaker 1
Like what? What gets left behind?
Speaker 2
ConfigMaps, secrets, database migrations. An application might run an additive migration during the canary phase. The rollback restores the old image. The new column is still there.
Speaker 1
So the old code is running against a schema it didn't write.
Speaker 2
Exactly. Which is why the parallel-change pattern exists. Martin Fowler describes it as three phases: expand the schema, migrate the data, then contract. Old and new versions can both read the expanded schema safely.
Speaker 1
So the migration has to be designed for recovery before the deployment even starts.
Speaker 2
Exactly. Treat schema, ConfigMap, and Secret changes as persistent state. By the time you are rolling back, an incompatible change may already be live even though the old image has returned.
Speaker 1
Okay, so what does Harness actually do mechanically when the canary fails verification?
Speaker 2
Two things, immediately. It shifts traffic back to one hundred percent on Primary, and it removes the canary workload. Those happen in that order.
Speaker 1
Why does order matter there?
Speaker 2
Because if you delete the canary pods before the routing layer confirms zero canary traffic, some requests are still in flight to pods that no longer exist.
Speaker 1
And then you have failures on top of a rollback.
Speaker 2
Right. So Harness should verify the EndpointSlices for the canary Service are empty before cleanup begins. That's the confirmation that the traffic-shifting layer has actually removed the canary from routing.
Speaker 1
What if the Primary itself starts degrading right after the canary traffic is removed?
Speaker 2
Treat that as a recovery failure, not as a confusing extension of the canary incident. Preserve the traffic and workload evidence first, then determine what shared state changed.
Speaker 1
How? The image didn't change—
Speaker 2
State drift. The canary wrote to a shared cache, or committed a queue offset, or triggered an external side effect. The Primary pods inherit that state. The old image, new world.
Speaker 1
So the blast radius isn't just the canary pods. It's everything the canary touched.
Speaker 2
That's the right mental model. And it's why Google's SRE team frames the response as detect, mitigate, fix, and roll forward, not just revert and move on.
Speaker 1
They're pretty direct about data corruption too, right?
Speaker 2
Very. Their guidance says a rollback alone may not be sufficient if the release caused data corruption. Recovery can require more than reverting the image.
Speaker 1
The Knight Capital incident is the extreme version of that.
Speaker 2
It is. A faulty deployment enabled a dormant algorithm on seven of eight servers. Rolling back those seven stopped most of the erroneous orders. But the eighth server kept running, and it interacted with the rolled-back code in ways that made things worse.
Speaker 1
The rollback itself worsened the situation.
Speaker 2
the previous version is not automatically safe. It can carry dormant behavior that the current configuration or inputs will trigger.
Speaker 1
So what's the, the minimum a team should actually test before they trust their rollback story?
Speaker 2
I'd hand that back to you. What would Marco's checklist look like?
Speaker 1
Fair. Health checks, revision history, and the rollback steps themselves. At least three scenarios in a game-day exercise.
Speaker 2
A canary metric regression, a state-drift scenario where the canary wrote something the Primary now reads, and a partial-rollback scenario where one cluster or shard still serves the failed version. Each one exercises a different recovery risk.
Speaker 1
And Harness retains evidence after rollback?
Speaker 2
It should. Logs, metric snapshots, the canary Deployment spec. You want that before cleanup runs, because root-cause analysis needs the failed state, not the cleaned-up one.
Speaker 1
One operational note: choose the approved rollback mechanism—revision-based or manifest-based—before an incident.
Speaker 2
Its mechanics are separate from validating recovery. During an incident, the priority is proving that traffic, workload capacity, and shared state are safe again.
Speaker 1
So we're back to the same scene we opened on. Rollback ran. Team went back to sleep.
Speaker 2
Before closing the incident, prove that all traffic is on the restored Primary, its endpoints and replica count are correct, rollback errors are understood, and shared state is compatible with the restored version. The rollback command is not the answer. Recovery validation is.
