Caddy ile Grafana ve Loki Kullanarak Log İzleme

Modern altyapılarda log yönetimi ve metrik izleme artık bir lüks değil, zorunluluk haline geldi. Özellikle mikroservis mimarilerinde onlarca container’dan gelen logları tek bir yerden takip etmek, sorunları hızlıca tespit etmek için kritik önem taşıyor. Bu yazıda Caddy web sunucusunu reverse proxy olarak kullanarak Grafana ve Loki’yi nasıl kuracağımızı, yapılandıracağımızı ve gerçek dünya senaryolarında nasıl kullanacağımızı adım adım inceleyeceğiz.

Neden Bu Üçlü: Caddy + Grafana + Loki?

Grafana, metrik ve log görselleştirme konusunda endüstri standardı haline gelmiş durumda. Loki ise Grafana Labs tarafından geliştirilen, Prometheus’tan ilham alan bir log aggregation sistemi. Elasticsearch’e kıyasla çok daha az kaynak tüketiyor çünkü log içeriklerini full-text index’lemiyor, sadece metadata’yı index’liyor.

Caddy’yi bu karışıma dahil etmemizin birkaç güçlü nedeni var:

  • Otomatik TLS: Let’s Encrypt entegrasyonu sayesinde sertifika yönetimine vakit harcamazsınız
  • Sade konfigürasyon: Nginx’e kıyasla Caddyfile çok daha okunabilir
  • Güvenli varsayılanlar: HTTP/2, modern TLS ayarları kutudan çıkar çıkmaz aktif
  • Düşük kaynak tüketimi: Go ile yazılmış olması bellek açısından verimli

Sistem Mimarisi

Kuracağımız yapıda şu akış söz konusu:

  • Promtail (veya Alloy) log’ları toplayıp Loki’ye gönderiyor
  • Loki log’ları depolayıp sorgulama API’si sunuyor
  • Grafana, Loki’yi datasource olarak kullanıp görselleştiriyor
  • Caddy tüm bunların önünde reverse proxy olarak duruyor, SSL sonlandırıyor

Ön Gereksinimler

Sunucuda şunların kurulu olduğunu varsayıyorum:

  • Docker ve Docker Compose (v2+)
  • Bir domain adı (örnek: monitoring.example.com)
  • Ubuntu 22.04 veya Debian 12
  • En az 2GB RAM (Loki ve Grafana için)

Docker Compose ile Temel Yapının Kurulumu

Önce proje dizinimizi oluşturalım:

mkdir -p /opt/monitoring/{caddy,grafana,loki,promtail}
mkdir -p /opt/monitoring/caddy/{config,data}
mkdir -p /opt/monitoring/loki/{data,config}
mkdir -p /opt/monitoring/grafana/{data,provisioning}
cd /opt/monitoring

Şimdi ana docker-compose.yml dosyamızı yazalım:

cat > /opt/monitoring/docker-compose.yml << 'EOF'
version: '3.8'

networks:
  monitoring:
    driver: bridge
  caddy_net:
    driver: bridge

volumes:
  loki_data:
  grafana_data:
  caddy_data:
  caddy_config:

services:
  caddy:
    image: caddy:2.7-alpine
    container_name: caddy
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
      - "443:443/udp"
    volumes:
      - ./caddy/Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy_data:/data
      - caddy_config:/config
    networks:
      - caddy_net
    depends_on:
      - grafana

  grafana:
    image: grafana/grafana:10.2.0
    container_name: grafana
    restart: unless-stopped
    environment:
      - GF_SECURITY_ADMIN_USER=admin
      - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD:-changeme123}
      - GF_SERVER_ROOT_URL=https://monitoring.example.com
      - GF_SERVER_SERVE_FROM_SUB_PATH=false
      - GF_ANALYTICS_REPORTING_ENABLED=false
      - GF_SECURITY_DISABLE_GRAVATAR=true
    volumes:
      - grafana_data:/var/lib/grafana
      - ./grafana/provisioning:/etc/grafana/provisioning
    networks:
      - monitoring
      - caddy_net
    depends_on:
      - loki

  loki:
    image: grafana/loki:2.9.0
    container_name: loki
    restart: unless-stopped
    volumes:
      - ./loki/config/loki-config.yml:/etc/loki/local-config.yaml:ro
      - loki_data:/loki
    command: -config.file=/etc/loki/local-config.yaml
    networks:
      - monitoring

  promtail:
    image: grafana/promtail:2.9.0
    container_name: promtail
    restart: unless-stopped
    volumes:
      - ./promtail/promtail-config.yml:/etc/promtail/config.yml:ro
      - /var/log:/var/log:ro
      - /var/lib/docker/containers:/var/lib/docker/containers:ro
    command: -config.file=/etc/promtail/config.yml
    networks:
      - monitoring
    depends_on:
      - loki
