Kubernetes Deployments Made Easy

Learn step-by-step with real examples and visual explanations

Medium 25 min read Interactive

Understanding Deployments

Why Deployments Matter

The Problem: Managing individual pods is complex and error-prone at scale.

The Solution: Deployments provide declarative updates for Pods and ReplicaSets.

Key Benefits: Automated rollouts, rollbacks, scaling, and self-healing.

Real-World Analogy

Think of a Deployment as a factory production line manager:

  • Deployment = Production manager with the blueprint
  • ReplicaSet = Shift supervisor ensuring worker count
  • Pods = Individual workers on the line
  • Desired State = Production targets to meet
  • Rolling Update = Gradual shift changes without stopping production

Deployment Architecture

Deployment

The top-level controller. Manages ReplicaSets and provides declarative updates, rollback, and scaling.

ReplicaSet

Created by the Deployment. Ensures the desired number of pod replicas are running at all times.

Pods

The actual running instances of your application. Managed by the ReplicaSet, self-healing on failure.

Basic Deployment YAML

basic-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 3  # Number of pod replicas
  selector:
    matchLabels:
      app: nginx
  template:  # Pod template
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.21
        ports:
        - containerPort: 80

Working with Deployments

Creating and Managing Deployments

deployment-commands.sh
# Create deployment from YAML
kubectl apply -f deployment.yaml

# Create deployment imperatively
kubectl create deployment nginx --image=nginx:1.21 --replicas=3

# Get deployments
kubectl get deployments
kubectl get deploy nginx-deployment -o wide

# Describe deployment
kubectl describe deployment nginx-deployment

# Get deployment in YAML
kubectl get deployment nginx-deployment -o yaml

# Watch deployment status
kubectl rollout status deployment/nginx-deployment

# Get ReplicaSets created by deployment
kubectl get rs -l app=nginx

# Get pods created by deployment
kubectl get pods -l app=nginx

Advanced Deployment Configuration

production-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: app-deployment
  namespace: production
  labels:
    app: myapp
    version: v1.0.0
spec:
  replicas: 5
  revisionHistoryLimit: 10  # Keep 10 old ReplicaSets
  selector:
    matchLabels:
      app: myapp
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 2        # Max pods above desired
      maxUnavailable: 1  # Max pods unavailable
  template:
    metadata:
      labels:
        app: myapp
        version: v1.0.0
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/port: "9090"
    spec:
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 100
            podAffinityTerm:
              labelSelector:
                matchExpressions:
                - key: app
                  operator: In
                  values:
                  - myapp
              topologyKey: kubernetes.io/hostname
      containers:
      - name: app
        image: myapp:v1.0.0
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 8080
          name: http
        - containerPort: 9090
          name: metrics
        env:
        - name: ENV
          value: production
        resources:
          requests:
            memory: "128Mi"
            cpu: "100m"
          limits:
            memory: "256Mi"
            cpu: "200m"
        livenessProbe:
          httpGet:
            path: /health
            port: 8080
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /ready
            port: 8080
          initialDelaySeconds: 5
          periodSeconds: 5

Deployment Best Practices

  • Always use labels: For organization and selection
  • Set resource limits: Prevent resource exhaustion
  • Configure health checks: Ensure only healthy pods receive traffic
  • Use antiAffinity: Spread pods across nodes for HA
  • Set revisionHistoryLimit: Control how many old ReplicaSets to keep

Scaling Applications

Manual Scaling

scaling-commands.sh
# Scale deployment to 10 replicas
kubectl scale deployment nginx-deployment --replicas=10

# Scale multiple deployments
kubectl scale deployment app1 app2 --replicas=5

# Scale based on current replicas
kubectl scale deployment nginx-deployment --current-replicas=3 --replicas=5

# Edit deployment to change replicas
kubectl edit deployment nginx-deployment

# Patch deployment
kubectl patch deployment nginx-deployment -p '{"spec":{"replicas":8}}'

Horizontal Pod Autoscaler (HPA)

hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: app-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: app-deployment
  minReplicas: 3
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70
  - type: Resource
    resource:
      name: memory
      target:
        type: Utilization
        averageUtilization: 80
  behavior:
    scaleDown:
      stabilizationWindowSeconds: 300
      policies:
      - type: Percent
        value: 10
        periodSeconds: 60
    scaleUp:
      stabilizationWindowSeconds: 0
      policies:
      - type: Percent
        value: 100
        periodSeconds: 15
hpa-commands.sh
# Create HPA imperatively
kubectl autoscale deployment app-deployment --cpu-percent=70 --min=3 --max=10

# Get HPA status
kubectl get hpa
kubectl describe hpa app-hpa

# Watch HPA in action
kubectl get hpa -w

# Generate load to test HPA
kubectl run -it load-generator --rm --image=busybox --restart=Never -- /bin/sh
# Inside the pod:
while true; do wget -q -O- http://app-service; done

Rolling Updates & Rollbacks

Rolling Update Process

During a rolling update, Kubernetes gradually replaces old pods with new ones, ensuring zero downtime:

Step 1: Initial State

All 3 pods are running v1 of your application. The Deployment is stable.

Step 2: Creating New

A new v2 pod is created while 2 v1 pods still serve traffic. Readiness probes gate traffic.

Step 3: Replacing

Once v2 is ready, an old v1 pod terminates. Now 1 v1 + 2 v2 pods are running.

Step 4: Complete

All pods are now running v2. The old ReplicaSet is scaled to 0 but kept for rollback.

Performing Updates

update-commands.sh
# Update image
kubectl set image deployment/nginx-deployment nginx=nginx:1.22

# Update with record (for rollback)
kubectl set image deployment/nginx-deployment nginx=nginx:1.22 --record

# Update environment variable
kubectl set env deployment/nginx-deployment ENV=production

# Watch rollout status
kubectl rollout status deployment/nginx-deployment

# Pause rollout
kubectl rollout pause deployment/nginx-deployment

# Resume rollout
kubectl rollout resume deployment/nginx-deployment

# Check rollout history
kubectl rollout history deployment/nginx-deployment

# Rollback to previous version
kubectl rollout undo deployment/nginx-deployment

# Rollback to specific revision
kubectl rollout undo deployment/nginx-deployment --to-revision=2

# View specific revision
kubectl rollout history deployment/nginx-deployment --revision=3

Update Strategies Comparison

Strategy Description Use Case Downtime
RollingUpdate Gradually replaces old pods with new ones Most production deployments Zero downtime
Recreate Terminates all old pods before creating new ones Dev environments, stateful apps Brief downtime
Blue/Green Switch between two identical environments Critical apps, instant rollback needed Zero downtime
Canary Gradual rollout to subset of users Risk mitigation, A/B testing Zero downtime

Common Update Pitfalls

Problem: Update stuck or failing

  • Check image pull errors: kubectl describe pods
  • Verify health checks are passing
  • Ensure sufficient resources available
  • Check for PodDisruptionBudget conflicts

Deployment Strategies

Rolling Update

Default strategy, gradually replaces pods. Zero downtime, configurable pace, automatic rollback on failure.

Blue/Green

Two complete environments running side-by-side. Instant switch, easy rollback, but resource intensive.

Canary

Gradual rollout to a subset of users first. Great for risk mitigation and real user testing. More complex setup.

Blue/Green Deployment Example

blue-green-deployment.yaml
# Blue Deployment (Current)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: app-blue
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp
      version: blue
  template:
    metadata:
      labels:
        app: myapp
        version: blue
    spec:
      containers:
      - name: app
        image: myapp:v1.0
---
# Green Deployment (New)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: app-green
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp
      version: green
  template:
    metadata:
      labels:
        app: myapp
        version: green
    spec:
      containers:
      - name: app
        image: myapp:v2.0
---
# Service pointing to active deployment
apiVersion: v1
kind: Service
metadata:
  name: app-service
spec:
  selector:
    app: myapp
    version: blue  # Switch to 'green' to deploy
  ports:
  - port: 80
    targetPort: 8080

Canary Deployment Example

canary-steps.sh
# Step 1: Deploy stable version
kubectl apply -f stable-deployment.yaml

# Step 2: Deploy canary version (small replica count)
kubectl apply -f canary-deployment.yaml

# Step 3: Split traffic using Service
# Both deployments have same labels for Service selector

