Log Yönetimi: Docker Compose Ortamında Çıktı İzleme

Prodüksiyonda bir şeyler ters gittiğinde, o kritik anlarda sana hayat kurtaran tek şey iyi yönetilmiş loglardır. Docker Compose ile çalışan ortamlarda log yönetimi, başlangıçta basit görünse de üzerinde düşünülmesi gereken oldukça derin bir konu. Birden fazla servisin aynı anda çalıştığı, her birinin farklı log formatında çıktı ürettiği bir ortamda neyin nerede olduğunu bilmek, bir olayı dakikalar içinde çözmek ile saatlerce uğraşmak arasındaki farkı yaratır.

Bu yazıda Docker Compose ortamında log yönetimini her yönüyle ele alacağız. Temel komutlardan başlayıp ileri düzey yapılandırmalara, log driver’larına ve merkezi log toplama mimarisine kadar gideceğiz.

Docker Compose Log Temelleri

Docker Compose, arka planda Docker’ın kendi log altyapısını kullanır. Varsayılan olarak her konteyner standart çıktıya (stdout) ve standart hata çıktısına (stderr) yazan logları yakalar ve bunları JSON formatında saklar. Bu JSON dosyaları genellikle /var/lib/docker/containers// altında bulunur.

Compose ile ayağa kaldırdığın bir uygulamanın loglarını görmenin en hızlı yolu:

# Tüm servislerin loglarını göster
docker compose logs

# Belirli bir servisin loglarını göster
docker compose logs web

# Birden fazla servisin loglarını birlikte izle
docker compose logs web db redis

# Canlı log takibi (tail -f benzeri)
docker compose logs -f

# Son N satırı göster
docker compose logs --tail=100 web

# Timestamp ile birlikte göster
docker compose logs -t web

# Hem canlı takip hem de timestamp hem de son 50 satır
docker compose logs -f -t --tail=50 web

Burada sık kullanılan parametrelere bakalım:

  • -f / –follow: Logları gerçek zamanlı olarak takip eder, yeni gelen satırları ekrana basar
  • -t / –timestamps: Her log satırının başına zaman damgası ekler
  • –tail=N: Sadece son N satırı gösterir, büyük log dosyalarında performans açısından kritik
  • –no-color: Renk kodlarını devre dışı bırakır, log dosyasına yönlendirirken işe yarar
  • –since: Belirli bir zamandan itibaren logları gösterir (örneğin --since 2h son 2 saat)
  • –until: Belirli bir zamana kadar olan logları gösterir

Gerçek dünya senaryosu olarak düşün: Sabah 3’te bir alarm geliyor ve servisin çöktüğünü görüyorsun. İlk yapacağın şey gece yarısından itibaren olan loglara bakmak olur.

# Son 2 saatin loglarını göster
docker compose logs --since 2h web

# Belirli bir saat aralığını göster
docker compose logs --since "2024-01-15T02:00:00" --until "2024-01-15T04:00:00" web

# Hataları filtrele
docker compose logs web 2>&1 | grep -i "error|exception|fatal"

Log Driver Yapılandırması

Docker’ın varsayılan log driver’ı json-file‘dır. Bu driver basit kullanım için yeterli olsa da prodüksiyon ortamlarında sınırlamaları vardır. Disk dolması bunların başında gelir.

Log driver’ını ve parametrelerini docker-compose.yml dosyasında servis bazında ya da global olarak tanımlayabilirsin.

version: '3.8'

services:
  web:
    image: nginx:alpine
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "5"
        compress: "true"
  
  api:
    image: myapi:latest
    logging:
      driver: "json-file"
      options:
        max-size: "50m"
        max-file: "10"
        compress: "true"
        labels: "service_name,environment"
        env: "NODE_ENV,APP_VERSION"

  worker:
    image: myworker:latest
    logging:
      driver: "syslog"
      options:
        syslog-address: "tcp://logserver:514"
        syslog-facility: "local0"
        tag: "worker-service"