EOF

Loki Yapılandırması

Loki için minimal ama production-ready bir konfigürasyon yazalım:

cat > /opt/monitoring/loki/config/loki-config.yml << 'EOF'
auth_enabled: false

server:
  http_listen_port: 3100
  grpc_listen_port: 9096
  log_level: warn

common:
  instance_addr: 127.0.0.1
  path_prefix: /loki
  storage:
    filesystem:
      chunks_directory: /loki/chunks
      rules_directory: /loki/rules
  replication_factor: 1
  ring:
    kvstore:
      store: inmemory

query_range:
  results_cache:
    cache:
      embedded_cache:
        enabled: true
        max_size_mb: 100

schema_config:
  configs:
    - from: 2020-10-24
      store: tsdb
      object_store: filesystem
      schema: v12
      index:
        prefix: index_
        period: 24h

ruler:
  alertmanager_url: http://localhost:9093

limits_config:
  retention_period: 744h
  ingestion_rate_mb: 16
  ingestion_burst_size_mb: 32

compactor:
  working_directory: /loki/retention
  shared_store: filesystem
  retention_enabled: true
  retention_delete_delay: 2h
  retention_delete_worker_count: 150
EOF

Burada retention_period: 744h ile logları 31 gün saklıyoruz. Disk kapasitene göre bunu ayarlaman gerekiyor.

Promtail Yapılandırması

Promtail’i hem sistem loglarını hem de Docker container loglarını toplayacak şekilde ayarlayalım:

cat > /opt/monitoring/promtail/promtail-config.yml << 'EOF'
server:
  http_listen_port: 9080
  grpc_listen_port: 0

positions:
  filename: /tmp/positions.yaml

clients:
  - url: http://loki:3100/loki/api/v1/push

