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_perioddeğ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 -1ekleyerek streaming proxy modunu aktifleştir.
- Grafana anonymous access: Eğer internal bir monitoring sistemiyse ve ekip ile paylaşmak istiyorsan,
GF_AUTH_ANONYMOUS_ENABLED=trueenvironment 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 10mbveroll_keep 5ayarları 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 grafanaile provisioning’in yeniden yüklenmesini sağla. Container IP’lerinin değişip değişmediğinidocker network inspect monitoring_monitoringile kontrol et.
- Loki “too many outstanding requests” hatası:
limits_configaltındamax_query_parallelism: 32değ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/metricsile scrape durumunu kontrol et.promtail_sent_entries_totalcounter’ı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.