Kubernetes Deployment Strategies
Choosing the right deployment strategy balances risk, speed, and complexity.
Rolling Update (Default)
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
spec:
replicas: 6
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 2 # Max pods above desired during update
maxUnavailable: 1 # Max pods unavailable during update
selector:
matchLabels:
app: api
template:
metadata:
labels:
app: api
spec:
containers:
- name: api
image: myapp/api:v2
readinessProbe:
httpGet:
path: /health/ready
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
failureThreshold: 3
livenessProbe:
httpGet:
path: /health/live
port: 3000
initialDelaySeconds: 15
periodSeconds: 20
terminationGracePeriodSeconds: 30 # Let requests finish
# Perform rolling update
kubectl set image deployment/api api=myapp/api:v2
# Monitor rollout
kubectl rollout status deployment/api
# Rollback if needed
kubectl rollout undo deployment/api
kubectl rollout undo deployment/api --to-revision=2
Blue-Green Deployment
# Blue (current production)
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-blue
spec:
replicas: 5
selector:
matchLabels:
app: api
version: blue
---
# Green (new version)
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-green
spec:
replicas: 5
selector:
matchLabels:
app: api
version: green
---
# Service points to blue initially
apiVersion: v1
kind: Service
metadata:
name: api
spec:
selector:
app: api
version: blue # Change to "green" to switch traffic
ports:
- port: 80
targetPort: 3000
# Switch all traffic to green
kubectl patch service api -p '{"spec":{"selector":{"version":"green"}}}'
# Verify green is working
kubectl get pods -l version=green
# Scale down blue after confirming green is stable
kubectl scale deployment api-blue --replicas=0
# Or delete blue entirely
kubectl delete deployment api-blue
Canary with Argo Rollouts
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: api
spec:
replicas: 10
strategy:
canary:
steps:
- setWeight: 10 # 10% traffic to canary
- pause: { duration: 5m }
- setWeight: 30
- pause: { duration: 10m }
- analysis:
templates:
- templateName: success-rate
- setWeight: 60
- pause: { duration: 10m }
- setWeight: 100
canaryMetadata:
labels:
version: canary
stableMetadata:
labels:
version: stable
selector:
matchLabels:
app: api
template:
metadata:
labels:
app: api
spec:
containers:
- name: api
image: myapp/api:v2
---
# Analysis template for automated promotion
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
name: success-rate
spec:
metrics:
- name: success-rate
interval: 1m
successCondition: result[0] >= 0.95
failureLimit: 3
provider:
prometheus:
address: http://prometheus:9090
query: |
sum(rate(http_requests_total{job="api",status!~"5.."}[5m]))
/
sum(rate(http_requests_total{job="api"}[5m]))
PodDisruptionBudget
# Ensure minimum availability during voluntary disruptions
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: api-pdb
spec:
minAvailable: 4 # Always keep 4 pods running
selector:
matchLabels:
app: api
Health Checks That Matter
// Liveness vs Readiness vs Startup probes
app.get('/health/live', (req, res) => {
// Liveness: is the process alive? (restart if fails)
res.json({ status: 'alive' })
})
app.get('/health/ready', async (req, res) => {
// Readiness: can it serve traffic? (remove from LB if fails)
try {
await db.query('SELECT 1')
await redis.ping()
res.json({ status: 'ready' })
} catch (err) {
res.status(503).json({ status: 'not ready', reason: err.message })
}
})
app.get('/health/startup', (req, res) => {
// Startup: is initialization complete? (only during startup)
if (appStarted) {
res.json({ status: 'started' })
} else {
res.status(503).json({ status: 'starting' })
}
})