json-file driver seçenekleri:

  • max-size: Tek bir log dosyasının maksimum boyutu (10m, 1g gibi)
  • max-file: Kaç tane rotasyon dosyası tutulacağı
  • compress: Eski log dosyalarını gzip ile sıkıştırır
  • labels: Konteynere atanan label’lardan hangilerinin log metadata’sına ekleneceği
  • env: Hangi environment variable’ların log metadata’sına ekleneceği

Prodüksiyonda mutlaka max-size ve max-file parametrelerini ayarla. Aksi takdirde diskini dolduran loglar yüzünden tüm sunucu kilitlenebilir. Bunu acı tecrübeyle öğrenmemek için şimdiden düzgün yapılandır.

Local Driver ile Gelişmiş Kontrol

Docker 20.10 ile gelen local driver, json-file‘a göre daha iyi performans ve sıkıştırma sunar. Özellikle log üretiminin yoğun olduğu servislerde ciddi disk tasarrufu sağlar.

version: '3.8'

services:
  high-traffic-api:
    image: api:latest
    logging:
      driver: "local"
      options:
        max-size: "50m"
        max-file: "7"
        compress: "true"

local driver binary formatında yazar ve varsayılan olarak sıkıştırma kullanır. Dezavantajı docker compose logs komutuyla logları okuyabilirsin ama doğrudan dosya sistemi üzerinden okuman zorlaşır.

Çok Servisli Ortamda Log Filtreleme

Gerçek bir Compose ortamında genellikle 5-10 arası servis bulunur. Hepsinin logunu aynı anda görmek hem işe yarar hem de kaotik olabilir. Akıllıca filtreleme yapmak önemli.

# Sadece belirli kelimeleri içeren satırları göster
docker compose logs -f web | grep -E "ERROR|WARN|CRITICAL"

# Belirli bir IP adresinden gelen istekleri takip et
docker compose logs -f nginx | grep "192.168.1.100"

# Servis adını prefix olarak görmek istemiyorsan
docker compose logs --no-log-prefix web

# Log çıktısını dosyaya kaydet
docker compose logs --no-color web > /tmp/web-logs-$(date +%Y%m%d).log

# Birden fazla pattern ara
docker compose logs api | grep -E "(500|502|503|504)"

# Context ile birlikte göster (eşleşmenin etrafındaki satırlar)
docker compose logs api | grep -A 5 -B 2 "Exception"

Şimdi daha pratik bir senaryo: API servisinde 500 hataları alıyorsun ama bu hatalar sadece belirli endpoint’lerde oluşuyor.

# POST isteklerindeki 500 hatalarını bul
docker compose logs nginx | grep "POST" | grep " 500 "

# Son 1 saat içindeki hataları kategorize et
docker compose logs --since 1h api | awk '/ERROR/{print $0}' | sort | uniq -c | sort -rn | head -20

Log Yönetimi için Özel Compose Yapılandırması

Birden fazla ortam için farklı log yapılandırmaları kullanmak isteyebilirsin. Development ortamında her şeyi görmek isterken, prodüksiyonda sadece WARNING ve üzerini yakalamak yeterli olabilir.

# docker-compose.yml (base)
version: '3.8'

x-logging: &default-logging
  driver: "json-file"
  options:
    max-size: "10m"
    max-file: "3"

services:
  web:
    image: nginx:alpine
    logging: *default-logging
  
  api:
    image: myapi:latest
    logging: *default-logging
  
  db:
    image: postgres:15
    logging: *default-logging
# docker-compose.prod.yml (override)
version: '3.8'

x-logging-prod: &prod-logging
  driver: "json-file"
  options:
    max-size: "100m"
    max-file: "10"
    compress: "true"

services:
  web:
    logging: *prod-logging
  
  api:
    logging: *prod-logging
    environment:
      - LOG_LEVEL=WARNING
  
  db:
    logging:
      driver: "json-file"
      options:
        max-size: "200m"
        max-file: "15"
        compress: "true"

Bu yapılandırmayı kullanmak için:

# Development
docker compose up -d

# Production (override ile)
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

YAML anchor’ları (&default-logging ve *default-logging) ile tekrar eden logging yapılandırmalarından kurtuluyorsun. Bu özellikle 10+ servisin olduğu projelerde büyük kolaylık sağlar.

