Deploying software shouldn't mean downtime. Users shouldn't see errors during your update. Modern deployment strategies eliminate downtime, reduce risk, and enable instant rollbacks. In this lesson, you'll learn rolling updates, blue-green deployments, canary releases, and feature flags—the techniques that let you deploy with confidence.

1. Learning Objectives

By the end of this lesson, you will be able to:

  • Implement rolling updates for gradual changes
  • Set up blue-green deployments for instant rollbacks
  • Use canary releases to test with real traffic
  • Implement feature flags for controlled rollouts
  • Choose the right strategy for your application
  • Automate deployments in CI/CD pipelines

2. Why This Matters

Real-world scenario: Your team ships 50 deployments per day. One deployment introduces a bug that affects 1% of users. Without proper strategies, you either roll back everyone or let the bug affect all users. With canary deployments, only 1% see the bug. With feature flags, you disable the feature instantly.

3. Core Concepts

Rolling Updates (Kubernetes Native)

# rolling-update-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  replicas: 5
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1        # Can create 1 extra pod
      maxUnavailable: 0  # Never go below desired replicas
  template:
    spec:
      containers:
      - name: app
        image: myapp:v2

# Rolling update process:
# 1. Creates 1 new pod with v2
# 2. Waits for it to be ready
# 3. Removes 1 old pod
# 4. Repeats until all 5 pods are v2

# Commands
kubectl set image deployment/myapp app=myapp:v2
kubectl rollout status deployment/myapp
kubectl rollout history deployment/myapp
kubectl rollout undo deployment/myapp

# For zero-downtime, configure readiness probes
readinessProbe:
  httpGet:
    path: /ready
    port: 8080
  initialDelaySeconds: 5
  periodSeconds: 5
Rolling updates in Kubernetes

Blue-Green Deployment

# blue-green-deployment.yaml
# Blue (current production) - v1
# Green (new version) - v2

# Blue deployment (current)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-blue
  labels:
    version: blue
spec:
  replicas: 5
  selector:
    matchLabels:
      app: myapp
      version: blue
  template:
    metadata:
      labels:
        app: myapp
        version: blue
    spec:
      containers:
      - name: app
        image: myapp:v1

# Green deployment (new)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-green
  labels:
    version: green
spec:
  replicas: 5
  selector:
    matchLabels:
      app: myapp
      version: green
  template:
    metadata:
      labels:
        app: myapp
        version: green
    spec:
      containers:
      - name: app
        image: myapp:v2

# Service (points to active version)
apiVersion: v1
kind: Service
metadata:
  name: myapp-service
spec:
  selector:
    app: myapp
    version: blue  # Changes to 'green' during switch
  ports:
  - port: 80
    targetPort: 8080

# Blue-Green deployment script
#!/bin/bash
# deploy-blue-green.sh

echo "Deploying green version..."
kubectl apply -f green-deployment.yaml

echo "Waiting for green pods to be ready..."
kubectl wait --for=condition=ready pod -l version=green --timeout=300s

echo "Running smoke tests on green..."
GREEN_IP=$(kubectl get pods -l version=green -o jsonpath='{.items[0].status.podIP}')
curl -f http://$GREEN_IP:8080/health || exit 1

echo "Switching traffic to green..."
kubectl patch service myapp-service -p '{"spec":{"selector":{"version":"green"}}}'

echo "Waiting for traffic to drain from blue..."
sleep 30

echo "Scaling down blue..."
kubectl scale deployment myapp-blue --replicas=0

echo "Blue-green deployment complete!"

# Rollback (instant - just switch service back)
kubectl patch service myapp-service -p '{"spec":{"selector":{"version":"blue"}}}'
Blue-green deployment implementation

Canary Deployment

# canary-deployment.yaml
# Send small percentage of traffic to new version

apiVersion: v1
kind: Service
metadata:
  name: myapp-service
spec:
  selector:
    app: myapp
  ports:
  - port: 80

