Deploy strategies: blue-green and canary
By default a deploy is direct: the new revision of a function takes all traffic as soon as it is ready, and the deployment that served before is torn down. That stays the default. A project can switch on a deploy strategy instead, per function:
| Strategy | What a deploy does |
|---|---|
direct (default) |
The new revision takes all traffic once it is ready (unchanged behaviour). |
blue_green |
The new revision starts next to the one that serves, with 0 % of the traffic and a test URL of its own. A promotion moves all traffic at once — by hand, or by itself once the revision is ready and its health check passes. The previous revision stays warm for a while, so a rollback is instant. |
canary |
The new revision gets a growing share of the traffic in steps (10 % → 50 % → 100 % by default). Each step lasts an interval; when the new revision's error rate or p95 latency exceeds a threshold during a step, the rollout rolls back by itself. |
Strategies apply to functions served over HTTP: the http and workflow triggers — the project's own workload (production) and each of its environments (staging, …), which roll out with the project's settings unless they set their own (Environments). Queue and schedule functions, preview environments and the deploys a region move starts always deploy directly.
Settings#
The strategy is a project setting with options for both strategies; switching between them keeps the options of the other. Changing it creates no version — it changes how the next deploys roll out, not what they run.
| Option | Default | Range | Meaning |
|---|---|---|---|
strategy |
direct |
direct, blue_green, canary |
blue-green is accepted too |
blue_green.auto_promote |
false |
Promote by itself once the new revision is ready; false waits for a promotion |
|
blue_green.health_path |
none | a path starting with / |
With auto_promote: the path must answer 2xx before the promotion |
blue_green.health_checks |
3 |
1–20 | Consecutive 2xx answers needed (one check every 10 s, at most 5 minutes) |
blue_green.keep_previous_minutes |
15 |
0–1440 | How long the previous revision stays warm after the promotion; 0 releases it at once |
canary.steps |
[10, 50, 100] |
1–10 strictly increasing percentages, 1–100 | The new revision's share per step; reaching 100 is the promotion (a list that ends below 100 is promoted after its last step) |
canary.interval_minutes |
5 |
1–1440 | Time per step |
canary.error_rate_threshold |
5 |
0–100 (%), or none | Rolls back above this share of the new revision's requests failing |
canary.p95_threshold_ms |
none | 1–600000, or none | Rolls back above this p95 latency of the new revision |
Where to set it:
Console: the project's settings, Deploy strategy.
API:
GET/PUT /api/projects/{project}/deploy-strategy(below).runr.yaml — the CLI's
runr deployand a git push apply it; only the keys present change, an invalid value is left out with a warning:deploy: strategy: canary # direct | blue-green | canary canary: steps: [10, 50, 100] interval_minutes: 5 error_rate_threshold: 5 p95_threshold_ms: 800 blue_green: auto_promote: true health_path: /healthz health_checks: 3 keep_previous_minutes: 15CLI:
runr strategy set canary --steps 10,50,100 --interval 5m --max-error-rate 5 --max-p95 800ms(CLI).
One deploy can use another strategy than the one of the environment it deploys to: strategy on POST /api/projects/{project}/deploy, runr deploy --strategy direct (or runr rollback --strategy direct for an urgent rollback), the strategy argument of the MCP tools deploy and rollback. The options always come from that environment's settings (production's are the project's).
Canary thresholds#
A canary step is judged by the new revision's own requests — not the project's, of which the stable revision still serves most — over the time the step has run, from the region's Prometheus. The first source that has requests of the revision in the window answers:
- Span metrics of the revision (
source: span_metrics), while tracing is on for the project and its region's tracing backend accepts spans: the series the alert rules use, narrowed to the revision. The runtime wrapper names the revision on every span (runr.revision, fromK_REVISION); the rollout names its revision (<slug>-v<N>-<id>) and gives its traced containers — container images with their own OpenTelemetry SDK too — the resource attributerunr.revision. The collector keeps it as a span metrics dimension (runr_revision, the label RunR queries:OBS_SPANMETRICS_REVISION_LABEL). A collector installed by an earlier release gets that configuration fromregion:reconcile(steptracing, the collector pod rolls once); until then the next source answers. - Knative's request metrics of the revision (
source: knative): the queue-proxy next to every revision pod counts each request by response code class and measures its latency (revision_request_count/revision_request_latencies, or theirkn_names), labelled with the revision — where the region's Prometheus scrapes them. The error rate is the share of5xxanswers. - Neither: the canary progresses on time only (the console and
runr rollout statussay so).
A window needs at least 10 requests of the revision before a threshold is judged; the thresholds are compared every minute and at the end of each step. A rollback names the revision's value and the limit (The new revision's error rate 12.5 % over the last 3 min is above the limit of 5 %.).
Environments#
Each environment of a project deploys with the project's settings unless it sets its own — another strategy, other options, or both; what it does not set follows the project's, also when the project's settings change later. A deploy to the environment, a promotion into it and a rollback of one of its deployments roll out with the environment's settings.
- Its own workload: an environment's rollout runs on the environment's Knative Service (
<slug>-<environment>, e.g.shop-staging) in the environment's region. The new revision starts next to the deployment that serves the environment, its test URL is on the environment's host (https://green-shop-staging.…), the canary thresholds are judged on the environment's own traces, and the previous revision kept warm is the environment's. - On its own: each environment has at most one open rollout, next to production's and the other environments'. A rollout never touches another environment's traffic or deployments — a production rollout's promotion does not end staging's deployment, and a deploy to staging does not replace production's rollout.
- Decided the same way: promote, abort and roll back an environment's rollout by its new deployment (
POST …/deployments/{deployment}/rollout/promote, …), like production's.GET /api/projects/{project}/rollout?environment=stagingis the environment's newest rollout. - Setting it: in the console, the Deploy strategy on the environment's card in the Environments tab (or in the Deployments tab while the environment is selected) — it says whether the environment follows the project's settings; Use the project's goes back. In the API,
environmentonGET(query) andPUT /api/projects/{project}/deploy-strategy(body); aPUTwithenvironmentstores what it sends as the environment's own and needsenvironments.managebesidesprojects.update;reset: truedrops what the environment sets. The answer has the environment's effective settings withenvironment,own(what it sets),inherited(it sets nothing) andproject(the project's strategy). In the CLI,runr strategy show|set --env stagingandrunr strategy reset --env staging.runr.yaml'sdeploy:section is the project's. - What an environment sets is kept with its configuration overrides (
overrides.deploy_strategyof the environment:PATCH …/environments/{environment}can set it too and keeps it when it does not send it). Environments carrydeploy_strategy, the name of the strategy they deploy with. Deleting an environment deletes its rollouts.
A rollout#
A deploy with a strategy, while a deployment serves the project, opens a rollout. With nothing to fall back to — the first deploy, a stopped project, a service that serves nothing on the region's cluster — the deploy is direct.
- The new deployment's revision is created with a name (
<slug>-v<N>-<id>) and a traffic tag; the Knative Service's traffic stays with the revision that serves (100 %), the new one gets 0 %. Its test URL is the tag's route:https://green-<slug>.<function domain>(blue-green) orhttps://canary-<slug>.<function domain>(canary). - When the new revision is ready, its deployment is
runningnext to theactiveone.- Blue-green without
auto_promotewaits for a promotion (waiting_promotion). - Blue-green with
auto_promotechecks the health path (progressing) and promotes itself after enough passes in a row; without a health path it promotes at once. Checks that do not pass within 5 minutes fail the rollout and the traffic stays. - Canary enters its first step (
progressing) and moves to the next one after each interval.
- Blue-green without
- Promotion: all traffic to the new revision; the new deployment becomes
active. A blue-green rollout keeps the previous revision routable (0 %, tagprevious:https://previous-<slug>.<function domain>) and warm — at least one pod — untilkeep_until; its deployment staysrunning. Then the previous revision is released: the service is back to Knative's default (all traffic to the latest revision) and the previous deployment is torn down. A canary releases the previous revision with its promotion. - Abort (any time before the promotion): the new revision leaves the traffic split, the traffic stays where it was and the new deployment is torn down. A canary step above a threshold does the same by itself (
rolled_back). - Rollback (after a blue-green promotion, while the previous revision is kept): all traffic back to the previous revision at once; it is the
activedeployment again and the new one is torn down.
A new deploy while a rollout is open replaces it: the open rollout is aborted (its share goes back first) and the new deploy rolls out from the same revision that serves. A new deploy while a promoted rollout keeps its previous revision releases that revision.
Rollout statuses#
| Status | Meaning |
|---|---|
pending |
The new revision is being created; 0 % of the traffic |
progressing |
Canary steps under way, or blue-green health checks before a promotion |
waiting_promotion |
Blue-green: ready, waiting for a promotion |
promoted |
The new revision has all traffic (released_at once the previous one is released) |
aborted |
Stopped before the promotion — by a user, a newer deploy or a deployment that ended |
rolled_back |
A canary threshold was exceeded, or a user rolled back after the promotion |
failed |
The new revision never became ready, or the blue-green health checks did not pass |
The deployments during a rollout#
| Deployment | During the rollout | After the promotion | After abort / rollback |
|---|---|---|---|
| New ("candidate") | pending, then running |
active |
torn down (shut_down) |
| Previous ("stable") | active |
running while kept warm, then torn down |
active |
The deployment list shows each deployment's part (rollout_role: candidate, stable, previous). While both revisions run, both are metered and the region needs room for both. The deployment that serves while a rollout is under way cannot be deleted (409 rollout_in_progress); deleting the new deployment aborts its rollout first. A stopped project ends its rollout.
API#
| Method and path | Permission | What it does |
|---|---|---|
GET /api/projects/{project}/deploy-strategy |
projects.view |
The settings, eligible (http / workflow trigger), trigger_type, tracing (canary thresholds can be judged), defaults, environment; ?environment= an environment's, with own, inherited and project (Environments) |
PUT /api/projects/{project}/deploy-strategy |
projects.update (with environment also environments.manage) |
A partial update: strategy, blue_green {…}, canary {…}; only the keys sent change, null clears an optional value. environment: what the environment sets of its own; reset: true drops it. 422 for an invalid value or a strategy on a queue / schedule function |
POST /api/projects/{project}/deploy |
projects.deploy |
strategy (optional) overrides the strategy of the environment it deploys to for this deploy; the answer's rollout is the rollout it opened |
GET /api/projects/{project}/rollout |
projects.view |
The newest rollout of the project's own workload, ?environment= of an environment (data: null when it never rolled out) |
GET /api/projects/{project}/deployments/{deployment}/rollout |
projects.view |
The rollout that brought this deployment |
POST …/deployments/{deployment}/rollout/promote |
projects.deploy |
Promote now (progressing or waiting_promotion); 409 rollout_not_promotable |
POST …/deployments/{deployment}/rollout/abort |
projects.deploy |
Abort an open rollout, optional reason; 409 rollout_not_abortable |
POST …/deployments/{deployment}/rollout/rollback |
projects.deploy |
Back to the kept previous revision, optional reason; 409 rollout_not_rollbackable |
The actions answer the rollout; 502 rollout_traffic_failed when the region's cluster did not take the new traffic split (nothing changed — try again), 409 rollout_busy while another change of the same rollout runs.
A rollout carries the environment it rolls out (production: the project's own workload) and its environment_id, its strategy, status, traffic (candidate / stable percentages), revision and stable_revision, test_url and previous_url, the options it runs with, step (canary: index, count, percent, next percent, start and end), health (blue-green checks: passes, required, last answer, deadline), metrics (canary: tracing, source — span_metrics, knative or null —, the revision measured, error rate, p95, request volume and window, the thresholds), keep_until, promoted_at, released_at, finished_at, next_check_at, reason, its events (created, ready, step, health, promoted, aborted, rolled_back, failed, released, traffic_failed — with time, traffic and actor) and can (promote, abort, rollback). Deployments carry rollout and rollout_role; projects carry deploy_strategy (the strategy's name).
Changes are pushed live on the platform channel: deployment.rollout with the rollout and deployment.status for the other deployment's status (WebSocket events).
MCP and CLI#
- MCP tools (details):
deployment_promote,deployment_abort(an open rollout is aborted; after a promotion, while the previous revision is kept, it rolls back), anddeployment_statusshows the rollout; withenvironmentthey act on that environment's current rollout.deployandrollbacktakestrategyand stop waiting once the new revision is ready and its rollout under way. - CLI (details):
runr strategy show|set|reset [--env NAME],runr rollout status|promote|abort|rollback [--env NAME],runr deploy [--env NAME] --strategy … [--wait-rollout].
Audit#
The settings and a user's actions are audited by their route: project.deploy_strategy.update, deployment.rollout.promote, deployment.rollout.abort, deployment.rollout.rollback; a deploy that opened a rollout records it on deployment.create (rollout_id). What a rollout does by itself is recorded with the actor system: deployment.rollout.auto_promoted, deployment.rollout.auto_rolled_back, deployment.rollout.failed, deployment.rollout.released (audit log).
How it works#
- Traffic: the deploy's apply names the new revision (
spec.template.metadata.name) and writesspec.traffic; promotions, steps, aborts and releases change onlyspec.traffic(merge patch). A direct deploy of a project that rolled out before writes Knative's default traffic block back, so nothing a rollout pinned stays. - Revision and image retention: revisions named in
spec.trafficare routed, so Knative's revision garbage collection never removes the revisions a rollout holds, and registry retention keeps the images of every revision the service's traffic names (bothspec.trafficandstatus.traffic), with or without a share. - Keeping the previous revision warm: its PodAutoscaler gets
autoscaling.knative.dev/min-scale: 1for the keep window; a revision that leaves the routes scales to zero again whatever the annotation says. A scheduler that keeps functions warm should warm the revisions that receive traffic — during a canary both, during a blue-green wait the one that serves, not the latest ready one (App\Services\Rollouts\RolloutTraffic::revisionsWithTraffic()reads them from the service'sstatus.traffic). - Health checks go through the region's Kubernetes API proxy to the Knative activator, addressed to the new revision by the
Knative-Serving-Revisionheader: no DNS or public route from RunR to the function is needed, and a revision scaled to zero is started. - Timers: each rollout's next look is a queued job at its
next_check_at(AdvanceRolloutJob); a scheduled job every minute (deploy-rollouts) catches one that was lost and ends rollouts whose deployments ended. - Test URLs are Knative tag routes on the region's ingress, as public as the function's own host.
Operator settings (environment of the API and the queue workers):
| Variable | Default | Meaning |
|---|---|---|
RUNR_ROLLOUT_HEALTH_INTERVAL |
10 |
Seconds between two blue-green health checks |
RUNR_ROLLOUT_HEALTH_TIMEOUT |
10 |
Seconds one health check may take |
RUNR_ROLLOUT_HEALTH_DEADLINE |
300 |
Seconds the checks have to pass after the revision is ready |
RUNR_ROLLOUT_METRICS_INTERVAL |
60 |
Seconds between two threshold checks of a canary step |
RUNR_ROLLOUT_MIN_REQUESTS |
10 |
Requests a canary window needs before a threshold is judged |