Health Checks en Zero-Downtime Deployments met Docker Compose

Implementeer robuuste health checks en rolling updates voor productie-omgevingen zonder downtime. Praktische voorbeelden met NGINX, PostgreSQL en applicatie-containers.

Jean-Pierre Broeders

Freelance DevOps Engineer

1 april 20267 min. leestijd
Health Checks en Zero-Downtime Deployments met Docker Compose

Health Checks en Zero-Downtime Deployments met Docker Compose

Een productie-omgeving draait 24/7. Containers kunnen crashen, databases kunnen vast lopen, en updates moeten gebeuren zonder dat gebruikers iets merken. Health checks en zero-downtime deployments vormen de basis voor betrouwbare systemen.

Waarom Health Checks Essentieel Zijn

Een container kan draaien zonder dat de applicatie binnen die container daadwerkelijk werkt. De database kan onbereikbaar zijn. De API kan timeout errors geven. Een simpele docker ps toont alleen of het proces actief is — niet of het gezond is.

Health checks detecteren dit soort problemen automatisch. Bij een falende health check kan de container opnieuw opstarten, of de load balancer stuurt traffic naar gezonde instances.

Health Check Configuratie

Een basale health check voor een web applicatie ziet er zo uit:

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

Wat gebeurt er?

  • Elke 30 seconden wordt de endpoint gecontroleerd
  • De check mag maximaal 10 seconden duren
  • Na 3 falende pogingen geldt de container als "unhealthy"
  • De eerste 40 seconden worden gefaalde checks genegeerd (opstarttijd)

Voor databases werkt een andere aanpak beter:

services:
  postgres:
    image: postgres:16
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 20s

pg_isready controleert of PostgreSQL daadwerkelijk verbindingen accepteert. Dit is betrouwbaarder dan alleen checken of het proces draait.

Dependencies Tussen Services

Applicaties starten vaak te snel, voordat de database klaar is. Dit veroorzaakt crashes bij het opstarten. De depends_on optie combineert goed met health checks:

services:
  web:
    image: myapp:latest
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/ready"]
      interval: 15s
      timeout: 5s
      retries: 3

  postgres:
    image: postgres:16
    healthcheck:
      test: ["CMD-SHELL", "pg_isready"]
      interval: 10s

  redis:
    image: redis:7-alpine
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s

De web applicatie start pas als PostgreSQL en Redis beide gezond zijn. Dit voorkomt race conditions en onnodige crashes.

Zero-Downtime Deployments

Een update zonder downtime vereist een rolling update strategie. Docker Compose ondersteunt dit niet standaard, maar er zijn twee praktische oplossingen.

Optie 1: Blue-Green met Proxy

Draai twee identieke stacks achter een load balancer. Update de ene terwijl de andere traffic afhandelt:

services:
  web-blue:
    image: myapp:v1
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 10s

  web-green:
    image: myapp:v2
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 10s

  nginx:
    image: nginx:alpine
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    ports:
      - "80:80"
    depends_on:
      web-blue:
        condition: service_healthy
      web-green:
        condition: service_healthy

NGINX routeert traffic naar de gezonde containers. Een simpele configuratie:

upstream backend {
    server web-blue:8080 max_fails=3 fail_timeout=30s;
    server web-green:8080 max_fails=3 fail_timeout=30s;
}

server {
    listen 80;
    
    location / {
        proxy_pass http://backend;
        proxy_next_upstream error timeout http_500 http_502 http_503;
    }
    
    location /health {
        access_log off;
        return 200 "OK";
    }
}

Bij een deployment wordt eerst web-green gestopt en vervangen door de nieuwe versie. NGINX routeert automatisch alles naar web-blue. Zodra web-green gezond is, wordt web-blue vervangen.

Optie 2: Rolling Update met Replicas

Voor grotere deployments werkt Docker Swarm beter. Een simpele migratie:

version: "3.8"

services:
  web:
    image: myapp:latest
    deploy:
      replicas: 4
      update_config:
        parallelism: 1
        delay: 10s
        order: start-first
      rollback_config:
        parallelism: 1
        delay: 10s
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 10s

De configuratie update één replica per keer met 10 seconden vertraging. De start-first optie start nieuwe containers voordat oude worden gestopt. Bij problemen rolt Swarm automatisch terug.

Deploy met: docker stack deploy -c docker-compose.yml myapp

Monitoring van Health Checks

Health checks zijn waardeloos zonder monitoring. Een simpele oplossing met een health check aggregator:

services:
  healthcheck-monitor:
    image: alpine:latest
    command: |
      sh -c '
      while true; do
        echo "=== Health Check Status ==="
        docker ps --format "table {{.Names}}\t{{.Status}}"
        sleep 60
      done
      '
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro

Voor productie is dit te basic. Betere oplossingen integreren met Prometheus of Grafana. Een voorbeeld met metrics export:

services:
  cadvisor:
    image: gcr.io/cadvisor/cadvisor:latest
    volumes:
      - /:/rootfs:ro
      - /var/run:/var/run:ro
      - /sys:/sys:ro
      - /var/lib/docker/:/var/lib/docker:ro
    ports:
      - "8081:8080"

cAdvisor exporteert container metrics inclusief health status naar Prometheus. Dit geeft realtime inzicht in de gezondheid van alle services.

Praktische Tips

Test health checks lokaal. Een falende health check in productie betekent downtime. Controleer of de endpoint snel genoeg reageert en betrouwbaar is:

docker compose up -d
docker inspect --format='{{.State.Health.Status}}' container_name

Maak onderscheid tussen liveness en readiness. Een liveness check detecteert crashes. Een readiness check bepaalt of de container traffic aan kan. Beide zijn belangrijk:

Check TypeDoelActie bij falen
LivenessIs de app gecrasht?Container herstarten
ReadinessKan de app traffic aan?Geen nieuwe requests sturen

Docker Compose ondersteunt alleen liveness checks. Voor readiness checks is een load balancer nodig die beide endpoints controleert.

Vermijd te agressieve timeouts. Een health check die elke 5 seconden draait kan de database overbelasten. Start met langzame intervallen en verscherp alleen als nodig.

Log gefaalde health checks. Dit helpt bij debugging:

healthcheck:
  test: ["CMD-SHELL", "curl -f http://localhost:8080/health || exit 1"]

Conclusie

Health checks en zero-downtime deployments zijn geen luxe. Ze vormen de basis voor betrouwbare productie-omgevingen. Start met simpele health checks op kritieke services. Voeg dependencies toe om race conditions te voorkomen. Implementeer een deployment strategie die downtime voorkomt.

Zonder deze fundamenten blijft een productie-omgeving kwetsbaar voor onverwachte crashes en downtime tijdens updates. Met de juiste configuratie draaien systemen stabiel en kunnen updates gebeuren zonder dat gebruikers het merken.

Wil je op de hoogte blijven?

Schrijf je in voor mijn nieuwsbrief of neem contact op voor freelance projecten.

Neem Contact Op