---
# Stable version (95% traffic)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-stable
spec:
  replicas: 19
  selector:
    matchLabels:
      app: myapp
      track: stable
  template:
    metadata:
      labels:
        app: myapp
        track: stable
    spec:
      containers:
      - name: app
        image: myapp:v1

---
# Canary version (5% traffic)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-canary
spec:
  replicas: 1
  selector:
    matchLabels:
      app: myapp
      track: canary
  template:
    metadata:
      labels:
        app: myapp
        track: canary
    spec:
      containers:
      - name: app
        image: myapp:v2

# Traffic splitting with Istio
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: myapp-virtualservice
spec:
  hosts:
  - myapp-service
  http:
  - route:
    - destination:
        host: myapp-stable
        subset: v1
      weight: 95
    - destination:
        host: myapp-canary
        subset: v2
      weight: 5

# Progressive canary (increase traffic gradually)
#!/bin/bash
# progressive-canary.sh

# Start with 1%
kubectl patch virtualservice myapp-virtualservice --type='json' \
  -p='[{"op": "replace", "path": "/spec/http/0/route/1/weight", "value": 1}]'

sleep 300  # Monitor for 5 minutes

# Increase to 10%
kubectl patch virtualservice myapp-virtualservice --type='json' \
  -p='[{"op": "replace", "path": "/spec/http/0/route/1/weight", "value": 10}]'

sleep 300

# Increase to 50%
kubectl patch virtualservice myapp-virtualservice --type='json' \
  -p='[{"op": "replace", "path": "/spec/http/0/route/1/weight", "value": 50}]'

sleep 300

# Full rollout (100%)
kubectl patch virtualservice myapp-virtualservice --type='json' \
  -p='[{"op": "replace", "path": "/spec/http/0/route/1/weight", "value": 100}]'
Canary deployment with progressive traffic shifting

Feature Flags (Feature Toggles)

# feature_flags.py - Feature flag implementation
import os
import redis
from functools import wraps
from flask import Flask, jsonify, request

app = Flask(__name__)
redis_client = redis.Redis(host='redis-service', decode_responses=True)

class FeatureFlags:
    """Feature flag management"""
    
    def __init__(self):
        self.default_flags = {
            'new_checkout': False,
            'dark_mode': False,
            'analytics_v2': False,
            'recommendations': True,
        }
    
    def is_enabled(self, flag_name, user_id=None):
        """Check if feature flag is enabled"""
        # Check if overridden in Redis
        flag_value = redis_client.get(f'feature_flag:{flag_name}')
        if flag_value is not None:
            return flag_value.lower() == 'true'
        
        # Check user-specific override (percentage rollout)
        if user_id:
            percentage = redis_client.get(f'feature_flag:{flag_name}:percentage')
            if percentage:
                hash_value = hash(f"{user_id}:{flag_name}") % 100
                if hash_value < int(percentage):
                    return True
        
        # Return default
        return self.default_flags.get(flag_name, False)
    
    def enable(self, flag_name, percentage=None):
        """Enable feature flag for all users or percentage"""
        if percentage:
            redis_client.set(f'feature_flag:{flag_name}:percentage', percentage)
        else:
            redis_client.set(f'feature_flag:{flag_name}', 'true')
    
    def disable(self, flag_name):
        """Disable feature flag"""
        redis_client.delete(f'feature_flag:{flag_name}')
        redis_client.delete(f'feature_flag:{flag_name}:percentage')

feature_flags = FeatureFlags()

# Decorator for feature-flagged endpoints
def feature_flag(flag_name):
    def decorator(f):
        @wraps(f)
        def decorated_function(*args, **kwargs):
            user_id = request.headers.get('X-User-ID')
            if feature_flags.is_enabled(flag_name, user_id):
                return f(*args, **kwargs)
            return jsonify({'error': 'Feature not available'}), 404
        return decorated_function
    return decorator