Fluentd ile Merkezi Log Toplama

Küçük ortamlar için json-file yeterlidir. Ama birden fazla sunucuda Compose yığınları çalıştırıyorsan ya da log analizi yapman gerekiyorsa, merkezi bir log toplama sistemine ihtiyacın var. Fluentd bu iş için oldukça yaygın kullanılan bir araç.

version: '3.8'

services:
  fluentd:
    image: fluentd:v1.16
    volumes:
      - ./fluentd/conf:/fluentd/etc
      - ./logs:/var/log/fluentd
    ports:
      - "24224:24224"
      - "24224:24224/udp"
    networks:
      - logging-net

  web:
    image: nginx:alpine
    logging:
      driver: "fluentd"
      options:
        fluentd-address: "localhost:24224"
        fluentd-async: "true"
        tag: "nginx.access"
    depends_on:
      - fluentd
    networks:
      - logging-net

  api:
    image: myapi:latest
    logging:
      driver: "fluentd"
      options:
        fluentd-address: "localhost:24224"
        fluentd-async: "true"
        tag: "api.application"
        fluentd-buffer-limit: "8MB"
    depends_on:
      - fluentd
    networks:
      - logging-net

networks:
  logging-net:
    driver: bridge

Fluentd konfigürasyon dosyası (./fluentd/conf/fluent.conf):

<source>
  @type forward
  port 24224
  bind 0.0.0.0
</source>

<filter nginx.**>
  @type parser
  key_name log
  <parse>
    @type nginx
  </parse>
</filter>

<match nginx.**>
  @type file
  path /var/log/fluentd/nginx
  append true
  <buffer time>
    timekey 1h
    timekey_wait 10m
  </buffer>
  <format>
    @type json
  </format>
</match>

<match api.**>
  @type file
  path /var/log/fluentd/api
  append true
</match>

fluentd-async: "true" parametresi önemli. Bu ayar olmadan Fluentd’ye log gönderimi başarısız olursa konteyner bloklanabilir. Async modda loglar buffer’a yazılır ve Fluentd erişilebilir olduğunda gönderilir.

ELK Stack Entegrasyonu

Daha kapsamlı bir log analizi için Elasticsearch, Logstash ve Kibana üçlüsünü Compose içinde ayağa kaldırabilirsin.

version: '3.8'

services:
  elasticsearch:
    image: elasticsearch:8.11.0
    environment:
      - discovery.type=single-node
      - xpack.security.enabled=false
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    volumes:
      - es-data:/usr/share/elasticsearch/data
    ports:
      - "9200:9200"
    networks:
      - elk-net

  logstash:
    image: logstash:8.11.0
    volumes:
      - ./logstash/pipeline:/usr/share/logstash/pipeline
    ports:
      - "5044:5044"
      - "5000:5000/udp"
    environment:
      - "LS_JAVA_OPTS=-Xmx256m -Xms256m"
    depends_on:
      - elasticsearch
    networks:
      - elk-net

  kibana:
    image: kibana:8.11.0
    ports:
      - "5601:5601"
    environment:
      - ELASTICSEARCH_HOSTS=http://elasticsearch:9200
    depends_on:
      - elasticsearch
    networks:
      - elk-net

  # Uygulama servisleri
  web:
    image: nginx:alpine
    logging:
      driver: "gelf"
      options:
        gelf-address: "udp://localhost:5000"
        tag: "nginx"
    networks:
      - elk-net

volumes:
  es-data:

networks:
  elk-net:
    driver: bridge

Bu yapılandırma özellikle log arama ve görselleştirme ihtiyacı olan ekipler için çok değerli. Kibana üzerinden tüm servislerinin loglarını tek ekranda görebilir, zaman bazlı grafikler çizebilir, alert kurabilirsin.

Log Rotasyonu ve Temizleme

Log rotasyonu sadece Compose yapılandırmasıyla değil, sistem düzeyinde de yapılabilir. Özellikle eski logları periyodik olarak temizlemek için cron job’lar kullanmak iyi bir pratiktir.

