Docker’da Sağlık Kontrolü: HEALTHCHECK ile Konteyner İzleme ve Otomatik Yeniden Başlatma
Production ortamında konteynerlerinizin “çalışıyor” görünmesi ile gerçekten sağlıklı çalışması arasında ciddi bir fark var. Docker, bir konteyneri başlattıktan sonra process’in ayakta olup olmadığını kontrol eder, ama uygulamanızın içi çökmüş olabilir. İşte tam bu noktada HEALTHCHECK direktifi devreye giriyor ve işleri bambaşka bir boyuta taşıyor.
HEALTHCHECK Nedir ve Neden Önemlidir
Docker’ın varsayılan davranışı oldukça kaba bir kontrol yapar: process çalışıyor mu? Evet. Tamam, konteyner running durumunda. Ama ya veritabanı bağlantınız kopmuşsa? Ya uygulama deadlock’a girmişse? Ya memory leak yüzünden response time 30 saniyeye çıkmışsa? Docker bunların hiçbirini bilmez, konteyner hala “healthy” görünür.
HEALTHCHECK, Docker’a “bu konteynerin gerçekten sağlıklı olup olmadığını şu komutla kontrol et” deme imkanı verir. Bu komut belirli aralıklarla çalışır, sonuca göre konteynerin durumu healthy, unhealthy veya starting olarak işaretlenir. Swarm mode’da unhealthy konteynerler otomatik olarak yeniden başlatılır, Kubernetes’te ise benzer mekanizmalar devreye girer.
Gerçek dünyadan bir örnek verelim: E-ticaret platformunda gecenin 3’ünde Redis bağlantısı koptu, ama uygulama process’i ayaktaydı. Monitoring sistemi konteyneri healthy görüyordu. Sabaha kadar binlerce kullanıcı sepet hatası aldı. HEALTHCHECK olsaydı, konteyner unhealthy işaretlenir ve Swarm otomatik olarak yeniden başlatırdı.
HEALTHCHECK Sözdizimi ve Parametreler
Dockerfile içinde iki farklı kullanım şekli vardır.
# Temel kullanım
HEALTHCHECK [OPTIONS] CMD <komut>
# HEALTHCHECK'i devre dışı bırakma (base image'dan miras alınanı ezmek için)
HEALTHCHECK NONE
Kullanabileceğiniz opsiyonlar şunlar:
- –interval=DURATION: Kontroller arasındaki süre. Varsayılan: 30s
- –timeout=DURATION: Komutun maksimum çalışma süresi. Varsayılan: 30s
- –start-period=DURATION: Konteyner başladıktan sonra ilk kontrole kadar bekleme süresi. Varsayılan: 0s
- –retries=N: Kaç başarısız kontrolden sonra unhealthy sayılacak. Varsayılan: 3
Komutun çıkış kodu önemlidir:
- 0: Başarılı, konteyner sağlıklı
- 1: Başarısız, konteyner sağlıksız
- 2: Reserved, kullanmayın
İlk Pratik Örnek: Basit Web Uygulaması
Nginx üzerinde koşan basit bir web uygulaması için başlangıç seviyesi bir HEALTHCHECK yazalım:
FROM nginx:alpine
COPY ./html /usr/share/nginx/html
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3
CMD wget --no-verbose --tries=1 --spider http://localhost/ || exit 1
EXPOSE 80
Burada wget ile localhost’a istek atıyoruz. --spider flag’i sayfayı indirmeden sadece erişilebilir olup olmadığını kontrol eder. --no-verbose gereksiz çıktıyı engeller. Alternatif olarak curl kullanabilirsiniz:
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3
CMD curl -f http://localhost/ || exit 1
curl -f flag’i, HTTP 4xx veya 5xx döndüğünde exit code 1 üretir. Bu önemli çünkü sunucu ayakta ama 500 döndürüyorsa bunu da yakalamak istiyoruz.
Node.js Express Uygulaması için HEALTHCHECK
Production’da sıkça karşılaşılan senaryo: Node.js uygulaması çalışıyor ama veritabanı bağlantısı kopmuş durumda. Bu durumu yakalamak için dedicated bir health endpoint oluşturalım.
Önce uygulama tarafında /health endpoint’ini ekleyelim:
// health.js - Express uygulamanıza eklenecek
app.get('/health', async (req, res) => {
try {
// Veritabanı bağlantısını kontrol et
await db.query('SELECT 1');
// Redis bağlantısını kontrol et
await redis.ping();
res.status(200).json({
status: 'healthy',
timestamp: new Date().toISOString(),
uptime: process.uptime()
});
} catch (error) {
res.status(503).json({
status: 'unhealthy',
error: error.message
});
}
});
Şimdi bu endpoint’i kullanan Dockerfile:
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
# Uygulama başlamadan önce bağlantıların kurulması için 15s bekliyoruz
HEALTHCHECK --interval=20s --timeout=5s --start-period=15s --retries=3
CMD wget -qO- http://localhost:3000/health || exit 1
EXPOSE 3000
CMD ["node", "server.js"]
--start-period=15s burada kritik. Uygulama başlarken veritabanı bağlantısı kurulmadan önce health check çalışırsa false negative alırsınız ve konteyner gereksiz yere unhealthy işaretlenir. Start period boyunca başarısız kontroller unhealthy sayılmaz.
Python/Flask Uygulaması ve Gelişmiş Kontrol
Database migration yapan, birden fazla bağımlılığı olan bir Flask uygulaması için daha kapsamlı bir örnek:
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
# Python ile health check - dış araç gerektirmez
HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3
CMD python -c "
import urllib.request
import sys
try:
response = urllib.request.urlopen('http://localhost:5000/health', timeout=8)
if response.status == 200:
sys.exit(0)
sys.exit(1)
except Exception:
sys.exit(1)
"
EXPOSE 5000
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "app:application"]
Python’un standart kütüphanesini kullandık, ekstra bağımlılık yok. Alpine tabanlı imajlarda bazen wget veya curl olmayabilir, bu yaklaşım daha taşınabilir.
Docker Compose ile HEALTHCHECK Entegrasyonu
docker-compose.yml içinde de HEALTHCHECK tanımlayabilir, hatta servislerin birbirini beklemesini sağlayabilirsiniz. Bu, depends_on ile birlikte kullanıldığında güçlü bir pattern oluşturur:
version: '3.8'
services:
postgres:
image: postgres:15
environment:
POSTGRES_DB: myapp
POSTGRES_USER: user
POSTGRES_PASSWORD: password
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user -d myapp"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
volumes:
- pgdata:/var/lib/postgresql/data
redis:
image: redis:7-alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 3
app:
build: .
ports:
- "3000:3000"
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
environment:
DATABASE_URL: postgresql://user:password@postgres:5432/myapp
REDIS_URL: redis://redis:6379
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:3000/health"]
interval: 20s
timeout: 5s
retries: 3
start_period: 20s
volumes:
pgdata:
Burada condition: service_healthy ile uygulamanın, PostgreSQL ve Redis sağlıklı hale gelene kadar başlamayacağını söylüyoruz. Eski depends_on sadece konteynerin başladığını kontrol ederdi, bu yaklaşım çok daha güvenilir.
HEALTHCHECK Durumunu İzleme
Konteynerin sağlık durumunu nasıl takip edersiniz?
# Tüm konteynerlerin durumunu gör
docker ps
# Detaylı sağlık bilgisi
docker inspect --format='{{json .State.Health}}' konteyner_adi | python3 -m json.tool
# Sadece sağlık durumu
docker inspect --format='{{.State.Health.Status}}' konteyner_adi
# Son health check loglarını gör
docker inspect --format='{{range .State.Health.Log}}{{.Output}}{{end}}' konteyner_adi
docker ps çıktısında STATUS kolonunda (healthy), (unhealthy) veya (health: starting) görürsünüz. Monitoring scriptlerinizde bu bilgiyi kullanabilirsiniz:
#!/bin/bash
# health_monitor.sh - Tüm konteynerleri izle ve alert gönder
UNHEALTHY=$(docker ps --filter health=unhealthy --format "{{.Names}}" 2>/dev/null)
if [ -n "$UNHEALTHY" ]; then
echo "UNHEALTHY KONTEYNERLER: $UNHEALTHY"
# Slack webhook veya email notification buraya
# curl -X POST -H 'Content-type: application/json'
# --data "{"text":"Unhealthy konteyner: $UNHEALTHY"}"
# YOUR_SLACK_WEBHOOK_URL
exit 1
fi
echo "Tüm konteynerler sağlıklı"
exit 0
Docker Swarm ile Otomatik Yeniden Başlatma
Swarm mode’un gerçek gücü burada ortaya çıkıyor. Bir servis tanımlarken restart policy ile birleşince çok güçlü bir sistem elde edersiniz:
# Swarm servisi oluşturma
docker service create
--name web-app
--replicas 3
--restart-condition on-failure
--restart-delay 5s
--restart-max-attempts 3
--health-cmd "curl -f http://localhost:3000/health || exit 1"
--health-interval 20s
--health-timeout 5s
--health-retries 3
--health-start-period 15s
myapp:latest
Swarm, unhealthy olan replika’yı otomatik olarak sonlandırır ve yerine yenisini başlatır. 3 replica çalışıyorsa, biri unhealthy olduğunda servis kesintisiz çalışmaya devam eder.
Mevcut bir servisin health check ayarlarını güncellemek için:
docker service update
--health-cmd "wget -qO- http://localhost:3000/health || exit 1"
--health-interval 30s
--health-timeout 10s
--health-retries 3
web-app
Karmaşık Senaryolar: Multi-Stage Health Check
Bazen tek bir HTTP isteği yeterli olmaz. Birden fazla şeyi kontrol eden bir script yazabilirsiniz:
FROM java:17-alpine
WORKDIR /app
COPY target/app.jar .
# Health check script'ini kopyala
COPY healthcheck.sh /usr/local/bin/healthcheck.sh
RUN chmod +x /usr/local/bin/healthcheck.sh
HEALTHCHECK --interval=30s --timeout=15s --start-period=60s --retries=3
CMD /usr/local/bin/healthcheck.sh
EXPOSE 8080
CMD ["java", "-jar", "app.jar"]
#!/bin/sh
# healthcheck.sh
# HTTP health endpoint kontrolü
HTTP_STATUS=$(wget -qO- -S http://localhost:8080/actuator/health 2>&1 | grep "HTTP/" | awk '{print $2}')
if [ "$HTTP_STATUS" != "200" ]; then
echo "HTTP health check basarisiz: $HTTP_STATUS"
exit 1
fi
# Disk alanı kontrolü - %90'dan fazla doluysa uyar
DISK_USAGE=$(df /app | awk 'NR==2 {print $5}' | sed 's/%//')
if [ "$DISK_USAGE" -gt 90 ]; then
echo "Disk dolulugu kritik: %$DISK_USAGE"
exit 1
fi
# Memory kontrolü - 200MB'tan az varsa uyar
FREE_MEM=$(free -m | awk 'NR==2{print $4}')
if [ "$FREE_MEM" -lt 200 ]; then
echo "Yetersiz bellek: ${FREE_MEM}MB"
exit 1
fi
echo "Tum kontroller basarili"
exit 0
Java Spring Boot uygulamaları için /actuator/health endpoint’i zaten built-in geliyor, ayrıca endpoint yazmaya gerek yok.
Yaygın Hatalar ve Çözümleri
Production’da en sık karşılaşılan sorunları ve çözümlerini paylaşayım.
start-period’u çok kısa ayarlamak: Uygulama başlarken veritabanı migration’ları veya cache warmup işlemleri uzun sürebilir. Uygulamanın ortalama başlama süresini ölçün ve start-period’u ona göre ayarlayın. Şüphe durumunda biraz fazla vermek az vermekten iyidir.
Timeout’u health check süresinden kısa ayarlamak: Eğer health check endpoint’iniz bazen 4-5 saniye sürüyorsa ve timeout’u 3s’ye ayarladıysanız, false negative alırsınız. Timeout her zaman gerçek response süresinin en az 2 katı olmalı.
Health endpoint’inin çok ağır olması: /health endpoint’i hafif olmalı. Tüm veritabanı tablolarını sayan, kompleks sorgular çalıştıran bir health check yazmayın. Sadece bağlantının açık olduğunu doğrulayan basit bir SELECT 1 yeterli.
Image’da wget veya curl olmaması: Alpine tabanlı minimal imajlarda bu araçlar olmayabilir. Ya RUN apk add --no-cache curl ile ekleyin, ya da Python/sh ile native HTTP isteği yapın.
# Alpine'de curl yoksa ekle
FROM alpine:3.18
RUN apk add --no-cache curl
# veya wget kullan (Alpine'de varsayılan olarak gelir)
HEALTHCHECK CMD wget -qO- http://localhost/ || exit 1
Distroless imajlarda HEALTHCHECK: Distroless imajlarda shell bile yok. Bu durumda binary olarak komut belirtmelisiniz:
FROM gcr.io/distroless/java17
COPY --from=builder /app/target/app.jar /app.jar
# Shell yok, direkt binary çağır
HEALTHCHECK --interval=30s --timeout=10s
CMD ["/ko-app/healthcheck"]
CMD ["/app.jar"]
Prometheus ve Grafana ile Entegrasyon
HEALTHCHECK sonuçlarını Prometheus’a beslemek için basit bir exporter yazabilirsiniz:
#!/bin/bash
# docker_health_exporter.sh
# Bu script'i cron ile her 30 saniyede çalıştırın
METRICS_FILE="/var/lib/prometheus/docker_health.prom"
TEMP_FILE="${METRICS_FILE}.tmp"
echo "# HELP docker_container_health Docker container health status" > "$TEMP_FILE"
echo "# TYPE docker_container_health gauge" >> "$TEMP_FILE"
docker ps --format "{{.Names}}" | while read container; do
STATUS=$(docker inspect --format='{{.State.Health.Status}}' "$container" 2>/dev/null)
case "$STATUS" in
"healthy") VALUE=1 ;;
"unhealthy") VALUE=0 ;;
"starting") VALUE=2 ;;
*) VALUE=-1 ;;
esac
echo "docker_container_health{container="$container",status="$STATUS"} $VALUE" >> "$TEMP_FILE"
done
mv "$TEMP_FILE" "$METRICS_FILE"
Bu metrikleri Grafana’da görselleştirip, unhealthy konteyner sayısı 0’ın üzerine çıktığında alert tetikleyebilirsiniz.
Sonuç
HEALTHCHECK direktifi, konteyner yönetiminde “çalışıyor mu?” sorusundan “gerçekten sağlıklı mı?” sorusuna geçişi sağlayan kritik bir araç. Doğru kullanıldığında gece 3’teki Redis kesintisi gibi olayları otomatik olarak çözebilir, on-call ekibinizin uyku düzenini koruyabilirsiniz.
Pratik önerilerim şunlar: Her production Dockerfile’ına mutlaka HEALTHCHECK ekleyin. Uygulama katmanında /health endpoint’i standart hale getirin ve bu endpoint’te kritik bağımlılıkları kontrol edin. start-period değerini gerçek başlama sürelerine göre ayarlayın ve asla sıfır bırakmayın. Docker Compose’da condition: service_healthy ile servis bağımlılıklarını sağlam temele oturtun.
HEALTHCHECK tek başına yeterli değil, bunu monitoring stack’inizin bir parçası olarak düşünün. Prometheus, Grafana, alerting mekanizmaları ile birleşince gerçek anlamda observable bir sistem elde edersiniz. Konteynerlerinizin sadece ayakta değil, gerçekten iş yapıyor olduğunu bilmek, hem uyku kalitenizi hem de kullanıcı deneyimini doğrudan etkiler.