# API endpoints to manage flags
@app.route('/api/features', methods=['GET'])
def list_features():
    """List all feature flags and their status"""
    flags = {}
    for flag in feature_flags.default_flags:
        flags[flag] = feature_flags.is_enabled(flag)
    return jsonify(flags)

@app.route('/api/features/<flag_name>', methods=['POST'])
def enable_feature(flag_name):
    """Enable a feature flag"""
    data = request.json
    percentage = data.get('percentage')
    feature_flags.enable(flag_name, percentage)
    return jsonify({'status': 'enabled', 'flag': flag_name})

@app.route('/api/features/<flag_name>', methods=['DELETE'])
def disable_feature(flag_name):
    """Disable a feature flag"""
    feature_flags.disable(flag_name)
    return jsonify({'status': 'disabled', 'flag': flag_name})

# Feature-flagged endpoint
@app.route('/api/checkout')
@feature_flag('new_checkout')
def new_checkout():
    """New checkout flow (behind feature flag)"""
    return jsonify({'checkout_flow': 'v2', 'features': ['one-click', 'saved-cards']})

@app.route('/api/checkout/old')
def old_checkout():
    """Old checkout flow (fallback)"""
    return jsonify({'checkout_flow': 'v1', 'features': ['standard']})

# Enable feature for 10% of users
# curl -X POST /api/features/new_checkout -H 'Content-Type: application/json' -d '{"percentage": 10}'
Feature flags implementation

4. Complete Project: Multi-Strategy Deployment Pipeline

# .github/workflows/deployment-pipeline.yml
name: Multi-Strategy Deployment

on:
  push:
    branches: [main]
  workflow_dispatch:
    inputs:
      strategy:
        description: 'Deployment strategy'
        required: true
        default: 'rolling'
        type: choice
        options:
          - rolling
          - blue-green
          - canary
      canary_percentage:
        description: 'Canary traffic percentage'
        default: '10'
        type: string

env:
  CLUSTER: production
  NAMESPACE: production