#!/bin/bash
# /usr/local/bin/cleanup-docker-logs.sh

# 30 günden eski Docker log dosyalarını temizle
find /var/lib/docker/containers/ -name "*.log" -mtime +30 -exec truncate -s 0 {} ;

# Boyutu 100MB'ı aşan log dosyalarını truncate et
find /var/lib/docker/containers/ -name "*.log" -size +100M -exec truncate -s 50M {} ;

# Durmuş konteynerlerin loglarını temizle
docker container prune -f

echo "Log cleanup completed at $(date)"

Bu script’i crontab’a ekle:

# Her gece 02:00'de çalıştır
0 2 * * * /usr/local/bin/cleanup-docker-logs.sh >> /var/log/docker-cleanup.log 2>&1

Compose servislerinin loglarını komut satırından temizlemek için Docker’ın doğrudan bir “log clear” komutu yok. Ama şu yöntemle yapabilirsin:

# Belirli bir konteynerin log dosyasını temizle
CONTAINER_ID=$(docker compose ps -q web)
truncate -s 0 /var/lib/docker/containers/${CONTAINER_ID}/${CONTAINER_ID}-json.log

# Veya container'ı yeniden başlatarak log rotation tetikle
docker compose restart web

Uygulama İçi Log Yönetimi Best Practices

Konteynerlerin log yönetimi sadece Docker tarafında değil, uygulama tarafında da doğru yapılmalı. Uygulamanın stdout ve stderr’e doğru şekilde yazması şart.

Bir Node.js uygulaması için örnek:

// Kötü: Dosyaya yazıyor
const fs = require('fs');
fs.appendFileSync('/app/logs/app.log', message);

// İyi: stdout/stderr'e yazıyor
console.log(JSON.stringify({
  timestamp: new Date().toISOString(),
  level: 'INFO',
  service: 'api',
  message: message,
  requestId: req.id
}));

// Hata için stderr
console.error(JSON.stringify({
  timestamp: new Date().toISOString(),
  level: 'ERROR',
  service: 'api',
  message: error.message,
  stack: error.stack
}));

JSON formatında log yazmak, log aggregation araçlarıyla çok daha kolay çalışmana olanak tanır. Structured logging denen bu yaklaşım, logları makinenin okuyabileceği formata sokar.

Sağlık Kontrolü ve Log Korelasyonu

Servis sağlık durumunu loglarla ilişkilendirmek, sorun gidermeyi büyük ölçüde hızlandırır.

version: '3.8'

services:
  api:
    image: myapi:latest
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s
    logging:
      driver: "json-file"
      options:
        max-size: "50m"
        max-file: "5"
        labels: "com.myapp.service"
    labels:
      com.myapp.service: "api"

Sağlık kontrolü başarısız olduğunda otomatik olarak log toplayan bir script:

#!/bin/bash
# health-monitor.sh

PROJECT_NAME="myapp"
ALERT_EMAIL="[email protected]"