# Step 4: Monitor metrics
kubectl top pods -l app=myapp

# Step 5: Gradually increase canary replicas
kubectl scale deployment canary-deployment --replicas=2

# Step 6: If successful, replace stable with canary
kubectl set image deployment/stable-deployment app=myapp:v2.0

# Step 7: Remove canary deployment
kubectl delete deployment canary-deployment

Practice Problems

Easy Create a Production Deployment

Create a production-ready deployment with 5 replicas, rolling update strategy, resource limits, health checks, and pod anti-affinity.

Use spec.strategy.type: RollingUpdate with maxSurge and maxUnavailable. Add livenessProbe and readinessProbe under the container spec. Use podAntiAffinity to spread pods across nodes.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: production-app
spec:
  replicas: 5
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 1
  selector:
    matchLabels:
      app: production-app
  template:
    metadata:
      labels:
        app: production-app
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - production-app
            topologyKey: kubernetes.io/hostname
      containers:
      - name: app
        image: nginx:1.21
        resources:
          requests:
            memory: "128Mi"
            cpu: "100m"
          limits:
            memory: "256Mi"
            cpu: "500m"
        livenessProbe:
          httpGet:
            path: /
            port: 80
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /
            port: 80
          initialDelaySeconds: 5
          periodSeconds: 5

Medium Implement Blue/Green Deployment

Set up a blue/green deployment: create blue (v1) and green (v2) deployments, a service pointing to blue, then switch to green and verify.

Use different label values (version: blue vs version: green) to distinguish deployments. The Service selector determines which set receives traffic. Use kubectl patch to switch.

# Create blue deployment
kubectl create deployment app-blue --image=nginx:1.20 --replicas=3
kubectl label deployment app-blue version=blue

# Create green deployment
kubectl create deployment app-green --image=nginx:1.21 --replicas=3
kubectl label deployment app-green version=green

# Create service
kubectl expose deployment app-blue --port=80 --name=app-service

# Switch to green
kubectl patch service app-service -p '{"spec":{"selector":{"version":"green"}}}'

# Verify
kubectl get endpoints app-service

# Rollback to blue if needed
kubectl patch service app-service -p '{"spec":{"selector":{"version":"blue"}}}'

Medium Configure Autoscaling

Set up HPA with custom metrics: create a deployment with resource requests, configure CPU/memory-based autoscaling, generate load, and monitor scaling behavior.

You must set resource requests on the deployment before HPA can measure utilization. Use kubectl autoscale or apply an HPA YAML. Generate load with a busybox pod.

# Create deployment
kubectl create deployment autoscale-app --image=nginx
kubectl set resources deployment autoscale-app \
  --requests=cpu=100m,memory=128Mi

# Create HPA
kubectl autoscale deployment autoscale-app \
  --cpu-percent=50 \
  --min=2 \
  --max=10

# Generate load
kubectl run -it load-generator --rm --image=busybox \
  --restart=Never -- \
  sh -c "while true; do wget -q -O- http://autoscale-app; done"

# Monitor HPA
watch kubectl get hpa

# Check metrics
kubectl top pods -l app=autoscale-app

Hard Zero-Downtime Update Challenge

Perform a zero-downtime update that does not affect active connections. Implement a PodDisruptionBudget, use preStop hooks for graceful shutdown, and monitor the rollout with rollback readiness.

A PodDisruptionBudget ensures a minimum number of pods remain available. The preStop hook gives in-flight requests time to complete before the container exits. Combine with readiness probes for a fully graceful rollout.

# PodDisruptionBudget
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: app-pdb
spec:
  minAvailable: 2
  selector:
    matchLabels:
      app: myapp
---
# Deployment with preStop hook
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  replicas: 3
  strategy:
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      terminationGracePeriodSeconds: 60
      containers:
      - name: app
        image: myapp:v2.0
        lifecycle:
          preStop:
            exec:
              command: ["sh", "-c", "sleep 15"]
        readinessProbe:
          httpGet:
            path: /ready
            port: 8080
          periodSeconds: 5

Pro Tip

Start with basic deployments and work your way up to advanced strategies. Most production issues come from missing health checks and insufficient resource limits - nail those first before worrying about canary deployments!