scrape_configs:
  - job_name: system
    static_configs:
      - targets:
          - localhost
        labels:
          job: varlogs
          host: monitoring-server
          __path__: /var/log/*.log

  - job_name: docker
    docker_sd_configs:
      - host: unix:///var/run/docker.sock
        refresh_interval: 5s
        filters:
          - name: status
            values: ["running"]
    relabel_configs:
      - source_labels: ['__meta_docker_container_name']
        regex: '/(.*)'
        target_label: 'container'
      - source_labels: ['__meta_docker_container_log_stream']
        target_label: 'logstream'
      - source_labels: ['__meta_docker_container_label_com_docker_compose_service']
        target_label: 'service'

  - job_name: caddy_access
    static_configs:
      - targets:
          - localhost
        labels:
          job: caddy_access
          host: monitoring-server
          __path__: /var/log/caddy/*.log
EOF

Docker socket’a erişim için promtail container’ına /var/run/docker.sock mount etmek gerekiyor. docker-compose.yml‘e şunu eklemeyi unutma:

# promtail servisine ekle:
    volumes:
      - ./promtail/promtail-config.yml:/etc/promtail/config.yml:ro
      - /var/log:/var/log:ro
      - /var/lib/docker/containers:/var/lib/docker/containers:ro
      - /var/run/docker.sock:/var/run/docker.sock:ro

Caddy ile Reverse Proxy Yapılandırması

İşte asıl sihir burada başlıyor. Caddyfile’ı production ortamı için yazalım:

cat > /opt/monitoring/caddy/Caddyfile << 'EOF'
{
  email [email protected]
  admin off
}

monitoring.example.com {
  # Temel güvenlik başlıkları
  header {
    X-Content-Type-Options nosniff
    X-Frame-Options DENY
    X-XSS-Protection "1; mode=block"
    Referrer-Policy strict-origin-when-cross-origin
    -Server
  }

  # Grafana'ya proxy
  reverse_proxy grafana:3000 {
    header_up Host {upstream_hostport}
    header_up X-Real-IP {remote_host}
    header_up X-Forwarded-For {remote_host}
    header_up X-Forwarded-Proto {scheme}
  }

  # Access log'larini JSON formatinda tut
  log {
    output file /var/log/caddy/access.log {
      roll_size 10mb
      roll_keep 5
      roll_keep_for 720h
    }
    format json
  }
}

# Loki API'yi sadece iç ağdan veya VPN'den erişime aç
loki.example.com {
  # IP bazlı kısıtlama - sadece guvenilen IP'ler
  @blocked not remote_ip 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16
  respond @blocked "Access Denied" 403

  reverse_proxy loki:3100 {
    header_up Host {upstream_hostport}
  }

  log {
    output file /var/log/caddy/loki-access.log {
      roll_size 5mb
      roll_keep 3
    }
    format json
  }
}
EOF

Eğer domain’in yoksa ya da local test ortamı kuruyorsan, localhost üzerinde şu şekilde çalışabilirsin:

cat > /opt/monitoring/caddy/Caddyfile << 'EOF'
{
  admin off
}

:8080 {
  reverse_proxy grafana:3000
  
  log {
    output stdout
    format console
  }
}

:8081 {
  @internal remote_ip 127.0.0.1 ::1
  handle @internal {
    reverse_proxy loki:3100
  }
  respond "Forbidden" 403
}
EOF

Grafana Provisioning ile Otomatik Datasource

Grafana’yı başlattığında Loki datasource’unu otomatik eklemek için provisioning kullanabiliriz:

mkdir -p /opt/monitoring/grafana/provisioning/datasources

cat > /opt/monitoring/grafana/provisioning/datasources/loki.yml << 'EOF'
apiVersion: 1

datasources:
  - name: Loki
    type: loki
    access: proxy
    url: http://loki:3100
    isDefault: true
    version: 1
    editable: false
    jsonData:
      maxLines: 1000
      derivedFields:
        - datasourceUid: loki
          matcherRegex: "(?:traceID|trace_id)=(\w+)"
          name: TraceID
          url: '$${__value.raw}'
EOF

Sistemi Ayağa Kaldırma

cd /opt/monitoring

# .env dosyasi olustur
cat > .env << 'EOF'
GRAFANA_ADMIN_PASSWORD=super_secret_password_123
EOF

# Dizin izinlerini ayarla
chmod 600 .env
chown -R 472:472 /opt/monitoring/grafana/data 2>/dev/null || true

# Servisleri baslat
docker compose up -d

# Logları takip et
docker compose logs -f --tail=50

Birkaç dakika sonra şu komutla sağlık durumunu kontrol edebilirsin:

# Loki health check
curl -s http://localhost:3100/ready

# Container durumları
docker compose ps

# Loki'ye log gönderilip gönderilmediğini kontrol et
curl -s "http://localhost:3100/loki/api/v1/labels" | python3 -m json.tool

Grafana’da LogQL Sorguları

Sistem ayağa kalktıktan sonra Grafana’ya giriş yapıp Explore ekranından Loki datasource’unu seçerek log sorgularını çalıştırabilirsin. İşte işe yarar birkaç LogQL örneği:

Caddy access loglarından 5xx hatalarını bul:

{job="caddy_access"} | json | status >= 500

Belirli bir container’ın loglarını filtrele:

{container="grafana"} |= "error" | json

Son 1 saatteki hata oranını hesapla:

sum(rate({job="varlogs"} |= "error" [5m])) by (host)

Gerçek Dünya Senaryosu: Caddy Log Analizi

Diyelim ki production sunucunda 502 hataları aldığını fark ettin. Grafana’da şu sorgu ile hızlıca analiz yapabilirsin:

# Once hangi endpoint'lerin problem cikardığını bul
{job="caddy_access"} 
  | json 
  | status == 502 
  | line_format "{{.request_uri}} - {{.upstream_host}} - {{.duration}}"

Bu sorgu sana hem hangi upstream’in sorun çıkardığını hem de ne kadar süre gecikmeler yaşandığını gösterir. Burada Caddy’nin JSON formatındaki access logları kritik önem taşıyor. Nginx’in default log formatı ile kıyaslandığında, Caddy’nin JSON logları doğrudan parse edilebiliyor, regex yazmana gerek kalmıyor.

Alerting: Kritik Loglar için Bildirim

Grafana Alerting ile belirli log pattern’leri için alert oluşturabiliriz. Örneğin authentication failure’ları için:

# Grafana alert rule provisioning
mkdir -p /opt/monitoring/grafana/provisioning/alerting

cat > /opt/monitoring/grafana/provisioning/alerting/rules.yml << 'EOF'
apiVersion: 1

groups:
  - orgId: 1
    name: security_alerts
    folder: Security
    interval: 1m
    rules:
      - uid: auth_failure_alert
        title: High Auth Failure Rate
        condition: C
        data:
          - refId: A
            queryType: range
            relativeTimeRange:
              from: 300
              to: 0
            model:
              expr: 'sum(count_over_time({job="varlogs"} |= "authentication failure" [5m]))'
              legendFormat: auth_failures
          - refId: C
            queryType: ''
            relativeTimeRange:
              from: 300
              to: 0
            model:
              conditions:
                - evaluator:
                    params:
                      - 10
                    type: gt
                  operator:
                    type: and
                  query:
                    params:
                      - C
                  reducer:
                    type: last
              type: classic_conditions
        noDataState: NoData
        execErrState: Error
        for: 2m
        annotations:
          summary: "5 dakikada 10'dan fazla authentication failure tespit edildi"
EOF

Performans ve Güvenlik İpuçları

Produksiyon ortamında dikkat etmen gereken birkaç kritik nokta var:

  • Loki retention ayarları: Disk dolduğunda Loki yazmayı durdurur. retention_period değerini mevcut disk kapasitene göre hesapla. 100GB disk için 30 günlük retention genellikle güvenli bir başlangıç noktası.
  • Caddy buffer ayarları: Büyük log dosyaları için Caddyfile’a flush_interval -1 ekleyerek streaming proxy modunu aktifleştir.
  • Grafana anonymous access: Eğer internal bir monitoring sistemiyse ve ekip ile paylaşmak istiyorsan, GF_AUTH_ANONYMOUS_ENABLED=true environment variable’ı ile anonim okuma erişimi açabilirsin, ama bunu dikkatli kullan.
  • Loki chunk cache: Yoğun log ortamlarında Redis kullanarak Loki’nin chunk cache’ini dışarıya taşı. Bu sorgu performansını ciddi ölçüde artırır.
  • Caddy log rotation: roll_size 10mb ve roll_keep 5 ayarları ile Caddy’nin kendi loglarının kontrolden çıkmasını engelle.
  • Network segmentation: Loki’nin portunu (3100) direkt internete açma. Yukarıdaki Caddyfile örneğinde gösterildiği gibi IP bazlı kısıtlama kullan ya da VPN arkasında tut.

Bakım ve Güncelleme

Docker imajlarını güvenli şekilde güncellemek için:

cd /opt/monitoring

# Mevcut imajları kaydet (rollback icin)
docker compose images

# Yeni imajları cek
docker compose pull

# Sirayla guncelle (downtime minimize)
docker compose up -d --no-deps loki
sleep 10
docker compose up -d --no-deps grafana
docker compose up -d --no-deps caddy

# Eski imajlari temizle
docker image prune -f

Loki’de şema değişikliklerinde dikkatli ol. Majör versiyon atlarken her zaman önce loki/data dizinini yedekle:

tar -czf /backup/loki-data-$(date +%Y%m%d).tar.gz /opt/monitoring/loki/data

Sorun Giderme

En sık karşılaşılan sorunlar ve çözümleri:

  • Grafana “datasource not found” hatası: docker compose restart grafana ile provisioning’in yeniden yüklenmesini sağla. Container IP’lerinin değişip değişmediğini docker network inspect monitoring_monitoring ile kontrol et.
  • Loki “too many outstanding requests” hatası: limits_config altında max_query_parallelism: 32 değerini düşür.
  • Caddy otomatik TLS çalışmıyor: DNS’in doğru IP’yi gösterdiğinden emin ol. docker compose logs caddy çıktısında ACME challenge hatalarını ara.
  • Promtail logları Loki’ye ulaşmıyor: curl http://promtail:9080/metrics ile scrape durumunu kontrol et. promtail_sent_entries_total counter’ının arttığını doğrula.

Sonuç

Caddy, Grafana ve Loki üçlüsü kurulum kolaylığı ve düşük kaynak tüketimi açısından gerçekten güçlü bir kombinasyon. Özellikle Caddy’nin otomatik TLS ve temiz konfigürasyon sözdizimi, bu stack’i küçük ve orta ölçekli altyapılar için ideal yapıyor. Büyük ölçekli ortamlarda Loki’yi distributed modda çalıştırıp S3 veya GCS’e log yazmasını sağlayabilirsin, bu konfigürasyonu bir sonraki yazıda ele alacağım.

Şu anda Nginx veya Traefik kullanıyorsan ve benzer bir log izleme sistemi kurmayı düşünüyorsan, Caddy’ye geçiş öğrenme eğrisi açısından çok daha hızlı ve sertifika yönetimi derdi olmadan ilerleyebilirsin. Üstelik Grafana’nın son versiyonlarında Loki entegrasyonu çok olgunlaştı; dashboardları direkt Grafana.com’dan import ederek dakikalar içinde anlamlı görselleştirmeler elde edebilirsin.

Yorum yapın