while true; do
  # Sağlıksız konteynerleri kontrol et
  UNHEALTHY=$(docker compose -p $PROJECT_NAME ps --format json | 
    python3 -c "import sys, json; 
    containers = [json.loads(l) for l in sys.stdin]; 
    print('n'.join([c['Name'] for c in containers if c.get('Health') == 'unhealthy']))")
  
  if [ -n "$UNHEALTHY" ]; then
    echo "Unhealthy containers detected: $UNHEALTHY"
    
    # Sağlıksız konteynerlerin loglarını topla
    for container in $UNHEALTHY; do
      SERVICE=$(echo $container | sed 's/.*_//')
      docker compose -p $PROJECT_NAME logs --tail=100 $SERVICE > 
        /tmp/unhealthy-${SERVICE}-$(date +%Y%m%d%H%M%S).log
    done
    
    # Opsiyonel: email gönder
    # mail -s "Unhealthy containers: $UNHEALTHY" $ALERT_EMAIL < /dev/null
  fi
  
  sleep 60
done

Compose Event’lerini İzleme

Docker Compose’un log komutunun yanı sıra events komutu da çok işe yarar. Konteyner başlatma, durdurma, yeniden başlatma gibi olayları gerçek zamanlı izleyebilirsin.

# Tüm Docker event'lerini izle
docker events

# Sadece Compose projesindeki event'leri izle
docker events --filter "label=com.docker.compose.project=myapp"

# Belirli event türlerini filtrele
docker events --filter "event=die" --filter "event=oom"

# JSON formatında event'leri kaydet
docker events --format '{{json .}}' > /var/log/docker-events.log &

OOM (Out of Memory) event’leri özellikle kritik. Bir konteyner bellek yetersizliğinden ölüyorsa bunu anında yakalamak lazım.

# OOM kill event'lerini izle ve log al
docker events --filter "event=oom" | while read event; do
  echo "OOM Kill detected: $event" | tee -a /var/log/oom-events.log
  # İlgili servisin son loglarını kaydet
  docker compose logs --tail=200 >> /var/log/oom-events.log
done

Pratik Sorun Giderme Senaryosu

Gerçek bir prodüksiyon senaryosunu baştan sona ele alalım. Diyelim ki e-ticaret platformun var, sabah 9’da kullanıcılar ödeme yapamıyor.

# 1. Adım: Hangi servisler çalışıyor?
docker compose ps

# 2. Adım: Son 30 dakikanın genel görünümü
docker compose logs --since 30m 2>&1 | grep -E "ERROR|FATAL|Exception" | head -50

# 3. Adım: Ödeme servisine odaklan
docker compose logs --since 30m payment-service | tail -100

# 4. Adım: Database bağlantı hatalarını ara
docker compose logs --since 30m payment-service | grep -i "connection|timeout|refused"

# 5. Adım: Database servisinin loglarına bak
docker compose logs --since 30m db | grep -E "ERROR|FATAL|connection"

# 6. Adım: Tam hata stack trace'ini bul
docker compose logs payment-service | grep -A 20 "SQLException"

# 7. Adım: Resource kullanımını kontrol et
docker stats --no-stream $(docker compose ps -q)

# 8. Adım: Sorun bulundu, servisi yeniden başlat
docker compose restart payment-service

# 9. Adım: Yeniden başlatma sonrası logları izle
docker compose logs -f payment-service

Bu sistematik yaklaşım, sorunun 5-10 dakika içinde tespit edilmesini sağlar. Log’lar düzgün yapılandırılmamışsa bu süreç saatlere uzayabilir.

Sonuç

Docker Compose ortamında log yönetimi, kurulum sırasında “sonra hallederim” diyip geçiştirilen ama prodüksiyonda pahalıya mal olan bir konu. Birkaç temel pratiği hayatına sokman yeterli:

  • Mutlaka log rotation konfigüre et. max-size ve max-file ayarlamadan hiçbir servisi prodüksiyona alma. Disk dolması senaryosu kaçınılmaz olur.
  • Structured logging kullan. Uygulamalarını JSON formatında log üretecek şekilde yapılandır. Bu küçük değişiklik, log analizi sırasında sana çok büyük zaman kazandırır.
  • Ortama göre farklı yapılandırmalar kullan. Development ortamında verbose logging normal ama prodüksiyonda sadece gerekli seviyeleri logla.
  • Merkezi log sistemi kur. Birden fazla servis veya sunucun varsa Fluentd ya da ELK Stack gibi bir çözüme geç. Onlarca serviste tek tek log aramak hem zaman kaybı hem de hata yapma riski demek.
  • OOM ve health event’lerini izle. Konteynerlerin ne zaman ve neden öldüğünü anlamak için Docker event stream’ini takip et.
  • Log’ları sorun olmadan önce test et. Bir incident sırasında log yapılandırmasının eksik olduğunu fark etmek, en kötü zamanlarda gelen en kötü sürprizlerden biridir.

Log yönetimi bir kez iyi kurulduğunda arka planda sessizce çalışır ve ihtiyaç duyduğun anda tam olarak aradığını bulmanı sağlar. Bunu sistematik bir şekilde kurmak için harcadığın birkaç saatlik çaba, ilerleyen süreçte onlarca saatlik sorun giderme süresini sana geri verir.

Yorum yapın