Safe Deployment and Rollback Strategies Questions
Releasing changes to production safely and incrementally, and recovering when they fail: blue-green, canary, and rolling deployments, feature flags, dark launches, traffic shifting, and progressive rollout, together with rollback strategies, safe-deploy practices, blast-radius containment, automated recovery, and safe forward/backward migration. Covers deployment orchestration across cloud platforms, staged exposure of new behavior to users, assessing deployment risk, designing reversible releases, and restoring a known-good state quickly. Focuses on how a release reaches production and how it is unwound on failure, distinct from broader incident command, which lives in Enterprise Operations & Incident Management.
Describe how to implement a canary deployment using only native Kubernetes primitives (no service mesh): creating and controlling the canary ReplicaSet, shifting traffic gradually, evaluating metrics, and automating promotion or rollback.
Sample Answer
Direct answer
Without a service mesh, a canary on native Kubernetes primitives means running a SEPARATE, smaller ReplicaSet for the canary version, sharing the same Service (and therefore the same label selector) as the stable ReplicaSet, and controlling the traffic split purely by the RATIO of canary-to-stable pod counts, since without a mesh there's no fine-grained percentage-based routing, only "however many pods exist, roughly that share of traffic."
Structured elaboration
- Two ReplicaSets, one Service: the stable Deployment/ReplicaSet and a separate canary ReplicaSet both carry a label the shared Service selects on (e.g.
app: checkout-api), so Kubernetes' built-in load-balancing (round-robin across matching endpoints) sends traffic to both, with the SPLIT determined by relative pod count, not an explicit percentage. - Shifting traffic gradually: scale the canary ReplicaSet up (and optionally scale stable down proportionally) in steps, for example canary at 1 of 100 total pods (~1%), then 5 of 100 (~5%), then 25, then fully replacing stable. This is coarser-grained than a mesh's weighted routing (you're limited by pod-count granularity, especially at low replica counts) but requires no additional infrastructure.
- Evaluating metrics: since both versions share one Service, you need the canary pods separately LABELED and QUERYABLE (a
version: canarylabel alongside the sharedapplabel) so your metrics system can filter and compare canary-specific metrics against stable, even though both are receiving traffic through the same Service. - Automating promotion/rollback: a script or controller that watches the canary-specific metrics, and on a pass, scales the canary ReplicaSet up (and stable down) to the next step; on a fail, scales canary back to zero and stable back to full, reverting via the same ReplicaSet-scaling mechanism.
Worked example
At 100 total desired pods: canary starts at 1 replica (stable at 99), giving roughly 1% of traffic via Kubernetes' round-robin balancing across matching endpoints. After a clean observation window, canary scales to 5 (stable to 95), then 25/75, then finally canary fully replaces stable (canary scales to 100, stable to 0, and the canary Deployment is effectively promoted to become the new "stable").
Trade-offs and pitfalls
The coarse granularity is the real limitation: at low total replica counts, you can't achieve a genuinely fine percentage (with 10 total pods, the smallest non-zero canary slice is 10%, not 1%), and Kubernetes' round-robin isn't a precise, deterministic percentage split the way a mesh's weighted routing is, it's a rough approximation based on endpoint count. This approach is a reasonable, infrastructure-light starting point for teams without a service mesh, but the imprecision and lack of built-in session affinity or fine-grained routing rules are exactly what a mesh (or a load-balancer with native weighted routing, like an ALB) is built to solve properly.
Architect a deployment platform for a service with a strict 99.999% uptime target that must also handle a schema change which cannot be rolled back: canaries, feature flags, traffic routing, and SLO-driven gates all need to work together. What are the platform's components, and how do you handle a failed, unrollbackable migration?
Sample Answer
Direct answer
Hitting a 99.999% uptime target while also handling an unrollbackable schema change means the platform's SLO-driven gates and canary/feature-flag machinery all have to work together toward one goal: never let the risky, irreversible part of the release reach significant exposure until every reversible layer around it has already proven itself, and have an explicit, tested plan for the specific failure mode where the migration itself goes wrong, since "just roll back" isn't an option there.
Structured elaboration
Components:
- SLO-driven release gate: continuously computed error-budget status (matching the error-budget gate discussed elsewhere in this topic) blocks ANY release, not just this one, when the budget is already thin, since a 99.999% target (about 26 seconds of allowed downtime per month) leaves essentially no room for a risky release compounding an already-degraded state.
- Application-layer canary and feature flags: the APPLICATION code change is rolled out via a cautious, metric-gated canary, fully reversible at the traffic-routing level, completely independent of whether the schema change itself is reversible; this means the vast majority of the release's risk (the new code's behavior) stays instantly rollback-able even while the schema change underneath it is not.
- Traffic routing: the same weighted traffic-shifting mechanism used elsewhere (mesh or LB-based) controls how much traffic reaches the new code path, decoupled from the schema migration's own progress.
- The schema migration itself, handled with maximum caution BECAUSE it can't be rolled back: run against a read-only production clone first (validating the migration's actual effect on real data before touching production at all), executed in the smallest safe increments with extensive monitoring, and, critically, the APPLICATION CODE that depends on the new schema should NOT go live until the migration is fully complete and independently verified, so a migration problem is caught and can be paused/investigated before ANY application code depends on the new state.
- Handling a failed, unrollbackable migration: since code rollback doesn't fix a bad migration, the plan needs a genuinely different mechanism: restoring from a pre-migration snapshot/backup (accepting whatever data-loss window that implies), or a forward-fix migration that CORRECTS the bad state rather than reversing it (if the bad state is understood well enough to construct a corrective migration), decided and rehearsed BEFORE the real migration runs, not improvised if it actually fails.
Worked example
flowchart LR
A[SLO error-budget gate] -->|budget healthy| B[Migration: clone-validated, then run against prod]
B -->|migration verified complete| C[App code canary: 1 percent to 100 percent]
C --> D[Traffic routing: weighted split]
D -->|metrics clean| E[Full rollout]
B -->|migration fails| F[Forward-fix migration OR restore from snapshot]
The migration completing FIRST, verified independently, BEFORE the application canary even starts, means the application-layer rollback safety net (fast, traffic-level, fully reversible) is never contaminated by uncertainty about whether the schema itself is in a good state.
Trade-offs and pitfalls
This sequencing (migration fully verified before any application code depends on it) is slower than running them concurrently, but concurrent execution would mean a migration problem discovered mid-way leaves you with BOTH an uncertain schema state AND live application code already depending on it, compounding two hard problems instead of isolating them. The single most important design decision here is having the corrective-migration or restore-from-snapshot plan REHEARSED before the real migration runs, since discovering during an actual 99.999%-target incident that you don't actually have a tested recovery path for a failed migration is precisely the scenario this whole architecture exists to prevent.
Write a deployment gate that checks a service's remaining SLO error budget before allowing a new deployment: it fetches the SLO configuration, computes the burn rate over a rolling window, and blocks the deploy if the remaining budget falls below a threshold.
Sample Answer
Direct answer
An error-budget deployment gate reads the service's SLO configuration, computes how much of the allowed error budget has already been consumed over the measurement window, and blocks the deploy if the remaining budget drops below a threshold, typically living as a required check right before the production-promotion stage of the pipeline.
Structured elaboration
The gate needs: (1) the SLO's target (say 99.9% availability), from which the allowed error rate is 1 - target; (2) the OBSERVED error rate over the rolling window (30 days is common, though shorter windows react faster to recent degradation); (3) a computation of remaining budget as a percentage of the ALLOWED budget, not of total traffic, since "5% of allowed budget remaining" is a very different, more urgent statement than "5% error rate"; (4) a threshold below which the gate blocks (10% remaining is a common conservative choice).
Worked example (executed)
from dataclasses import dataclass
@dataclass
class SLOConfig:
target_availability: float
window_days: int = 30
def compute_remaining_budget_pct(slo: SLOConfig, observed_error_rate: float) -> float:
allowed_error_rate = 1 - slo.target_availability
remaining_fraction = 1 - (observed_error_rate / allowed_error_rate)
return remaining_fraction * 100
def deployment_gate(slo: SLOConfig, observed_error_rate: float, min_remaining_pct: float = 10.0):
remaining_pct = compute_remaining_budget_pct(slo, observed_error_rate)
return remaining_pct >= min_remaining_pct, remaining_pct
slo = SLOConfig(target_availability=0.999) # allowed error rate = 0.1%
Run against three cases: a healthy service at 0.02% observed error returns (True, 80.0), 80% of budget still available, deploy proceeds. A service that's burned most of its budget, observed at 0.095%, returns (False, 5.0), only 5% left, below the 10% floor, deploy blocked. A service that's blown past its budget entirely, observed at 0.15% against a 0.1% allowance, returns (False, -50.0), a negative number correctly signaling the budget is already exhausted rather than clamping at zero, which matters because "50% over budget" and "exactly at budget" should trigger differently urgent responses even though both block the deploy.
Pipeline placement
This gate sits as a required check immediately before the "promote to production" step, after build/test/staging have already passed, since it's answering "should THIS release happen right now," not "is the code correct." It should have an explicit bypass path for emergency fixes (a rollback or a critical hotfix that's REDUCING risk, not adding it), gated by an approval rather than silently exempt, so the override is visible and auditable.
Trade-offs and pitfalls
A 30-day window reacts slowly to a service that's degrading right now; a shorter window reacts faster but is noisier and can block deploys over a transient blip that's already resolved. The common pitfall is computing remaining budget as a percentage of TOTAL traffic instead of the ALLOWED budget, which massively understates how urgent the situation is: 0.08% error rate sounds fine in isolation, but against a 0.1% allowance it's already 80% of the budget gone.
You're on a canary rollout at 5% traffic when p95 latency rises 1.5x while the error rate stays flat. Walk through your diagnostic steps in order, and how you'd decide whether to continue, pause, or roll back.
Sample Answer
Direct answer
A 1.5x latency increase with a flat error rate during a canary is exactly the ambiguous case automated canary analysis is built for: it's not an obvious failure (nothing's erroring) but it's also not obviously fine, so the right move is a structured diagnostic pass, not an immediate gut call either direction.
Structured elaboration
Step-by-step, in order:
- Confirm it's real, not a sample-size artifact: check the canary's request count for this window; 5% traffic might be a small enough sample that a couple of slow requests skew the p95/p99 without it being a genuine, broad regression.
- Check WHICH percentile moved: did the mean move, or specifically the tail (p99)? A tail-only shift suggests a subset of requests hitting a slow path (a cold cache, a specific input shape), while a broad shift across all percentiles suggests something more systemic.
- Compare against infrastructure-level signals: CPU/memory on the canary pods specifically, are they resource-constrained relative to stable? A canary running on fewer instances than the stable fleet can look "slower" purely from having less capacity per request, not from a code regression.
- Trace a slow request: pull a distributed trace for one of the slow requests and see WHERE the extra time is going; is it in the new code path itself, or in a downstream dependency call that both versions share (which would point away from the deploy as the cause)?
- Check logs for the canary specifically: any new warnings, retries, or timeout patterns that correlate with the deploy?
- Decide: continue if the increase traces to a benign, expected cause (e.g., cold cache that's now warming) and the trend is improving; pause and gather more data if the cause isn't yet clear and you have time to wait; roll back if the trace points to a genuine regression in the new code or if the trend is worsening rather than stabilizing.
Worked example
Tracing a slow canary request shows the extra ~150ms is spent in a downstream inventory-service call that BOTH old and new code make identically, ruling out the new code as the cause; checking resource metrics shows the canary pods are running at higher CPU utilization simply because the canary slice has fewer replicas than its 5% traffic share would proportionally need. In this case: continue the rollout (the latency increase traces to an infrastructure sizing artifact of the canary itself, not a code regression), but flag the canary-sizing mismatch as something to fix before the NEXT canary run.
Trade-offs and pitfalls
The temptation under a flat error rate is to assume "no errors means it's fine," but latency regressions are real user-experience problems even without a single error logged, so treating error rate as the only signal that matters is a common and costly mistake. Equally, panicking and rolling back on the FIRST ambiguous signal without doing the diagnostic work wastes the whole point of running a canary, which is to gather enough information to make a confident call rather than a reflexive one.
Design a GitOps operator that can perform atomic multi‑service deployments based on a dependency graph: when a change touches multiple services, the operator must reconcile all manifests and ensure either all succeed or a safe rollback occurs across services. Describe the data model, reconciliation loop, handling of partial failures, and rollback/compensation semantics.
Sample Answer
Direct answer
Atomic multi-service reconciliation over a dependency graph needs the SAME "all succeed or safe rollback" guarantee a database transaction provides, but GitOps has no equivalent of a database's native transaction mechanism, each service's manifests apply independently through the underlying Kubernetes API, so the operator has to construct that guarantee itself: track each service's individual reconciliation status against the dependency graph's required ORDER, and on ANY service's failure, actively COMPENSATE (roll back) every service that had already succeeded in this same multi-service change, rather than leaving a partially-applied graph in an inconsistent state.
Structured elaboration
Data model. A MultiServiceChange custom resource capturing: the SET of services involved in this specific coordinated change, their DEPENDENCY ORDER (a directed acyclic graph, service B cannot reconcile until service A, which it depends on, has succeeded), each service's OWN manifest reference (a Git commit/digest), and, critically, each service's PRIOR successful state (the last known-good manifest reference for that service, needed as the compensation target if a rollback becomes necessary).
Reconciliation loop. Processes services in DEPENDENCY ORDER (a topological sort of the graph), reconciling each only once its dependencies have themselves reached a succeeded state; this is the mechanism that gives ordering guarantees a plain, independent per-service reconciliation loop does not provide on its own.
Handling of partial failures. If a service in the middle of the ordered sequence FAILS to reconcile (after its own bounded retry), the operator does NOT continue reconciling the REMAINING, not-yet-processed services in the graph (since they may depend on the failed one, and even if they don't directly, the overall multi-service change is now incomplete); it transitions the MultiServiceChange to a compensating state and begins rollback.
Rollback/compensation semantics. Roll back every service that ALREADY succeeded in THIS multi-service change, in REVERSE dependency order (a service's dependents must be rolled back before the service itself, mirroring the forward order's own logic), reverting each to its recorded PRIOR successful state (not simply "delete," since the prior state may itself be a specific, meaningful configuration, not merely "nothing"); the never-reconciled remaining services in the graph need no compensation at all, since they were never actually changed.
Worked example
A MultiServiceChange spanning three services with dependency order network-policy before auth-service before checkout-service (checkout depends on auth, auth depends on the network policy being in place first):
network-policyreconciles successfully first (no dependencies).auth-servicereconciles successfully second (its dependency, network-policy, already succeeded).checkout-serviceFAILS to reconcile (its new manifest references a config value that does not exist yet).- The operator transitions to
compensating: rolls backauth-serviceto its PRIOR successful manifest first (checkout, the failed one, was never actually applied, so it needs no rollback, just needs to stop being retried), then rolls backnetwork-policyto its prior state. - Final state: all three services back at their PRE-CHANGE configuration, a clean, fully-compensated failure, rather than network-policy and auth-service left on their NEW configuration while checkout alone failed, which would have been a genuinely inconsistent, partially-migrated state.
Trade-offs and pitfalls
- Common mistake: rolling back services in the SAME order they were applied, rather than REVERSE dependency order. Per the worked example, rolling back
network-policy(whichauth-servicedepends on) WHILEauth-serviceis still running its new configuration risksauth-serviceoperating against a network policy that no longer matches what it expects, briefly recreating the exact kind of inconsistency the whole compensation mechanism exists to avoid; reverse-order rollback (dependents first, dependencies last) is what keeps every INTERMEDIATE state during the rollback itself consistent too, not just the final state. - "Roll back to the prior successful state" requires that prior state to have actually been RECORDED before the new change began, an operator that only tracks the CURRENT desired state, with no memory of what preceded it, cannot perform this rollback at all; this is a real, easy-to-omit data-model requirement, not an implementation detail.
- A service that was never reached in the forward pass (because an earlier dependency failed first) needs NO compensation, per the worked example's
checkout-service, attempting to "roll back" a service that was never actually changed is at best a wasted no-op and at worst risks touching a resource the operator has no legitimate reason to be modifying right now. - This entire mechanism assumes the dependency graph itself is ACCURATE and complete: a graph missing a real dependency risks reconciling (or worse, considering "successful") a service whose actual prerequisite was never satisfied, defeating the ordering guarantee the whole design exists to provide.
Unlock Full Question Bank
Get access to all Safe Deployment and Rollback Strategies interview questions and detailed answers.
Sign in to ContinueJoin thousands of developers preparing for their dream job.