Monitoring ve Logging — Prometheus, Grafana, ELK
Production'a deploy ettik, best practice'leri uyguladık. Ama şimdi çok önemli bir soru var: uygulamanın gerçekten sağlıklı çalışıp çalışmadığını nasıl bileceksin? Container'ların CPU ve memory kullanımı normal mi? Hata oranı artıyor mu? Disk doluyor mu? Bu derste monitoring (izleme), logging (kayıt tutma) ve alerting (uyarı) sistemlerini kurarak bu soruların cevaplarını otomatik olarak almanı sağlayacağız.
Bir hastanede yoğun bakım ünitesini düşün. Her hastanın başında monitörler var — kalp atış hızı, tansiyon, oksijen seviyesi sürekli izleniyor. Bir değer anormal olduğunda alarm çalıyor ve doktorlar hemen müdahale ediyor. Ayrıca hastanın tüm tıbbi geçmişi dosyalanıyor — ne zaman ne oldu, hangi ilaç verildi, ne tepki verdi.
Docker dünyasında monitoring (izleme) bu monitörler, logging (kayıt tutma) bu hasta dosyalarıdır. Container'larının CPU kullanımı, bellek tüketimi, ağ trafiği sürekli izlenmeli. Uygulama logları toplanmalı, aranabilir olmalı, analiz edilebilmeli. Monitoring olmadan production'a çıkmak, gösterge paneli olmadan araba sürmek gibidir — ne hızla gittiğini, yakıtın ne kadar kaldığını bilmiyorsun.
Monitoring vs Logging vs Tracing
| Kavram | Ne İzler? | Araçlar | Soru Cevabı |
|---|---|---|---|
| Monitoring | Metrikler (CPU, RAM, istek sayısı) | Prometheus, Grafana | "Sistem sağlıklı mı?" |
| Logging | Olay kayıtları (hata, uyarı, bilgi) | ELK, Loki, Fluentd | "Ne oldu?" |
| Tracing | İstek yolculuğu (servisler arası) | Jaeger, Zipkin, Tempo | "Nerede yavaşladı?" |
Bu üçüne birlikte Observability (gözlemlenebilirlik) denir. Production'da üçü de olmalı.
Prometheus — Metrik Toplama
Prometheus, pull-based bir monitoring sistemidir. Container'lardan metrikleri çeker, depolar ve sorgulanmasını sağlar.
Prometheus Nasıl Çalışır?
┌──────────────────────────────────────────────────────────┐
│ Prometheus │
│ │
│ ┌─────────┐ ┌─────────────┐ ┌──────────────────┐ │
│ │ Scraper │───→│ Time Series │───→│ PromQL Query │ │
│ │ (pull) │ │ Database │ │ Engine │ │
│ └────┬─────┘ └─────────────┘ └────────┬─────────┘ │
│ │ │ │
│ Targets'ları API / Grafana │
│ periyodik olarak │
│ scrape eder │
└───┬───────────────────────────────────────────────────────┘
│
├──── cAdvisor (/metrics) → Container metrikleri
├──── Node Exporter (/metrics) → Host metrikleri
├──── App (/metrics) → Uygulama metrikleri
└──── Alertmanager → Alarm yönetimiProduction-Ready Monitoring Stack
# docker-compose.monitoring.yml
services:
# === Prometheus — Metrik toplama ===
prometheus:
image: prom/prometheus:v2.49.0
volumes:
- ./monitoring/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
- ./monitoring/prometheus/alerts.yml:/etc/prometheus/alerts.yml:ro
- prometheus-data:/prometheus
ports:
- "9090:9090"
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.retention.time=30d'
- '--storage.tsdb.retention.size=10GB'
- '--web.enable-lifecycle' # HTTP ile config reload
networks:
- monitoring
restart: unless-stopped
# === Grafana — Dashboard ve Görselleştirme ===
grafana:
image: grafana/grafana:10.3.0
volumes:
- grafana-data:/var/lib/grafana
- ./monitoring/grafana/provisioning:/etc/grafana/provisioning:ro
- ./monitoring/grafana/dashboards:/var/lib/grafana/dashboards:ro
ports:
- "3000:3000"
environment:
GF_SECURITY_ADMIN_USER: admin
GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_PASSWORD:-admin}
GF_USERS_ALLOW_SIGN_UP: "false"
GF_SERVER_ROOT_URL: https://grafana.example.com
networks:
- monitoring
restart: unless-stopped
# === cAdvisor — Container metrikleri ===
cadvisor:
image: gcr.io/cadvisor/cadvisor:v0.49.1
volumes:
- /:/rootfs:ro
- /var/run:/var/run:ro
- /sys:/sys:ro
- /var/lib/docker/:/var/lib/docker:ro
- /dev/disk/:/dev/disk:ro
ports:
- "8080:8080"
privileged: true
devices:
- /dev/kmsg
networks:
- monitoring
restart: unless-stopped
# === Node Exporter — Host metrikleri ===
node-exporter:
image: prom/node-exporter:v1.7.0
volumes:
- /proc:/host/proc:ro
- /sys:/host/sys:ro
- /:/rootfs:ro
command:
- '--path.procfs=/host/proc'
- '--path.sysfs=/host/sys'
- '--path.rootfs=/rootfs'
- '--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)'
ports:
- "9100:9100"
networks:
- monitoring
restart: unless-stopped
# === Alertmanager — Alarm yönetimi ===
alertmanager:
image: prom/alertmanager:v0.27.0
volumes:
- ./monitoring/alertmanager/alertmanager.yml:/etc/alertmanager/alertmanager.yml:ro
ports:
- "9093:9093"
networks:
- monitoring
restart: unless-stopped
volumes:
prometheus-data:
grafana-data:
networks:
monitoring:
driver: bridgePrometheus Konfigürasyonu
# monitoring/prometheus/prometheus.yml
global:
scrape_interval: 15s
evaluation_interval: 15s
# Alarm kuralları
rule_files:
- "alerts.yml"
# Alertmanager bağlantısı
alerting:
alertmanagers:
- static_configs:
- targets: ['alertmanager:9093']
# Scrape hedefleri
scrape_configs:
# Prometheus kendisi
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
# Container metrikleri (cAdvisor)
- job_name: 'cadvisor'
scrape_interval: 10s
static_configs:
- targets: ['cadvisor:8080']
# Host metrikleri
- job_name: 'node-exporter'
static_configs:
- targets: ['node-exporter:9100']
# Docker Engine metrikleri
- job_name: 'docker'
static_configs:
- targets: ['host.docker.internal:9323']
# Uygulama metrikleri
- job_name: 'api'
metrics_path: '/metrics'
static_configs:
- targets: ['api:3000']
scrape_interval: 10sAlarm Kuralları
# monitoring/prometheus/alerts.yml
groups:
- name: container_alerts
rules:
# Container çok fazla CPU kullanıyor
- alert: HighCPUUsage
expr: rate(container_cpu_usage_seconds_total{name!=""}[5m]) * 100 > 80
for: 5m
labels:
severity: warning
annotations:
summary: "Container {{ $labels.name }} yüksek CPU kullanımı"
description: "CPU kullanımı %{{ $value | printf \"%.1f\" }} (5 dakikadır > %80)"
# Container bellek limiti yaklaşıyor
- alert: HighMemoryUsage
expr: container_memory_usage_bytes{name!=""} / container_spec_memory_limit_bytes{name!=""} * 100 > 85
for: 5m
labels:
severity: critical
annotations:
summary: "Container {{ $labels.name }} bellek limiti yaklaşıyor"
description: "Bellek kullanımı %{{ $value | printf \"%.1f\" }}"
# Container restart döngüsünde
- alert: ContainerRestartLoop
expr: rate(container_last_seen{name!=""}[5m]) > 3
for: 10m
labels:
severity: critical
annotations:
summary: "Container {{ $labels.name }} sürekli restart ediyor"
- name: host_alerts
rules:
# Disk alanı azalıyor
- alert: DiskSpaceLow
expr: (node_filesystem_avail_bytes{mountpoint="/"} / node_filesystem_size_bytes{mountpoint="/"}) * 100 < 15
for: 5m
labels:
severity: warning
annotations:
summary: "Disk alanı azalıyor"
description: "Kalan alan: %{{ $value | printf \"%.1f\" }}"
# Host bellek azalıyor
- alert: HostMemoryLow
expr: (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100 < 10
for: 5m
labels:
severity: critical
annotations:
summary: "Host bellek kritik seviyede"Alertmanager Konfigürasyonu
# monitoring/alertmanager/alertmanager.yml
global:
resolve_timeout: 5m
route:
group_by: ['alertname', 'severity']
group_wait: 30s
group_interval: 5m
repeat_interval: 4h
receiver: 'slack-notifications'
routes:
- match:
severity: critical
receiver: 'slack-critical'
repeat_interval: 1h
receivers:
- name: 'slack-notifications'
slack_configs:
- api_url: 'https://hooks.slack.com/services/xxx/yyy/zzz'
channel: '#monitoring'
title: '{{ .GroupLabels.alertname }}'
text: '{{ .CommonAnnotations.description }}'
- name: 'slack-critical'
slack_configs:
- api_url: 'https://hooks.slack.com/services/xxx/yyy/zzz'
channel: '#critical-alerts'
title: '🔥 CRITICAL: {{ .GroupLabels.alertname }}'
text: '{{ .CommonAnnotations.description }}'Temel PromQL Sorguları
# Container CPU kullanımı (%)
rate(container_cpu_usage_seconds_total{name!=""}[5m]) * 100
# Container bellek kullanımı (MB)
container_memory_usage_bytes{name!=""} / 1024 / 1024
# Container network I/O (bytes/sec)
rate(container_network_receive_bytes_total{name!=""}[5m])
rate(container_network_transmit_bytes_total{name!=""}[5m])
# Container restart sayısı
increase(container_restart_count{name!=""}[1h])
# HTTP istek hızı (uygulama metriği)
rate(http_requests_total[5m])
# HTTP hata oranı
rate(http_requests_total{status=~"5.."}[5m]) / rate(http_requests_total[5m]) * 100
# Response time 95. yüzdelik (percentile)
histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m]))Uygulama Metrikleri — Custom Metrics
Uygulamanın kendi metriklerini Prometheus'a expose etmelisin.
Node.js Örneği
// src/metrics.js
const client = require('prom-client');
// Default metrikleri topla (CPU, memory, event loop)
client.collectDefaultMetrics({ prefix: 'myapp_' });
// Custom metrikler
const httpRequestCounter = new client.Counter({
name: 'http_requests_total',
help: 'Total HTTP requests',
labelNames: ['method', 'path', 'status'],
});
const httpRequestDuration = new client.Histogram({
name: 'http_request_duration_seconds',
help: 'HTTP request duration in seconds',
labelNames: ['method', 'path'],
buckets: [0.01, 0.05, 0.1, 0.5, 1, 2, 5],
});
const activeConnections = new client.Gauge({
name: 'active_connections',
help: 'Number of active connections',
});
// Middleware
function metricsMiddleware(req, res, next) {
const start = Date.now();
activeConnections.inc();
res.on('finish', () => {
const duration = (Date.now() - start) / 1000;
httpRequestCounter.inc({
method: req.method,
path: req.route?.path || req.path,
status: res.statusCode,
});
httpRequestDuration.observe(
{ method: req.method, path: req.route?.path || req.path },
duration
);
activeConnections.dec();
});
next();
}
// /metrics endpoint
async function metricsHandler(req, res) {
res.set('Content-Type', client.register.contentType);
res.end(await client.register.metrics());
}
module.exports = { metricsMiddleware, metricsHandler };// src/app.js
const express = require('express');
const { metricsMiddleware, metricsHandler } = require('./metrics');
const app = express();
app.use(metricsMiddleware);
app.get('/metrics', metricsHandler);
// ... diğer route'larGrafana — Dashboard ve Görselleştirme
Datasource Provisioning
# monitoring/grafana/provisioning/datasources/prometheus.yml
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
access: proxy
url: http://prometheus:9090
isDefault: true
editable: false
- name: Loki
type: loki
access: proxy
url: http://loki:3100
editable: falseDashboard Provisioning
# monitoring/grafana/provisioning/dashboards/dashboard.yml
apiVersion: 1
providers:
- name: 'Docker'
orgId: 1
folder: 'Docker'
type: file
disableDeletion: true
editable: false
options:
path: /var/lib/grafana/dashboards
foldersFromFilesStructure: trueÖnemli Grafana Dashboard'ları
# Docker container monitoring
# Dashboard ID: 893 (Docker and system monitoring)
# Dashboard ID: 14282 (cAdvisor Full)
# Node Exporter
# Dashboard ID: 1860 (Node Exporter Full)
# Grafana'da import:
# + → Import → Dashboard ID gir → Load → Prometheus datasource seç → ImportLogging — Centralized Log Yönetimi
Seçenek 1: Loki + Grafana (Hafif — Önerilen)
Loki, Grafana'nın log toplama aracıdır. Prometheus'un log versiyonu gibi düşün — hafif, verimli, Grafana ile entegre.
# docker-compose.logging.yml
services:
loki:
image: grafana/loki:2.9.4
volumes:
- ./monitoring/loki/loki-config.yml:/etc/loki/local-config.yaml:ro
- loki-data:/loki
ports:
- "3100:3100"
command: -config.file=/etc/loki/local-config.yaml
networks:
- monitoring
restart: unless-stopped
promtail:
image: grafana/promtail:2.9.4
volumes:
- ./monitoring/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
command: -config.file=/etc/promtail/config.yml
networks:
- monitoring
restart: unless-stopped
volumes:
loki-data:# monitoring/loki/loki-config.yml
auth_enabled: false
server:
http_listen_port: 3100
common:
path_prefix: /loki
storage:
filesystem:
chunks_directory: /loki/chunks
rules_directory: /loki/rules
replication_factor: 1
ring:
kvstore:
store: inmemory
schema_config:
configs:
- from: 2020-10-24
store: tsdb
object_store: filesystem
schema: v12
index:
prefix: index_
period: 24h
limits_config:
retention_period: 30d# monitoring/promtail/promtail-config.yml
server:
http_listen_port: 9080
positions:
filename: /tmp/positions.yaml
clients:
- url: http://loki:3100/loki/api/v1/push
scrape_configs:
# Docker container logları
- job_name: docker
docker_sd_configs:
- host: unix:///var/run/docker.sock
refresh_interval: 5s
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'Seçenek 2: ELK Stack (Ağır — Büyük Ölçek)
# docker-compose.elk.yml
services:
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:8.12.0
environment:
- discovery.type=single-node
- xpack.security.enabled=false
- "ES_JAVA_OPTS=-Xms1g -Xmx1g"
volumes:
- elasticsearch-data:/usr/share/elasticsearch/data
ports:
- "9200:9200"
deploy:
resources:
limits:
memory: 2G
networks:
- monitoring
restart: unless-stopped
logstash:
image: docker.elastic.co/logstash/logstash:8.12.0
volumes:
- ./monitoring/logstash/pipeline:/usr/share/logstash/pipeline:ro
ports:
- "5044:5044" # Beats input
- "12201:12201/udp" # GELF input (Docker log driver)
environment:
LS_JAVA_OPTS: "-Xms256m -Xmx256m"
networks:
- monitoring
restart: unless-stopped
kibana:
image: docker.elastic.co/kibana/kibana:8.12.0
ports:
- "5601:5601"
environment:
ELASTICSEARCH_HOSTS: '["http://elasticsearch:9200"]'
depends_on:
- elasticsearch
networks:
- monitoring
restart: unless-stopped
volumes:
elasticsearch-data:# monitoring/logstash/pipeline/logstash.conf
input {
gelf {
port => 12201
type => "docker"
}
}
filter {
# JSON logları parse et
if [message] =~ /^\{/ {
json {
source => "message"
target => "parsed"
}
}
# Timestamp düzelt
date {
match => [ "[parsed][timestamp]", "ISO8601" ]
}
}
output {
elasticsearch {
hosts => ["elasticsearch:9200"]
index => "docker-logs-%{+YYYY.MM.dd}"
}
}# Docker daemon'ı GELF log driver ile yapılandır
# Container logları otomatik olarak Logstash'e gider
services:
api:
image: myapp:v1.0
logging:
driver: gelf
options:
gelf-address: "udp://localhost:12201"
tag: "api"Loki vs ELK Karşılaştırması
| Özellik | Loki + Grafana | ELK Stack |
|---|---|---|
| Kaynak tüketimi | Düşük (~512MB) | Yüksek (2-4GB+) |
| Kurulum | Basit | Karmaşık |
| Full-text search | Label bazlı (sınırlı) | Tam metin arama ✅ |
| Maliyet | Düşük | Yüksek (disk + RAM) |
| Ölçeklenebilirlik | İyi | Çok iyi |
| Grafana entegrasyon | Native ✅ | Plugin ile |
| Öğrenme eğrisi | Düşük | Orta-yüksek |
| En iyi kullanım | Küçük-orta projeler | Büyük ölçek, karmaşık arama |
💡 İpucu: Çoğu proje için Loki + Grafana yeterli ve çok daha az kaynak tüketir. ELK'ya sadece full-text search kritikse veya çok büyük ölçekte log işliyorsan geç.
Docker Daemon Metrics
// /etc/docker/daemon.json
{
"metrics-addr": "0.0.0.0:9323",
"experimental": true,
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}# Daemon restart
sudo systemctl restart docker
# Metrikleri kontrol et
curl http://localhost:9323/metricsStructured Logging — JSON Log Formatı
Loglarını JSON formatında yaz — parse edilebilir, filtrelenebilir, analiz edilebilir.
// ❌ Düz text log — parse edilemez
console.log('User 123 logged in from 192.168.1.1');
// ✅ Structured JSON log — her alan aranabilir
const log = {
timestamp: new Date().toISOString(),
level: 'info',
message: 'User logged in',
userId: 123,
ip: '192.168.1.1',
userAgent: 'Mozilla/5.0...',
requestId: 'req-abc-123',
duration: 45
};
console.log(JSON.stringify(log));# Python — structlog kullanımı
import structlog
logger = structlog.get_logger()
logger.info(
"user_logged_in",
user_id=123,
ip="192.168.1.1",
duration=45
)
# Çıktı: {"event": "user_logged_in", "user_id": 123, "ip": "192.168.1.1", ...}Best Practices
✅ Yap:
Prometheus + Grafana + Loki üçlüsünü minimum monitoring stack olarak kur
cAdvisor ile container metriklerini, Node Exporter ile host metriklerini topla
Alerting kur — metrikler anormal olduğunda bildirim al (Slack, email, PagerDuty)
Uygulama custom metrikleri expose et (request rate, error rate, duration)
Structured (JSON) logging kullan — düz text log aranmaz
Log rotasyonu ayarla — disk dolar (
max-size,max-file)Dashboard'ları provisioning ile yönet (Git'te sakla, elle oluşturma)
Retention policy tanımla (30 gün metrik, 14 gün log gibi)
❌ Yapma:
Monitoring olmadan production'a çıkma — körlemesine uçuş
Her şeyi loglama — gereksiz log gürültü yaratır, disk doldurur
Alarm eşiklerini çok düşük tutma — alert fatigue (alarm yorgunluğu)
ELK Stack'i küçük projede kullanma — overkill, Loki yeterli
Log seviyelerini production'da DEBUG bırakma — INFO veya WARN yeterli
Grafana şifresini varsayılan (admin/admin) bırakma
Monitoring container'larına kaynak limiti koymayı unutma
Özet
Monitoring (Prometheus) metrikleri toplar, Logging (Loki/ELK) olayları kaydeder, Tracing (Jaeger) istek yolculuğunu takip eder
Prometheus + Grafana çifti endüstri standardı monitoring çözümüdür
cAdvisor container metriklerini, Node Exporter host metriklerini Prometheus'a sunar
Alertmanager ile metrik eşikleri aşıldığında otomatik bildirim (Slack, email)
Loki + Promtail hafif ve etkili log toplama — küçük-orta projeler için ideal
ELK Stack (Elasticsearch + Logstash + Kibana) büyük ölçek ve full-text arama için
Structured JSON logging ile logları aranabilir ve analiz edilebilir yap
Dashboard'ları Git'te sakla (provisioning) ve retention policy tanımla
AI Asistan
Sorularını yanıtlamaya hazır