backend / docker / 06_scaling_in_docker.md

What is Scaling in Docker?

3 interview angles 11 min read source

What is Scaling in Docker?

Definition

Scaling in Docker refers to the process of increasing or decreasing the number of container instances to handle varying workloads. It allows applications to adapt to demand by running more containers during high traffic and fewer containers during low traffic, ensuring optimal resource utilization and performance.

Types of Scaling

1. Horizontal Scaling (Scale Out/In)

Adding or removing container instances.

Scale Out: Add more containers Scale In: Remove containers

2. Vertical Scaling (Scale Up/Down)

Increasing or decreasing resources (CPU, memory) for existing containers.

Scale Up: Increase resources Scale Down: Decrease resources

Scaling Methods

1. Docker Compose Scaling

Basic Scaling:

# Scale a service to 3 instances
docker-compose up -d --scale web=3

# Scale multiple services
docker-compose up -d --scale web=3 --scale worker=2

docker-compose.yml:

version: '3.8'

services:
  web:
    build: .
    ports:
      - "5000-5002:5000"  # Map multiple ports
    environment:
      - ENV=production

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
    depends_on:
      - web

Load Balancing with Nginx:

# nginx.conf
upstream backend {
    server web:5000;
    server web:5000;
    server web:5000;
}

server {
    listen 80;
    
    location / {
        proxy_pass http://backend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

Limitations:

  • All instances share the same port range
  • Manual load balancer configuration needed
  • No automatic health checks
  • Limited to single host

2. Docker Swarm Scaling

Initialize Swarm:

# Initialize swarm
docker swarm init

# Join worker nodes
docker swarm join --token <token> <manager-ip>:2377

Deploy Service:

# Create service
docker service create \
  --name web \
  --replicas 3 \
  --publish 5000:5000 \
  my-web-app:latest

# Scale service
docker service scale web=5

# Update replicas
docker service update --replicas 10 web

docker-stack.yml:

version: '3.8'

services:
  web:
    image: my-web-app:latest
    deploy:
      replicas: 3
      update_config:
        parallelism: 1
        delay: 10s
      restart_policy:
        condition: on-failure
    ports:
      - "5000:5000"
    networks:
      - webnet

  nginx:
    image: nginx:alpine
    deploy:
      replicas: 1
    ports:
      - "80:80"
    networks:
      - webnet
    depends_on:
      - web

networks:
  webnet:

Deploy Stack:

# Deploy stack
docker stack deploy -c docker-stack.yml myapp

# Scale service in stack
docker service scale myapp_web=5

# Remove stack
docker stack rm myapp

Swarm Features:

  • Automatic load balancing
  • Service discovery
  • Rolling updates
  • Health checks
  • Multi-host support

3. Kubernetes Scaling

Deployment with Replicas:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-app
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web-app
  template:
    metadata:
      labels:
        app: web-app
    spec:
      containers:
      - name: web
        image: my-web-app:latest
        ports:
        - containerPort: 5000
        resources:
          requests:
            memory: "128Mi"
            cpu: "100m"
          limits:
            memory: "256Mi"
            cpu: "200m"

Manual Scaling:

# Scale deployment
kubectl scale deployment web-app --replicas=5

# Scale using file
kubectl scale --replicas=5 -f deployment.yaml

Horizontal Pod Autoscaler (HPA):

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: web-app-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: web-app
  minReplicas: 2
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70
  - type: Resource
    resource:
      name: memory
      target:
        type: Utilization
        averageUtilization: 80

Apply HPA:

kubectl apply -f hpa.yaml

# Check HPA status
kubectl get hpa

# Describe HPA
kubectl describe hpa web-app-hpa

4. Auto-Scaling

Docker Swarm Auto-Scaling (using external tools):

# Using docker-autoscaler
docker run -d \
  -v /var/run/docker.sock:/var/run/docker.sock \
  docker-autoscaler \
  --service web \
  --min-replicas 2 \
  --max-replicas 10 \
  --cpu-threshold 70

Kubernetes Auto-Scaling:

# HPA based on CPU/Memory
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: web-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: web
  minReplicas: 2
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70

Custom Metrics Auto-Scaling:

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: web-custom-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: web
  minReplicas: 2
  maxReplicas: 20
  metrics:
  - type: Pods
    pods:
      metric:
        name: requests_per_second
      target:
        type: AverageValue
        averageValue: "100"

Scaling Strategies

1. Manual Scaling

Docker Compose:

# Scale up
docker-compose up -d --scale web=5

# Scale down
docker-compose up -d --scale web=2

# Check status
docker-compose ps

Docker Swarm:

# Scale service
docker service scale web=5

# Check service
docker service ps web

2. Scheduled Scaling

Cron-based Scaling:

# Scale up during business hours
0 9 * * 1-5 docker service scale web=10

# Scale down during off-hours
0 18 * * 1-5 docker service scale web=3

Kubernetes CronJob:

apiVersion: batch/v1
kind: CronJob
metadata:
  name: scale-up
spec:
  schedule: "0 9 * * 1-5"  # 9 AM weekdays
  jobTemplate:
    spec:
      template:
        spec:
          containers:
          - name: kubectl
            image: bitnami/kubectl
            command:
            - kubectl
            - scale
            - deployment
            - web
            - --replicas=10
          restartPolicy: OnFailure

3. Reactive Scaling (Auto-Scaling)

Based on Metrics:

  • CPU utilization
  • Memory usage
  • Request rate
  • Queue length
  • Custom metrics

Example:

# Kubernetes HPA
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: web-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: web
  minReplicas: 2
  maxReplicas: 20
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70
  behavior:
    scaleDown:
      stabilizationWindowSeconds: 300
      policies:
      - type: Percent
        value: 50
        periodSeconds: 60
    scaleUp:
      stabilizationWindowSeconds: 0
      policies:
      - type: Percent
        value: 100
        periodSeconds: 15
      - type: Pods
        value: 4
        periodSeconds: 15
      selectPolicy: Max

Resource Management

Resource Limits

Docker Compose:

services:
  web:
    image: my-app:latest
    deploy:
      resources:
        limits:
          cpus: '0.5'
          memory: 512M
        reservations:
          cpus: '0.25'
          memory: 256M

Docker Swarm:

docker service create \
  --name web \
  --limit-cpu 0.5 \
  --limit-memory 512m \
  --reserve-cpu 0.25 \
  --reserve-memory 256m \
  my-app:latest

Kubernetes:

containers:
- name: web
  image: my-app:latest
  resources:
    requests:
      memory: "256Mi"
      cpu: "250m"
    limits:
      memory: "512Mi"
      cpu: "500m"

Resource Monitoring

Docker Stats:

# Monitor container resources
docker stats

# Monitor specific container
docker stats web-1

# Monitor service
docker service ps web --no-trunc

Kubernetes Metrics:

# Install metrics server
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml

# View node metrics
kubectl top nodes

# View pod metrics
kubectl top pods

Load Balancing

Docker Swarm Load Balancing

Automatic Load Balancing:

# Swarm automatically load balances
docker service create \
  --name web \
  --replicas 5 \
  --publish 5000:5000 \
  my-app:latest

# All 5 replicas share port 5000
# Swarm routes traffic automatically

Load Balancing Modes:

  • VIP (Virtual IP): Default, DNS-based
  • DNS Round Robin: DNS-based load balancing
  • Ingress Mode: Port published on all nodes

Nginx Load Balancing

upstream backend {
    least_conn;  # Least connections
    server web-1:5000;
    server web-2:5000;
    server web-3:5000;
}

server {
    listen 80;
    
    location / {
        proxy_pass http://backend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

Health Checks

Docker Compose Health Checks

services:
  web:
    image: my-app:latest
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:5000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

Docker Swarm Health Checks

docker service create \
  --name web \
  --health-cmd "curl -f http://localhost:5000/health || exit 1" \
  --health-interval 30s \
  --health-timeout 10s \
  --health-retries 3 \
  my-app:latest

Kubernetes Health Checks

containers:
- name: web
  image: my-app:latest
  livenessProbe:
    httpGet:
      path: /health
      port: 5000
    initialDelaySeconds: 30
    periodSeconds: 10
    timeoutSeconds: 5
    failureThreshold: 3
  readinessProbe:
    httpGet:
      path: /ready
      port: 5000
    initialDelaySeconds: 5
    periodSeconds: 5

Common Interview Questions and Answers

Q1: What is scaling in Docker and why is it important?

Scaling in Docker is the process of adjusting the number of container instances to handle workload changes. It’s important because:

  1. Performance: Handle increased traffic by adding containers
  2. Resource Efficiency: Reduce containers during low demand
  3. High Availability: Multiple instances provide redundancy
  4. Cost Optimization: Pay only for resources needed
  5. User Experience: Maintain performance under varying loads

Types:

  • Horizontal: Add/remove containers (scale out/in)
  • Vertical: Increase/decrease resources (scale up/down)

Q2: What’s the difference between Docker Compose scaling and Docker Swarm scaling?

Aspect Docker Compose Docker Swarm
Scope Single host Multiple hosts
Load Balancing Manual (Nginx) Automatic
Service Discovery Service names DNS-based
Health Checks Basic Advanced
Rolling Updates Manual Automatic
Use Case Development, single host Production, multi-host

Docker Compose: Good for development, single-host deployments Docker Swarm: Good for production, multi-host clusters

Q3: How do you implement auto-scaling in Docker?

Auto-scaling methods:

  1. Docker Swarm with External Tools:
# Using docker-autoscaler
docker run -d \
  -v /var/run/docker.sock:/var/run/docker.sock \
  docker-autoscaler \
  --service web \
  --min-replicas 2 \
  --max-replicas 10 \
  --cpu-threshold 70
  1. Kubernetes HPA:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: web-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: web
  minReplicas: 2
  maxReplicas: 10
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70
  1. Custom Scripts:
#!/bin/bash
# Monitor and scale
while true; do
    cpu_usage=$(docker stats --no-stream --format "{{.CPUPerc}}" web | sed 's/%//')
    if (( $(echo "$cpu_usage > 70" | bc -l) )); then
        docker service scale web=$(($(docker service ps web | wc -l) + 1))
    fi
    sleep 60
done

Q4: How does load balancing work in Docker Swarm?

Docker Swarm provides automatic load balancing:

  1. VIP (Virtual IP): Each service gets a virtual IP
  2. DNS Round Robin: DNS resolves to multiple container IPs
  3. Ingress Network: Published ports accessible on all nodes
  4. Automatic Routing: Swarm routes requests to healthy containers
# Service with 3 replicas
docker service create \
  --name web \
  --replicas 3 \
  --publish 5000:5000 \
  my-app:latest

# All requests to port 5000 are load balanced
# Swarm automatically distributes traffic

Load Balancing Modes:

  • VIP: Default, service-level load balancing
  • DNS Round Robin: Container-level load balancing

Q5: What are the best practices for scaling Docker containers?

Best practices:

  1. Set Resource Limits:
deploy:
  resources:
    limits:
      cpus: '0.5'
      memory: 512M
  1. Implement Health Checks:
healthcheck:
  test: ["CMD", "curl", "-f", "http://localhost:5000/health"]
  interval: 30s
  timeout: 10s
  retries: 3
  1. Use Stateless Containers: Containers should be stateless for easy scaling

  2. Monitor Metrics: Monitor CPU, memory, request rate

  3. Gradual Scaling: Scale gradually, not all at once

  4. Set Min/Max Replicas: Define scaling boundaries

  5. Use Readiness Probes: Ensure containers are ready before receiving traffic

  6. Implement Circuit Breakers: Handle failures gracefully

Q6: How do you scale stateful applications in Docker?

Stateful applications require special handling:

  1. External State Storage:
services:
  web:
    image: my-app:latest
    volumes:
      - db-data:/data  # Shared volume
    environment:
      - DB_HOST=postgres
      - REDIS_HOST=redis

  postgres:
    image: postgres:13
    volumes:
      - postgres-data:/var/lib/postgresql/data

volumes:
  db-data:
  postgres-data:
  1. Session Affinity (Sticky Sessions):
upstream backend {
    ip_hash;  # Sticky sessions
    server web-1:5000;
    server web-2:5000;
    server web-3:5000;
}
  1. Database Replication:
services:
  postgres-master:
    image: postgres:13
    environment:
      - POSTGRES_REPLICATION_MODE=master

  postgres-replica:
    image: postgres:13
    environment:
      - POSTGRES_REPLICATION_MODE=slave

Q7: What is the difference between horizontal and vertical scaling?

Horizontal Scaling (Scale Out/In):

  • Add/remove containers
  • Better for distributed systems
  • Improves availability
  • Can handle more concurrent requests
  • Example: 1 container → 5 containers

Vertical Scaling (Scale Up/Down):

  • Increase/decrease container resources
  • Simpler, no code changes needed
  • Limited by host resources
  • Single point of failure
  • Example: 512MB RAM → 2GB RAM

When to use:

  • Horizontal: High traffic, need availability, distributed systems
  • Vertical: Low traffic, simple apps, resource-intensive tasks

Q8: How do you monitor scaling performance?

Monitoring approaches:

  1. Docker Stats:
docker stats --no-stream

# Specific service
docker service ps web --no-trunc
  1. Prometheus + Grafana:
# docker-compose.yml
services:
  prometheus:
    image: prom/prometheus
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml

  grafana:
    image: grafana/grafana
    ports:
      - "3000:3000"
  1. Kubernetes Metrics:
# Install metrics server
kubectl apply -f metrics-server.yaml

# View metrics
kubectl top pods
kubectl top nodes
  1. Application Metrics:
  • Request rate
  • Response time
  • Error rate
  • Queue length

Q9: How do you handle scaling during deployments?

Deployment strategies:

  1. Rolling Updates (Docker Swarm):
docker service update \
  --update-parallelism 1 \
  --update-delay 10s \
  --image my-app:v2 \
  web
  1. Blue-Green Deployment:
services:
  web-blue:
    image: my-app:blue
    ports:
      - "5001:5000"
  
  web-green:
    image: my-app:green
    ports:
      - "5002:5000"
  
  nginx:
    image: nginx:alpine
    # Switch between blue/green
  1. Canary Deployment:
# Deploy 10% to new version
docker service update \
  --replicas 1 \
  --image my-app:v2 \
  web-canary

# Gradually increase
docker service scale web-canary=5

Q10: What are the challenges in Docker scaling?

Common challenges:

  1. State Management: Stateless containers are easier to scale
  2. Session Affinity: Need sticky sessions for stateful apps
  3. Database Connections: Connection pooling needed
  4. Service Discovery: Containers need to find each other
  5. Load Balancing: Proper load balancing configuration
  6. Health Checks: Ensure only healthy containers receive traffic
  7. Resource Limits: Prevent resource exhaustion
  8. Network Overhead: More containers = more network traffic

Solutions:

  • Use external state storage (Redis, database)
  • Implement proper health checks
  • Use service discovery (DNS, Consul)
  • Configure load balancers properly
  • Set resource limits
  • Monitor network performance

Best Practices

  1. Start Small: Begin with few replicas, scale as needed
  2. Set Limits: Define min/max replicas
  3. Monitor Metrics: Track CPU, memory, request rate
  4. Health Checks: Implement proper health checks
  5. Stateless Design: Design stateless applications
  6. Gradual Scaling: Scale gradually, not all at once
  7. Resource Limits: Set appropriate resource limits
  8. Load Testing: Test scaling behavior
  9. Documentation: Document scaling procedures
  10. Automation: Use auto-scaling when possible

Summary

Docker scaling enables:

  • Horizontal Scaling: Add/remove containers
  • Vertical Scaling: Adjust resources
  • Auto-Scaling: Automatic scaling based on metrics
  • Load Balancing: Distribute traffic across containers
  • High Availability: Multiple instances for redundancy

Scaling methods:

  • Docker Compose: Simple, single-host scaling
  • Docker Swarm: Multi-host, automatic load balancing
  • Kubernetes: Advanced scaling with HPA

Key considerations:

  • Stateless applications scale easier
  • Health checks ensure only healthy containers receive traffic
  • Resource limits prevent exhaustion
  • Monitoring is essential for effective scaling
  • Auto-scaling adapts to demand automatically

Interview angle

  • “How do you scale a containerised service?” - horizontally, more instances behind a load balancer, which requires the container to be stateless. Session state, uploads and caches must move to shared services first; that refactor is usually the real work.
  • “What limits should every container have?” - CPU and memory. Without a memory limit one container can exhaust the host and take unrelated workloads with it; with one, the kernel OOM-kills just that container.
  • “How do you handle graceful shutdown?” - the runtime sends SIGTERM then SIGKILL after a grace period. The process must trap SIGTERM, stop accepting new work, finish in-flight requests and exit. Ignoring it means dropped requests on every deploy.