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.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir