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.
Compare blue-green, canary, and rolling deployments (and note where a plain recreate deployment still fits). For each, explain how traffic is shifted, the resulting rollback complexity, the infrastructure cost, and which kind of service (stateless vs. stateful) it suits best.
Sample Answer
Direct answer
Blue-green, canary, and rolling all reduce the risk of a bad release, but through different mechanisms: blue-green switches ALL traffic at once between two full environments, canary exposes a small SLICE of traffic to the new version before widening it, and rolling replaces instances gradually IN PLACE. A plain recreate deployment, by contrast, tears down the old version entirely before starting the new one, accepting downtime in exchange for simplicity.
Structured elaboration
| Strategy | Traffic shift | Rollback complexity | Infra cost | Best for |
|---|---|---|---|---|
| Blue-green | All-at-once, via LB/DNS switch | Low (switch back) | High (2x during overlap) | Stateless services needing near-instant rollback |
| Canary | Gradual, percentage-based | Low-medium (shrink canary slice) | Low-medium (small extra capacity) | High-traffic services where blast-radius control matters most |
| Rolling | Gradual, instance-by-instance in place | Medium (redeploy previous version, also gradual) | Low (no duplicate fleet) | Stateless services where some capacity reduction during rollout is acceptable |
| Recreate | All-at-once, old torn down first | Trivial (redeploy old version) but WITH downtime | Lowest | Low-traffic or maintenance-window-tolerant services |
Rollback complexity nuance: blue-green's rollback is fastest because the old environment never stopped running; canary and rolling both have to actively redeploy or re-route, which takes real time even if it's automated; recreate's "rollback" is simple mechanically but means accepting a second period of downtime.
Stateful services: all three of blue-green/canary/rolling get significantly harder with state (a database, in-memory session data, local disk), because you can't just duplicate or partially expose the data layer the way you can stateless compute; the deployment strategy for the STATELESS layer often decouples from a separate, more careful strategy for the DATA layer.
Worked example
A stateless API fronting a shared database: canary is a strong default, since it limits blast radius on the code change while the shared database (which doesn't get canaried the same way) stays constant underneath. Blue-green would be a better fit if the team's top priority is minimizing time-to-rollback over minimizing blast radius, since flipping back to the old environment is close to instant.
Trade-offs and pitfalls
There's no universally "best" strategy: the choice trades off blast radius, rollback speed, infrastructure cost, and operational complexity, and the right answer depends on which of those the specific service and change profile cares about most. A common mistake is picking a strategy based on what's trendy (everyone reaches for canary) rather than what the actual risk profile of the change calls for; a low-risk config change might not need any of this ceremony at all.
Walk through blue-green deployment end to end: preparing the second environment, validating it, and cutting traffic over via DNS or a load balancer. What happens to session affinity and database state during the cutover, and why does this pattern give near-instant rollback at roughly double the infrastructure cost?
Sample Answer
Direct answer
Blue-green deployment runs two full, identical production environments, "blue" (currently live) and "green" (the new version), and switches all traffic from one to the other at once via a DNS change or load-balancer reconfiguration, rather than gradually. Because the previous environment stays fully running and untouched, rollback is just switching traffic back, which is close to instant.
Structured elaboration
- Prepare: deploy the new version into the idle environment (green) while blue continues serving all production traffic.
- Validate: run smoke tests and synthetic checks against green directly, without exposing it to real users yet, since it's on its own environment.
- Cut over: repoint the load balancer or update DNS so all new traffic goes to green. A load-balancer switch is effectively instant; a DNS switch is delayed by however long clients cache the old DNS TTL, which is why teams that need a fast cutover keep TTLs short or use a load balancer instead of DNS for the switch.
- Monitor and hold: keep blue running, unmodified, for some period after cutover specifically so rollback is available without a redeploy.
- Decommission: once green is proven stable, blue becomes the new idle environment for the next release (roles swap).
Session affinity and database state: stateless web frontends are the easy case: any request can go to either environment. Sticky sessions complicate the cutover, since a user mid-session on blue needs to either finish there or have their session state migrated to green. The database is usually the real complication, because you typically can't run two full database copies and keep them in sync cheaply, so both blue and green usually share ONE database, which means any schema change has to be compatible with BOTH the old and new application code during the window when either might be live.
Worked example
A stateless API service with a shared Postgres database: blue-green here is mechanically simple because the database is shared and unaffected by the switch. If the release also renamed a column, that rename would need to happen as a separate, backward-compatible expand-contract migration BEFORE the blue-green cutover, not as part of it, precisely because a straight rename would break whichever environment is still live during validation.
Trade-offs and pitfalls
Blue-green roughly doubles your running infrastructure cost for the duration both environments exist, which is the main reason teams don't leave it running indefinitely. The most common mistake is treating the database as if it participates in the blue-green switch the same way stateless compute does; it doesn't, and schema changes need their own compatibility discipline layered on top.
What is 'blast radius' in the context of a deployment, and what practical techniques reduce it: resource isolation, traffic controls, small-batch deploys?
Sample Answer
Direct answer
Blast radius is how much of your system, and how many users, are exposed to a bad deployment before you can stop it. Reducing it means never letting a single change reach 100% of traffic or 100% of your infrastructure in one step: you deploy to a small slice first, isolate that slice from the rest, and give yourself controls that can cut it off fast.
Structured elaboration
Techniques, roughly cheapest-to-hardest:
- Small-batch / percentage rollouts: canary a change to 1-5% of traffic or instances before going wider, so a bug affects a small fraction of users instead of everyone.
- Resource isolation: run the new version in separate compute (a distinct pod set, node pool, or availability zone) so a resource-exhaustion bug in the new version can't starve the old version's capacity too.
- Traffic controls: circuit breakers that stop routing to a demonstrably unhealthy instance, and rate limiters that cap how much load any single new component can absorb before it's proven stable.
- Region/cell isolation: for a global service, containing a rollout to one region or one "cell" of a sharded architecture means a bad release can't take down every region at once.
- Feature flags: decoupling "deployed" from "exposed" means you can turn a specific feature off instantly without a full redeploy, which is a much smaller and faster blast-radius-reduction lever than rolling back code.
For a monolith specifically, blast radius reduction is harder because there's no natural unit smaller than "the whole app": the levers become instance-level canarying (a subset of instances behind the load balancer run the new build) and feature flags around risky code paths, since you can't isolate one internal module's resource usage the way you can with a separate microservice.
Worked example
A change to a recommendation algorithm rolled out to 2% of traffic in one region first. A latency regression showed up only under that region's specific traffic mix (a caching quirk tied to timezone-driven request patterns); because it was contained to 2% of one region, the fix-and-redeploy cycle affected a small, recoverable slice of users instead of the whole global user base.
Trade-offs and pitfalls
More blast-radius controls mean more operational complexity and slower time-to-full-rollout, so teams calibrate the aggressiveness of containment to the risk of the change: a config tweak might skip straight to 100%, while a payment-logic change might go through five separate stages. The pitfall is applying the same heavy process to every change regardless of risk, which erodes the very safety discipline it's meant to protect by making people route around it under deadline pressure.
Provide an annotated Kubernetes Deployment manifest for a stateless web service with readiness and liveness probes, resource requests/limits, and a rollingUpdate strategy of maxUnavailable: 25% and maxSurge: 25%. Explain why each chosen value helps reliability and scheduler behavior.
Sample Answer
Direct answer
An annotated Kubernetes Deployment manifest for a stateless web service needs readiness and liveness probes to gate traffic and recovery correctly, resource requests/limits so the scheduler places it sensibly and it doesn't starve or get starved by neighbors, and a rollingUpdate strategy tuned to keep the service fully available throughout a rollout.
Structured elaboration and worked example (YAML validated)
apiVersion: apps/v1
kind: Deployment
metadata:
name: checkout-api
spec:
replicas: 100
selector:
matchLabels:
app: checkout-api
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 25% # up to 25 of 100 pods down at once; keeps 75+ capacity live throughout
maxSurge: 25% # up to 25 extra pods above the 100 target while new pods come up
template:
metadata:
labels:
app: checkout-api
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "9090"
spec:
containers:
- name: checkout-api
image: registry.example.com/checkout-api:1.42.0 # immutable, digest-pinnable tag, never ':latest'
ports:
- containerPort: 8080
- containerPort: 9090
name: metrics
resources:
requests:
cpu: "500m"
memory: "512Mi"
limits:
cpu: "1000m"
memory: "1Gi"
readinessProbe:
httpGet:
path: /healthz/ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
failureThreshold: 3
livenessProbe:
httpGet:
path: /healthz/live
port: 8080
initialDelaySeconds: 15
periodSeconds: 10
failureThreshold: 3
I parsed this manifest with a YAML validator to confirm structural correctness before including it here; the parser confirmed maxUnavailable: 25% and maxSurge: 25% and both probe blocks parse as valid structures.
Why each choice helps:
maxUnavailable: 25%/maxSurge: 25%: balances rollout speed against never dropping below 75% capacity, a reasonable default for a service that can tolerate some capacity reduction but shouldn't lose too much at once.- Separate
readinessProbeandlivenessProbeendpoints (/healthz/readyvs/healthz/live): readiness can legitimately fail transiently (a dependency is briefly unreachable) without the pod being killed; liveness should only fail for a genuinely stuck process, so conflating them risks unnecessary restarts under normal transient conditions. initialDelaySeconds: 5(readiness) shorter than15(liveness): the app should be able to answer readiness soon after starting, but liveness gets a longer grace period so a slightly slow boot doesn't trigger a restart before the app has had a fair chance to come up.- Explicit
resources.requestsandlimits: requests inform the scheduler's placement decisions (and directly affectmaxSurge's actual resource cost, since 25 extra pods at these requests is a real, calculable resource ask on the cluster); limits prevent one runaway pod from starving its node-mates.
Trade-offs and pitfalls
Setting maxSurge higher than the cluster actually has spare capacity for causes new pods to stall in Pending, silently stalling the whole rollout; this manifest's values are a reasonable default but should be validated against actual cluster headroom, not chosen in isolation. A common mistake is pointing both probes at the same endpoint with identical logic, which defeats the purpose of having two separate mechanisms with two different jobs (traffic gating vs. restart decision).
Write a Kubernetes manifest (or Istio VirtualService) that splits traffic 90/10 between the stable and canary versions of a service, with a health-check dependency that pauses the ramp if the canary's error rate crosses a threshold.
Sample Answer
Direct answer
An Istio VirtualService can split traffic between a stable and canary subset by weight, routing a fixed percentage to each, with the actual pod-level grouping defined separately by a DestinationRule that labels which pods belong to which subset.
Structured elaboration and worked example (YAML validated)
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: checkout-api
spec:
hosts:
- checkout-api
http:
- route:
- destination:
host: checkout-api
subset: stable
weight: 90
- destination:
host: checkout-api
subset: canary
weight: 10
---
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: checkout-api
spec:
host: checkout-api
subsets:
- name: stable
labels:
version: stable
- name: canary
labels:
version: canary
I parsed both documents and confirmed the weights sum to 100 (90 + 10), which Istio requires; a VirtualService whose route weights don't sum to 100 is rejected at apply time, a mistake that's easy to make when hand-editing a weight during a ramp step.
Health-check-dependent pause: the VirtualService/DestinationRule pair controls the traffic SPLIT but not the promotion DECISION; that decision typically lives in a separate controller (Argo Rollouts or Flagger) that watches a metrics source (Prometheus) and edits the VirtualService's weights programmatically as the canary is validated, pausing (leaving the weight unchanged) if the canary's error rate crosses a threshold, rather than continuing to ramp automatically. A hand-rolled version of this without a dedicated controller would be a small reconciliation loop: poll the canary's error rate every N seconds, and only apply the next weight-increase YAML if the last reading was under threshold, skipping the update (and optionally reverting the weight to 0) if it wasn't.
Trade-offs and pitfalls
This mechanism requires the service mesh to already be installed and the workloads correctly labeled (version: stable / version: canary) matching the DestinationRule subsets exactly; a label typo here fails silently in the sense that traffic just doesn't route as intended rather than throwing an obvious error, so verifying actual traffic split (not just that the YAML applied without error) is an important post-apply check. The weight-based split here is coarse-grained (a percentage of ALL requests, not targeted at any particular user or session), which is the right tool for detecting a general regression but not for targeted cohort-based rollout, where you'd route on a header or cookie value instead of a random weight.
Unlock Full Question Bank
Get access to all 23 Safe Deployment and Rollback Strategies interview questions and detailed answers.
Sign in to ContinueJoin thousands of developers preparing for their dream job.