Progress · 0/10 sections
- 01 — L4 vs L7 Load Balancing (Days 56–57)
- 02 — Reverse Proxies & API Gateways (Days 58–59)
- 03 — VPC & Cloud Networking (Days 60–61)
- 04 — Docker Internals (Days 62–63)
- 05 — Kubernetes Core Concepts (Days 64–65)
- 06 — Terraform & IaC (Days 66–67)
- 07 — CI/CD Pipelines (Days 68–69)
- 08 — Deployment Strategies (Days 70–71)
- 09 — Vault & Secret Management (Days 72–73)
- 10 — mTLS & Production Security (Days 74–75)
08 — Deployment Strategies (Days 70–71)
9 min read · Days 70–71
08 — Deployment Strategies (Days 70–71)
Core Mental Model: Zero-downtime deployment = traffic gradually shift karo, monitor karo, aur problem pe automatically rollback karo. Database migrations aur deployment decoupled honi chahiye — schema change aur code change same time pe nahi honi chahiye.
Deployment Strategies Overview
Strategy comparison:
Risk Speed Cost Rollback Best For
Rolling Update Medium Medium Low Slow Most services
Blue-Green Low Fast High Instant High-traffic, DB-heavy
Canary Low Slow Medium Fast Major changes, risky releases
Feature Flags Very Low Slow Medium Instant Business logic changes
Production rule: Canary first, then full rollout.
Feature flags for anything affecting user experience.
Rolling Update — Default K8s Strategy
Current state: 5 pods running v1.0
Deploy v1.1:
1. Kill pod-1 (v1.0)
2. Start pod-1-new (v1.1)
3. Wait for readiness probe pass
4. Kill pod-2 (v1.0)
5. Start pod-2-new (v1.1)
... repeat for all pods
During deploy: some pods v1.0, some v1.1 — BOTH live at same time!
API changes must be backward compatible!
apiVersion: apps/v1
kind: Deployment
metadata:
name: user-service
spec:
replicas: 5
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # 1 extra pod during deploy (6 pods max during rollout)
maxUnavailable: 0 # 0 pods down at once (full availability maintained)
# Trade-off: maxUnavailable=0 → slower deploy, but no capacity loss
# maxUnavailable=1 → faster deploy, brief capacity reduction
template:
spec:
containers:
- name: user-service
image: user-service:v1.1
readinessProbe: # ← CRITICAL for zero-downtime
httpGet:
path: /readyz
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
failureThreshold: 3
lifecycle:
preStop:
exec:
command: ["sh", "-c", "sleep 15"]
# LB/Service ko pod remove karne ka time do
# Traffic drain hone ke baad SIGTERMBlue-Green Deployment
Architecture:
Load Balancer
│
├── Blue (v1.0) — LIVE (100% traffic)
└── Green (v1.1) — IDLE (0% traffic)
Deploy process:
1. Green environment (v1.1) deploy karo aur test karo
2. Smoke tests pass?
3. LB switch: Blue → Green (instant, seconds)
4. Blue stands by (immediate rollback possible)
5. After confidence: Blue teardown (cost saving)
6. Next deploy: Green (current) → Blue (new)
Rollback: LB switch back to Blue → instant (seconds)
# Kubernetes Blue-Green with Services
# Two Deployments, one Service points to active
# Blue Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: user-service-blue
labels:
app: user-service
slot: blue
spec:
replicas: 5
selector:
matchLabels:
app: user-service
slot: blue
template:
metadata:
labels:
app: user-service
slot: blue
version: v1.0
spec:
containers:
- name: user-service
image: user-service:v1.0
---
# Green Deployment (new version, deploy quietly)
apiVersion: apps/v1
kind: Deployment
metadata:
name: user-service-green
spec:
replicas: 5
selector:
matchLabels:
app: user-service
slot: green
template:
metadata:
labels:
app: user-service
slot: green
version: v1.1
spec:
containers:
- name: user-service
image: user-service:v1.1
---
# Service — currently pointing to blue
apiVersion: v1
kind: Service
metadata:
name: user-service
spec:
selector:
app: user-service
slot: blue # ← Change this to "green" to switch traffic
ports:
- port: 80
targetPort: 8080# Traffic switch (blue → green)
kubectl patch service user-service \
-p '{"spec":{"selector":{"slot":"green"}}}'
# Rollback (green → blue)
kubectl patch service user-service \
-p '{"spec":{"selector":{"slot":"blue"}}}'Blue-Green Tradeoffs:
✅ Instant rollback (seconds)
✅ Full testing before traffic switch
✅ No v1.0/v1.1 coexistence issues
❌ Double cost during deploy (two full environments)
50 pods × 2 = 100 pods simultaneously
For 50M users setup: significant cost
❌ DB migrations tricky (both versions must share same DB schema)
❌ Stateful services harder (cache warmup, connection pools)
Canary Deployment — Production Best Practice
Send small % of traffic to new version first.
Monitor metrics. Gradually increase if healthy.
Traffic split:
0% → v1.1 (initial — deploy but no traffic)
5% → v1.1 (5% real users, 95% v1.0)
20% → v1.1 (monitoring good)
50% → v1.1 (half and half)
100%→ v1.1 (full rollout, v1.0 removed)
Each stage: wait N minutes, check error rate + latency.
Automatic rollback if thresholds breached.
# Argo Rollouts (recommended for canary in K8s)
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: user-service
spec:
replicas: 10
strategy:
canary:
steps:
- setWeight: 5 # 5% traffic to canary
- pause: {duration: 5m} # 5 min observe karo
- setWeight: 20 # 20% traffic
- pause: {duration: 10m}
- setWeight: 50
- pause: {duration: 10m}
- setWeight: 100 # full rollout
# Automatic rollback conditions (Argo Analysis)
analysis:
templates:
- templateName: success-rate-analysis
startingStep: 1 # analysis starts after first setWeight
args:
- name: service-name
value: user-service
# Traffic management (needs Istio or Nginx)
trafficRouting:
istio:
virtualService:
name: user-service-vsvc
routes:
- primary
selector:
matchLabels:
app: user-service
template:
metadata:
labels:
app: user-service
spec:
containers:
- name: user-service
image: user-service:v1.1
resources:
requests: {cpu: "250m", memory: "256Mi"}
limits: {cpu: "1000m", memory: "512Mi"}
---
# Analysis Template — what to check during canary
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
name: success-rate-analysis
spec:
args:
- name: service-name
metrics:
- name: success-rate
interval: 1m
count: 10 # 10 measurements lao (10 minutes)
successCondition: result[0] >= 0.99 # 99%+ success rate required
failureLimit: 2 # 2 failures → rollback trigger
provider:
prometheus:
address: http://prometheus.monitoring:9090
query: |
sum(rate(http_requests_total{
service="{{args.service-name}}",
status!~"5.."
}[1m])) /
sum(rate(http_requests_total{
service="{{args.service-name}}"
}[1m]))
- name: p99-latency
interval: 1m
successCondition: result[0] <= 0.5 # P99 <= 500ms
failureLimit: 2
provider:
prometheus:
address: http://prometheus.monitoring:9090
query: |
histogram_quantile(0.99,
sum(rate(http_request_duration_seconds_bucket{
service="{{args.service-name}}"
}[1m])) by (le)
)Feature Flags — Decouple Deploy from Release
Problem without feature flags:
New feature → deploy → immediately live to all users
Bug found → rollback entire deployment (not just feature)
50M users affected, can't test in production with real traffic
With feature flags:
New feature → deploy (flag OFF) → 0% users see it
Gradually enable:
Internal → 1% → 5% → 20% → 100%
Bug found → turn flag OFF → immediate (no deployment needed)
Feature flag lifecycle:
1. DARK DEPLOY: code deployed, flag OFF (0% exposure)
2. INTERNAL: company employees only
3. BETA: opt-in users / canary %
4. GRADUAL ROLLOUT: 1% → 5% → 20% → 100%
5. FULL: all users
6. CLEANUP: remove flag code (tech debt prevention)
// Feature flag evaluation in Go (using LaunchDarkly/Unleash/custom)
package feature
import (
"context"
ld "gopkg.in/launchdarkly/go-server-sdk.v6"
)
type Flags struct {
client *ld.LDClient
}
// Evaluate flag for specific user context
func (f *Flags) NewCheckoutEnabled(ctx context.Context, userID string) bool {
user := ld.NewUser(userID)
result, err := f.client.BoolVariation("new-checkout-flow", user, false)
if err != nil {
// Flag evaluation fail → safe default (false = old behavior)
return false
}
return result
}
// Usage in handler
func (h *Handler) HandleCheckout(w http.ResponseWriter, r *http.Request) {
userID := getUserID(r.Context())
if h.flags.NewCheckoutEnabled(r.Context(), userID) {
h.newCheckoutHandler(w, r) // new code path
} else {
h.oldCheckoutHandler(w, r) // safe, proven old path
}
}⚠️ Feature flag cleanup CRITICAL:
Uncleaned flags = dead code + confusion + tech debt
Rule: Flag must be removed within 2 sprints of 100% rollout
Process: JIRA ticket auto-created when flag hits 100%
Code reviewed to remove flag check + dead code branch
DB Migrations — Expand-Contract Pattern
Problem: Monolithic migration approach
Step 1: Rename column users.name → users.full_name
Step 2: Deploy new code using full_name
During deploy: old pods use "name", new pods use "full_name"
BOTH alive simultaneously during rolling update!
→ Old pods: SELECT name → column doesn't exist → 500 error
→ DOWNTIME
Expand-Contract (Zero-Downtime Column Rename)
Goal: Rename users.name → users.full_name without downtime
Phase 1 — EXPAND (backward compatible):
Migration: ADD COLUMN full_name VARCHAR(255)
Copy data: UPDATE users SET full_name = name
Add trigger: keep both columns in sync
Deploy: Code still uses old column "name"
Result: Both columns exist, both have data
Old pods: use "name" ✅
(No new deployment yet)
Phase 2 — MIGRATE CODE:
Deploy: Code switched to use "full_name" column
Old pods (rolling): use "name" (still works, trigger keeps in sync)
New pods (rolling): use "full_name" ✅
Result: All pods eventually use "full_name"
Phase 3 — CONTRACT (cleanup):
Verify: All pods using "full_name", no "name" usage in code
Migration: DROP COLUMN name
Remove trigger
Timeline: Phase 1 → few days → Phase 2 → few days → Phase 3
Downtime: ZERO throughout
-- Phase 1: Expand migration
ALTER TABLE users ADD COLUMN full_name VARCHAR(255);
-- Copy existing data
UPDATE users SET full_name = name WHERE full_name IS NULL;
-- Keep in sync during transition (PostgreSQL trigger)
CREATE OR REPLACE FUNCTION sync_name_columns()
RETURNS TRIGGER AS $$
BEGIN
IF TG_OP = 'INSERT' OR TG_OP = 'UPDATE' THEN
IF NEW.name IS NOT NULL AND NEW.full_name IS NULL THEN
NEW.full_name := NEW.name;
END IF;
IF NEW.full_name IS NOT NULL AND NEW.name IS NULL THEN
NEW.name := NEW.full_name;
END IF;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER sync_name_full_name
BEFORE INSERT OR UPDATE ON users
FOR EACH ROW EXECUTE FUNCTION sync_name_columns();
-- Phase 3: Contract migration (after code cleanup)
DROP TRIGGER sync_name_full_name ON users;
DROP FUNCTION sync_name_columns();
ALTER TABLE users DROP COLUMN name;Production Deployment Rules
Checklist before every production deploy:
1. DATABASE MIGRATIONS:
☐ Migration backward compatible? (Expand phase only)
☐ No column drops alongside code deploy?
☐ Index created CONCURRENTLY? (not blocking)
# PostgreSQL index creation without locking:
CREATE INDEX CONCURRENTLY idx_users_email ON users(email);
# vs: CREATE INDEX idx_users_email ON users(email);
# CONCURRENTLY = reads/writes continue during index build
2. API COMPATIBILITY:
☐ New endpoints additive? (old endpoints still work)
☐ Request/response fields additive? (no field renames in same deploy)
☐ Breaking changes behind feature flag?
3. MONITORING:
☐ Error rate dashboard open
☐ Latency P99 dashboard open
☐ Auto-rollback configured?
☐ On-call engineer notified
4. TIMING:
☐ Not Friday afternoon (weekend incident risk)
☐ Not peak traffic hours (lower blast radius off-peak)
☐ Feature freeze windows respected
5. ROLLBACK PLAN:
☐ kubectl rollout undo ready?
☐ Database migration reversible? (or contract delayed?)
☐ Previous artifact tag known?