Bir konteynerin “ayakta” olması ile “sağlıklı” olması arasında ciddi bir fark var. Docker, varsayılan olarak bir konteyner prosesinin çalışıp çalışmadığına bakar; ama o prosesin içindeki uygulamanın gerçekten işlevsel olup olmadığını umursamaz. Web sunucunuzun portu dinliyor ama 500 döndürüyor olması, veritabanı bağlantısının kopmuş olması ya da uygulamanın deadlock’a girmiş olması Docker’ın gözünden kaçar. İşte bu yüzden health check mekanizması kritik öneme sahip.
Bu yazıda Docker Compose’da health check tanımlamayı, bağımlılık yönetimiyle entegrasyonunu ve production ortamlarında karşılaşılan gerçek senaryoları ele alacağız.
Health Check Nedir ve Neden Lazım?
Docker’ın health check sistemi, belirli aralıklarla bir komut çalıştırır ve bu komutun çıkış koduna göre konteynerin durumunu belirler. Çıkış kodu 0 ise konteyner healthy, 1 ise unhealthy olarak işaretlenir.
Üç durum var:
- starting: Konteyner yeni başladı, henüz ilk kontrol yapılmadı
- healthy: Son kontrol başarıyla geçildi
- unhealthy: Ardışık kontroller başarısız oldu
Bu mekanizma olmadan şu problemlerle karşılaşırsınız:
- Uygulama başlatılıyor ama bağımlı servis henüz hazır değil, hata alınıyor
- Servis çökmüş ama Docker yeniden başlatmıyor çünkü proses hâlâ “çalışıyor”
- Load balancer trafiği hazır olmayan konteynere yönlendiriyor
depends_onbeklediği gibi çalışmıyor çünkü servisin “başlamış” olması yetmiyor
Temel Health Check Sözdizimi
Docker Compose’da health check tanımlamak için healthcheck bloğunu servis tanımına ekliyorsunuz:
version: "3.8"
services:
web:
image: nginx:alpine
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
Parametreleri açıklayalım:
- test: Çalıştırılacak komut.
CMDveyaCMD-SHELLformatında yazılır - interval: İki kontrol arasındaki süre (varsayılan: 30s)
- timeout: Komutun tamamlanması için maksimum süre (varsayılan: 30s)
- retries: Kaç ardışık başarısızlık sonrası unhealthy sayılacağı (varsayılan: 3)
- start_period: İlk kontrollerin başlamasından önce bekleme süresi (varsayılan: 0s)
test komutunda CMD ile CMD-SHELL farkına dikkat edin:
# CMD: Kabuk yorumlaması olmadan doğrudan çalışır
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
# CMD-SHELL: /bin/sh -c ile çalışır, pipe ve && kullanabilirsiniz
test: ["CMD-SHELL", "curl -f http://localhost/health && echo 'OK'"]
# Kısa yazım (CMD-SHELL olarak işlenir)
test: "curl -f http://localhost/health"
Web Uygulaması için Health Check
Bir Node.js API’ı düşünelim. Uygulama başlangıçta bağımlılıkları yüklüyor, veritabanı bağlantısı kuruyor ve ancak ondan sonra istekleri kabul etmeye hazır hale geliyor. start_period burada hayat kurtarıcı:
version: "3.8"
services:
api:
build: ./api
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- DB_HOST=postgres
healthcheck:
test: ["CMD", "node", "-e", "require('http').get('http://localhost:3000/health', (res) => { process.exit(res.statusCode === 200 ? 0 : 1) })"]
interval: 15s
timeout: 5s
retries: 5
start_period: 60s
depends_on:
postgres:
condition: service_healthy
Burada start_period: 60s ile uygulamaya 60 saniye hazırlanma süresi tanındı. Bu süre içindeki başarısızlıklar retry sayacını artırmaz. Node.js uygulamaları bazen yüzlerce bağımlılık yüklüyor, bu süre gerçekçi bir değer.
PostgreSQL Health Check
Veritabanı health check’i özellikle önemli. PostgreSQL’in çalışıyor olması, bağlantı kabul etmeye hazır olduğu anlamına gelmiyor:
version: "3.8"
services:
postgres:
image: postgres:15-alpine
environment:
POSTGRES_USER: appuser
POSTGRES_PASSWORD: secretpass
POSTGRES_DB: myapp
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U appuser -d myapp"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
api:
build: ./api
depends_on:
postgres:
condition: service_healthy
volumes:
pgdata:
pg_isready komutu PostgreSQL imajı içinde hazır gelir. Hem kullanıcı hem veritabanı adı belirtmek daha sağlıklı bir kontrol sağlar. Sadece sunucunun ayakta olduğunu değil, o özel veritabanının erişilebilir olduğunu doğrular.
Redis Health Check
Redis için redis-cli ping komutu standart yaklaşım:
version: "3.8"
services:
redis:
image: redis:7-alpine
command: redis-server --requirepass mysecretpass --appendonly yes
volumes:
- redisdata:/data
healthcheck:
test: ["CMD", "redis-cli", "-a", "mysecretpass", "ping"]
interval: 10s
timeout: 3s
retries: 3
start_period: 10s
ports:
- "6379:6379"
volumes:
redisdata:
Şifre korumalı Redis için -a parametresini eklemeyi unutmayın. Şifresiz Redis’te sadece redis-cli ping yeterli.
Gerçek Dünya Senaryosu: Tam Stack Uygulama
Bir e-ticaret uygulamasını ele alalım. Bu uygulamada PostgreSQL, Redis (cache), bir API servisi ve Nginx reverse proxy bulunuyor. Her servisin sağlıklı olması ve bağımlılıkların doğru sırayla başlaması gerekiyor:
version: "3.8"
services:
postgres:
image: postgres:15-alpine
environment:
POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD: ${DB_PASS}
POSTGRES_DB: ${DB_NAME}
volumes:
- pgdata:/var/lib/postgresql/data
- ./init-scripts:/docker-entrypoint-initdb.d
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB_USER} -d ${DB_NAME}"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
restart: unless-stopped
redis:
image: redis:7-alpine
command: redis-server --appendonly yes --maxmemory 256mb --maxmemory-policy allkeys-lru
volumes:
- redisdata:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 3
restart: unless-stopped
api:
build:
context: ./api
dockerfile: Dockerfile.prod
environment:
- DATABASE_URL=postgresql://${DB_USER}:${DB_PASS}@postgres:5432/${DB_NAME}
- REDIS_URL=redis://redis:6379
- PORT=3000
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:3000/api/health || exit 1"]
interval: 20s
timeout: 10s
retries: 3
start_period: 90s
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
restart: unless-stopped
deploy:
replicas: 2
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./certbot/conf:/etc/letsencrypt:ro
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://localhost/nginx-health || exit 1"]
interval: 15s
timeout: 5s
retries: 3
start_period: 15s
depends_on:
api:
condition: service_healthy
restart: unless-stopped
volumes:
pgdata:
redisdata:
Bu yapıda başlangıç sırası şöyle işliyor: önce PostgreSQL ve Redis sağlıklı hale geliyor, ardından API servisi başlıyor ve sağlıklı olunca Nginx devreye giriyor. Nginx sağlıklı olmadan hiçbir dış trafik uygulamaya ulaşamıyor.
Özel Health Check Script Kullanımı
Bazen tek bir komut yetmiyor. Birden fazla koşulu kontrol etmeniz gerekebilir. Bu durumda özel bir script yazıp konteynere dahil etmek daha temiz bir çözüm:
#!/bin/sh
# healthcheck.sh
# HTTP endpoint'i kontrol et
curl -f http://localhost:8080/health > /dev/null 2>&1 || exit 1
# Veritabanı bağlantısını kontrol et
curl -f http://localhost:8080/health/db > /dev/null 2>&1 || exit 1
# Disk kullanımını kontrol et (90% üzeriyse unhealthy)
DISK_USAGE=$(df / | awk 'NR==2 {print $5}' | sed 's/%//')
if [ "$DISK_USAGE" -gt 90 ]; then
echo "Disk usage critical: ${DISK_USAGE}%"
exit 1
fi
# Bellek kullanımını kontrol et
MEMORY_FREE=$(awk '/MemAvailable/ {print $2}' /proc/meminfo)
if [ "$MEMORY_FREE" -lt 104857 ]; then # 100MB'dan az
echo "Memory critically low"
exit 1
fi
exit 0
Dockerfile’a ekleyip Compose’da kullanmak:
# Dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
# Health check script'ini kopyala
COPY healthcheck.sh /usr/local/bin/healthcheck
RUN chmod +x /usr/local/bin/healthcheck
EXPOSE 3000
CMD ["node", "server.js"]
# docker-compose.yml içinde
services:
api:
build: .
healthcheck:
test: ["CMD", "/usr/local/bin/healthcheck"]
interval: 30s
timeout: 15s
retries: 3
start_period: 60s
Health Check Durumunu İzleme
Health check çalışıyor ama nasıl izleyeceksiniz? Birkaç pratik yöntem:
# Tüm konteynerlerin durumunu göster
docker compose ps
# Belirli bir konteynerin health durumunu göster
docker inspect --format='{{json .State.Health}}' konteyner_adi | python3 -m json.tool
# Health check loglarını izle
docker inspect --format='{{range .State.Health.Log}}{{.Start}} - {{.Output}}{{end}}' konteyner_adi
# Sadece health durumunu öğren
docker inspect --format='{{.State.Health.Status}}' konteyner_adi
# Sürekli izleme için watch ile kullan
watch -n 5 'docker compose ps'
Servis sağlıklı olana kadar bekleyen bir script de işinize yarayabilir:
#!/bin/bash
# wait-for-healthy.sh
SERVICE_NAME=$1
MAX_WAIT=${2:-120}
INTERVAL=5
ELAPSED=0
echo "Waiting for $SERVICE_NAME to be healthy..."
while [ $ELAPSED -lt $MAX_WAIT ]; do
STATUS=$(docker inspect --format='{{.State.Health.Status}}' "$SERVICE_NAME" 2>/dev/null)
if [ "$STATUS" = "healthy" ]; then
echo "$SERVICE_NAME is healthy after ${ELAPSED}s"
exit 0
elif [ "$STATUS" = "unhealthy" ]; then
echo "$SERVICE_NAME is unhealthy, checking logs..."
docker inspect --format='{{range .State.Health.Log}}Output: {{.Output}}{{end}}' "$SERVICE_NAME"
exit 1
fi
echo "Current status: $STATUS - waiting ${INTERVAL}s... (${ELAPSED}/${MAX_WAIT}s)"
sleep $INTERVAL
ELAPSED=$((ELAPSED + INTERVAL))
done
echo "Timeout: $SERVICE_NAME did not become healthy within ${MAX_WAIT}s"
exit 1
MySQL ve MongoDB için Health Check
MySQL için durum biraz farklı. mysqladmin ping yerine gerçek bir sorgu çalıştırmak daha güvenilir:
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: rootpass
MYSQL_DATABASE: myapp
MYSQL_USER: appuser
MYSQL_PASSWORD: apppass
healthcheck:
test: ["CMD-SHELL", "mysql -u appuser -papppass -e 'SELECT 1' myapp || exit 1"]
interval: 10s
timeout: 5s
retries: 5
start_period: 60s
volumes:
- mysqldata:/var/lib/mysql
mongodb:
image: mongo:6
environment:
MONGO_INITDB_ROOT_USERNAME: root
MONGO_INITDB_ROOT_PASSWORD: mongopass
healthcheck:
test: ["CMD-SHELL", "mongosh --eval 'db.adminCommand("ping")' --quiet || exit 1"]
interval: 15s
timeout: 10s
retries: 3
start_period: 40s
volumes:
- mongodata:/data/db
Dockerfile’da Health Check Tanımlama
Health check’i Compose dosyasına değil doğrudan Dockerfile’a da ekleyebilirsiniz. Bu yaklaşım imajı her ortamda aynı davranışı sergilediği için taşınabilirlik açısından avantajlı:
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
# Uygulama portu
EXPOSE 8000
# Health check doğrudan Dockerfile'da
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3
CMD python -c "import requests; response = requests.get('http://localhost:8000/health'); exit(0 if response.status_code == 200 else 1)"
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
Compose dosyasında bu health check’i devre dışı bırakmak isterseniz:
services:
api:
image: myapp:latest
healthcheck:
disable: true # Dockerfile'daki health check'i iptal eder
Sık Yapılan Hatalar
Çok agresif interval ve timeout değerleri: interval: 5s ve timeout: 2s gibi değerler yüksek yük altında sürekli false positive üretiyor. Production’da interval: 15s-30s arası genellikle makul.
start_period’u atlamak: Özellikle Java uygulamaları, Python uygulamaları veya büyük Node.js projeleri başlangıçta ciddi süre alıyor. start_period olmadan konteyner sürekli unhealthy olarak işaretleniyor ve gereksiz restart döngüsüne giriyor.
curl’ü imajda var saymak: Alpine tabanlı minimal imajlarda curl olmayabilir. Bu durumda ya curl yüklüyorsunuz ya wget kullanıyorsunuz ya da dile özgü HTTP istemci kullanıyorsunuz.
# Alpine'da wget alternatifi
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://localhost:8080/health || exit 1"]
# Python ile HTTP kontrolü
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"]
# Node.js ile
healthcheck:
test: ["CMD", "node", "-e", "require('http').get('http://localhost:3000/health', r => process.exit(r.statusCode===200?0:1))"]
Sadece port dinlemeyi kontrol etmek: nc -z localhost 3000 komutu portun açık olduğunu kontrol eder ama uygulamanın gerçekten çalışıp çalışmadığını değil. HTTP endpoint kontrolü her zaman daha anlamlı.
Health Check ile Otomatik Yeniden Başlatma
restart politikası ile health check’i birleştirince güçlü bir self-healing mekanizması elde ediyorsunuz:
services:
worker:
image: myworker:latest
healthcheck:
test: ["CMD-SHELL", "pgrep -f 'python worker.py' || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 20s
restart: on-failure
# ya da:
# restart: unless-stopped
restart: unless-stopped ile unhealthy olan konteyner otomatik yeniden başlar. Ama dikkat: bu sonsuz döngüye girebilir. Logları izlemek ve --restart-max-attempts gibi limitleri değerlendirmek önemli.
Sonuç
Health check, Docker Compose ile ciddi bir uygulama yönetiyorsanız atlanmaması gereken bir detay. Uygulamalarınıza birkaç satır ekleyerek hem başlangıç sırası problemlerini çözüyor hem de çalışma zamanında oluşan sessiz hataları yakalamış oluyorsunuz.
Özetlemek gerekirse:
- Her servis için uygun bir health check endpoint’i ya da komutu tanımlayın
start_perioddeğerini gerçekçi tutun, uygulamanızın soğuk başlangıç süresini ölçündepends_onilecondition: service_healthykombinasyonunu kullanın- Minimal imajlarda curl yerine alternatif araçları değerlendirin
- Health check durumlarını izlemek için
docker inspectkomutlarını alışkanlık haline getirin - Production ortamında health check loglarını merkezi log sistemine yönlendirin
Küçük bir yatırım, büyük bir güvenlik ağı. Özellikle gece 2’de telefon çalmadan önce bu kontrolü yapmanızı şiddetle tavsiye ederim.