jobs:
  deploy-rolling:
    if: github.event.inputs.strategy == 'rolling' || github.event_name == 'push'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Configure kubectl
        uses: azure/setup-kubectl@v3
        with:
          version: 'latest'
      
      - name: Set up kubeconfig
        run: |
          echo "${{ secrets.KUBE_CONFIG }}" | base64 -d > kubeconfig
          export KUBECONFIG=kubeconfig
      
      - name: Rolling update
        run: |
          kubectl set image deployment/myapp \
            app=${{ secrets.REGISTRY }}/myapp:${{ github.sha }} \
            -n ${{ env.NAMESPACE }}
          kubectl rollout status deployment/myapp -n ${{ env.NAMESPACE }} --timeout=300s

  deploy-blue-green:
    if: github.event.inputs.strategy == 'blue-green'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Deploy green version
        run: |
          envsubst < k8s/green-deployment.yaml | kubectl apply -f -
          kubectl wait --for=condition=ready pod -l version=green -n ${{ env.NAMESPACE }} --timeout=300s
      
      - name: Verify green deployment
        run: |
          GREEN_POD=$(kubectl get pod -l version=green -n ${{ env.NAMESPACE }} -o jsonpath='{.items[0].metadata.name}')
          kubectl exec $GREEN_POD -n ${{ env.NAMESPACE }} -- curl -f localhost:8080/health || exit 1
      
      - name: Switch traffic
        run: |
          kubectl patch service myapp-service -n ${{ env.NAMESPACE }} \
            -p '{"spec":{"selector":{"version":"green"}}}'
      
      - name: Scale down blue
        run: kubectl scale deployment myapp-blue -n ${{ env.NAMESPACE }} --replicas=0

  deploy-canary:
    if: github.event.inputs.strategy == 'canary'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Deploy canary version
        run: |
          envsubst < k8s/canary-deployment.yaml | kubectl apply -f -
          kubectl wait --for=condition=ready pod -l track=canary -n ${{ env.NAMESPACE }} --timeout=300s
      
      - name: Set canary traffic percentage
        run: |
          kubectl patch virtualservice myapp-virtualservice -n ${{ env.NAMESPACE }} --type='json' \
            -p='[{"op": "replace", "path": "/spec/http/0/route/1/weight", "value": ${{ github.event.inputs.canary_percentage }}}]'
      
      - name: Monitor canary
        run: |
          echo "Monitoring canary for 10 minutes..."
          sleep 600
          
          # Check error rate
          ERROR_RATE=$(kubectl exec -n monitoring prometheus-0 -- \
            wget -q -O- "http://localhost:9090/api/v1/query?query=sum(rate(http_requests_total{track=canary,status=~'5..'}[5m]))/sum(rate(http_requests_total{track=canary}[5m]))" | jq '.data.result[0].value[1]')
          
          if (( $(echo "$ERROR_RATE > 0.01" | bc -l) )); then
            echo "Error rate too high! Rolling back canary..."
            kubectl patch virtualservice myapp-virtualservice -n ${{ env.NAMESPACE }} --type='json' \
              -p='[{"op": "replace", "path": "/spec/http/0/route/1/weight", "value": 0}]'
            exit 1
          fi
          
          echo "Canary successful, promoting to 100%"
          kubectl patch virtualservice myapp-virtualservice -n ${{ env.NAMESPACE }} --type='json' \
            -p='[{"op": "replace", "path": "/spec/http/0/route/1/weight", "value": 100}]'

  notify:
    needs: [deploy-rolling, deploy-blue-green, deploy-canary]
    runs-on: ubuntu-latest
    if: always()
    steps:
      - name: Slack notification
        uses: slackapi/slack-github-action@v1
        with:
          payload: |
            {
              "text": "Deployment ${{ job.status }}: ${{ github.event.inputs.strategy }} to ${{ env.NAMESPACE }}"
            }
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}
Complete multi-strategy deployment pipeline

5. Strategy Comparison

┌─────────────────┬──────────────┬───────────────┬─────────────┬──────────────┐
│    Strategy     │  Downtime    │  Rollback     │  Complexity │  Risk        │
├─────────────────┼──────────────┼───────────────┼─────────────┼──────────────┤
│ Rolling Update  │ None         │ Slow          │ Low         │ Medium       │
├─────────────────┼──────────────┼───────────────┼─────────────┼──────────────┤
│ Blue-Green      │ None         │ Instant       │ Medium      │ Low          │
├─────────────────┼──────────────┼───────────────┼─────────────┼──────────────┤
│ Canary          │ None         │ Instant       │ High        │ Very Low     │
├─────────────────┼──────────────┼───────────────┼─────────────┼──────────────┤
│ Feature Flags   │ None         │ Instant       │ High        │ Lowest       │
├─────────────────┼──────────────┼───────────────┼─────────────┼──────────────┤
│ Recreate        │ Downtime     │ Slow          │ Lowest      │ High         │
└─────────────────┴──────────────┴───────────────┴─────────────┴──────────────┘

When to use each:
- Rolling Update: Default for stateless apps, simple to implement
- Blue-Green: When you need instant rollback capability
- Canary: High-traffic apps, risk-averse teams
- Feature Flags: Gradual rollout, A/B testing, complex features
- Recreate: Batch jobs, development environments only
Deployment strategy comparison

6. Common Errors & Solutions

7. Summary Checklist

8. Next Steps

Next lesson: CI/CD Lesson 7.4: Infrastructure as Code in CI

Gataya Med

DevOps Engineer & Backend Developer. Sharing insights on cloud, automation, and scalable systems.

Comments (0)

Sarah Chen February 15, 2025

This is exactly what I needed! The initContainer approach solved our migration issues completely. Thanks for the detailed guide!

Reply

Leave a Comment