Eksiksiz Observability (Gözlemlenebilirlik) Rehberi — Prometheus, Grafana, Loki & Jaeger

15 Ağustos 2026 · netologist · 15 dakika, 3060 kelime ·

“LGTM+P” yığınını (Loki, Grafana, Tempo/Jaeger, Prometheus) derinlemesine ele alan pratik bir rehber — mimari, sorgu dilleri, alerting, dashboard tasarımı, tracing ve production seviyesinde best practice’ler.


İçindekiler

  1. Observability’nin Temelleri
  2. Prometheus
  3. Grafana
  4. Loki
  5. Jaeger ve Distributed Tracing
  6. Metrik, Log ve Trace’leri İlişkilendirme
  7. SLO, SLI ve Error Budget
  8. Production Kontrol Listesi

1. Observability’nin Temelleri

1.1 Üç Sütun (Three Pillars)

SütunCevapladığı SoruAraç
Metrics (Metrikler)“Bir sorun var mı, ne kadar kötü?”Prometheus
Logs (Loglar)“Tam olarak ne oldu?”Loki
Traces (İzler)“Nerede oldu, hangi serviste?”Jaeger

Observability, monitoring (izleme) ile aynı şey değildir. Monitoring, bilinen hata senaryolarının gerçekleştiğini söyler; observability ise yeni kod yazmadan sistem davranışı hakkında keyfi/yeni sorular sormanıza olanak tanır.

1.2 RED ve USE Metodolojileri

RED (istek tabanlı servisler için):

USE (kaynaklar için — CPU, disk, memory, network):

Genel kural: RED’i servislere, USE’u altyapı kaynaklarına uygulayın.

1.3 Dört Altın Sinyal (Google SRE)

Latency (gecikme), Traffic (trafik), Errors (hatalar), Saturation (doygunluk) — esasen RED + Saturation.


2. Prometheus

2.1 Mimari

 ┌────────────┐   scrape (pull)   ┌──────────────┐
 │ Target/     │◄─────────────────│  Prometheus   │
 │ Exporter    │                  │  Server        │
 └────────────┘                  │  - TSDB        │
                                   │  - PromQL      │
                                   │  - Rule Engine │
                                   └──────┬─────────┘
                                          │ alert
                                   ┌──────▼─────────┐
                                   │  Alertmanager   │
                                   └────────────────┘

2.2 Veri Modeli

Her time series, bir metrik adı + key-value label çiftleri kümesiyle benzersiz şekilde tanımlanır:

http_requests_total{method="POST", handler="/api/orders", status="200"} 27

2.2.1 Dört Metrik Tipi

TipAçıklamaÖrnek
CounterMonoton olarak artan değer; restart’ta 0’a dönerhttp_requests_total
GaugeArtabilen veya azalabilen değernode_memory_available_bytes
HistogramGözlemleri yapılandırılabilir bucket’lara örnekler, _sum ve _count içerirhttp_request_duration_seconds
SummaryHistogram’a benzer ama quantile’ları (φ-quantile) client tarafında hesaplarrpc_duration_seconds

Histogram vs Summary — neredeyse her zaman histogram seçin:

2.3 PromQL Derinlemesine

Instant Vector vs Range Vector

http_requests_total                       # instant vector (anlık değer)
http_requests_total[5m]                   # range vector (son 5 dakikadaki tüm örnekler)

Rate, Increase, IRate

rate(http_requests_total[5m])       # 5dk pencere üzerinde saniye başına ortalama artış — ALERTING/DASHBOARD İÇİN KULLANIN
irate(http_requests_total[5m])      # sadece son iki nokta kullanarak saniyelik oran — dalgalı, sadece hızlı değişen grafiklerde kullanın
increase(http_requests_total[1h])   # 1 saatlik toplam artış (rate * süre, ekstrapole edilmiş)

Altın kural: counter’lara sum() ile agregasyon yapmadan önce her zaman rate()/irate() uygulayın. Ham counter’ları önce sum() edip sonra rate() almayın — bu, per-series reset tespitini bozar.

# DOĞRU
sum(rate(http_requests_total[5m])) by (service)

# YANLIŞ — counter reset yönetimini bozar
rate(sum(http_requests_total) by (service))[5m]

Agregasyon Operatörleri

sum(rate(http_requests_total[5m])) by (job)
avg(node_load1) by (instance)
max(up) by (job)
count(up == 0)                    # down olan target sayısı
topk(5, rate(http_requests_total[5m]))
bottomk(3, node_filesystem_free_bytes)

by belirtilen label’ları korur; without belirtilen label’ları kaldırıp geri kalan her şeyi tutar.

Histogram Quantile’ları

histogram_quantile(0.99,
  sum(rate(http_request_duration_seconds_bucket[5m])) by (le, service)
)

Binary Operatörler ve Vector Matching

# one-to-one: label kümeleri (metrik adı hariç) tam olarak eşleşmelidir
node_memory_MemFree_bytes / node_memory_MemTotal_bytes

# many-to-one, ignoring/group_left ile — metrikleri ekstra label ile zenginleştirme
sum(rate(http_requests_total[5m])) by (instance)
  * on(instance) group_left(datacenter) node_meta

# eşleştirme için belirli label'ları göz ardı etme
rate(a[5m]) / ignoring(instance) rate(b[5m])

Faydalı Fonksiyonlar

predict_linear(node_filesystem_free_bytes[6h], 4*3600)  # 4 saat sonra disk dolacak mı tahmini
deriv(some_gauge[10m])                                   # saniye başına türev
delta(cpu_temp_celsius[1h])                               # aralık üzerindeki fark (gauge'lar için)
absent(up{job="critical-service"})                        # metrik yoksa 1 döner — "sessiz hata" alert'leri için harika
changes(process_start_time_seconds[1h])                   # restart sayısı
resets(some_counter[1h])                                  # counter reset sayısı
label_replace(up, "svc", "$1", "job", "(.*)-prod")        # regex ile label manipülasyonu
clamp_min(my_metric, 0)

Subquery’ler

max_over_time(rate(http_requests_total[5m])[1h:1m])

Güçlü ama pahalıdır — dikkatli kullanın; tekrar eden ağır sorgular için recording rule tercih edin.

2.4 Service Discovery

Statik konfigürasyonlar ölçeklenmez. SD mekanizmalarını kullanın:

scrape_configs:
  - job_name: kubernetes-pods
    kubernetes_sd_configs:
      - role: pod
    relabel_configs:
      - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
        action: keep
        regex: true
      - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path]
        action: replace
        target_label: __metrics_path__
        regex: (.+)
      - source_labels: [__address__, __meta_kubernetes_pod_annotation_prometheus_io_port]
        action: replace
        regex: ([^:]+)(?::\d+)?;(\d+)
        replacement: $1:$2
        target_label: __address__

Desteklenen SD: Kubernetes, Consul, EC2, Azure, GCE, DNS, dosya tabanlı (file_sd_configs — özel envanterler için ideal), Docker, OpenStack.

relabel_configs scrape öncesinde çalışır (__address__‘ı değiştirebilir, target’ları düşürebilir); metric_relabel_configs scrape sonrasında çalışır (yüksek kardinaliteli metrik/label’ları düşürme/yeniden adlandırma).

2.5 Recording Rule’lar

Pahalı/sık kullanılan sorguları önceden hesaplayıp yeni time series’lere dönüştürün:

groups:
  - name: api_slos
    interval: 30s
    rules:
      - record: job:http_requests:rate5m
        expr: sum(rate(http_requests_total[5m])) by (job)
      - record: job:http_request_errors:rate5m
        expr: sum(rate(http_requests_total{status=~"5.."}[5m])) by (job)
      - record: job:http_error_ratio:rate5m
        expr: job:http_request_errors:rate5m / job:http_requests:rate5m

İsimlendirme kuralı: level:metric:operations (ör. job:http_errors:rate5m).

2.6 Alerting Rule’lar ve Alertmanager

groups:
  - name: availability
    rules:
      - alert: HighErrorRate
        expr: job:http_error_ratio:rate5m > 0.05
        for: 10m
        labels:
          severity: critical
        annotations:
          summary: "{{ $labels.job }} üzerinde yüksek hata oranı"
          description: "{{ $labels.job }} hata oranı {{ $value | humanizePercentage }} (eşik %5)"

      - alert: InstanceDown
        expr: up == 0
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "{{ $labels.instance }} down durumda"

Önemli pratikler:

Alertmanager, routing, gruplama, tekilleştirme (deduplication), susturma (silencing) ve inhibition işlemlerini yönetir:

route:
  receiver: default
  group_by: [alertname, cluster]
  group_wait: 30s
  group_interval: 5m
  repeat_interval: 4h
  routes:
    - match: {severity: critical}
      receiver: pagerduty
    - match: {severity: warning}
      receiver: slack

inhibit_rules:
  - source_match: {severity: critical}
    target_match: {severity: warning}
    equal: [alertname, cluster, service]

2.7 Exporter’lar

ExporterAmaç
node_exporterHost seviyesi metrikler (CPU, memory, disk, network)
blackbox_exporterHTTP/DNS/TCP/ICMP üzerinden probe — sentetik kontroller
kube-state-metricsKubernetes objelerinin durumu (deployment, pod vb — cAdvisor seviyesi kaynak kullanımı DEĞİL)
cAdvisorContainer kaynak kullanım metrikleri
mysqld_exporter / postgres_exporterVeritabanı metrikleri
redis_exporterRedis metrikleri

Genel amaçlı bir exporter ile sarmalamak yerine kendi uygulama kodunuzu bir client kütüphanesi (client_golang, client_python, Node için prom-client, Java için simpleclient) ile enstrümante etmeyi tercih edin — direkt enstrümantasyon iş (business) seviyesinde metrikler sağlar.

2.8 Kardinalite — Prometheus’un #1 Katili

Her benzersiz label-değer kombinasyonu yeni bir time series demektir. Yaygın kardinalite bombaları:

Genel kural: instance başına aktif seri sayısını en fazla tek haneli milyonlar seviyesinde tutun; metrik başına kardinalite tahmin edilebilir ve sınırlı olmalıdır. Denetim için count({__name__=~".+"}) ve topk(10, count by (__name__)({__name__=~".+"})) kullanın.

2.9 Federation, Remote Write ve Uzun Süreli Saklama


3. Grafana

3.1 Veri Kaynakları (Data Source)

Grafana, birçok backend’in üzerinde bir görselleştirme ve alerting katmanıdır: Prometheus, Loki, Jaeger/Tempo, Elasticsearch, InfluxDB, MySQL/Postgres, CloudWatch ve daha fazlası. UI üzerinden veya kod olarak yapılandırılabilir:

apiVersion: 1
datasources:
  - name: Prometheus
    type: prometheus
    access: proxy
    url: http://prometheus:9090
    isDefault: true
    jsonData:
      timeInterval: 30s
      exemplarTraceIdDestinations:
        - name: trace_id
          datasourceUid: jaeger-uid

3.2 Dashboard Tasarım Best Practice’leri

  1. Yukarıdan aşağıya düzen: özet satır (RED/USE özeti) → altında detaya inen satırlar.
  2. Bir dashboard, bir amaç. İlgisiz servisleri tek bir dashboard’a sıkıştırmayın.
  3. İkincil detay panelleri için satır (row) daraltmayı kullanın.
  4. Aynı tip panellerde tutarlı birim ve eşikler kullanın (gecikme her zaman s/ms, asla karışık olmasın).
  5. “Grafik çorbasından” kaçının: dashboard başına panel sayısını sınırlayın (~12–20); daha fazla panel eklemek yerine detay dashboard’a drill-down linkini tercih edin.
  6. Dashboard sorgularında sabit [5m] yazmak yerine $__rate_interval kullanın — seçilen zaman aralığına ve scrape interval’ına otomatik uyum sağlar.
  7. Deploy/olayları Annotations API ile işaretleyin, böylece dashboard’lar nedensel olarak ilgili işaretleri gösterir.

3.3 Değişkenler (Variables) ve Templating

Değişken: $datacenter   → label_values(up, datacenter)
Değişken: $instance     → label_values(up{datacenter="$datacenter"}, instance)
Değişken: $interval     → interval tipi: 1m,5m,10m,30m,1h

Multi-value + Include All seçeneklerini dikkatli kullanın — “All” genellikle büyük bir regex sorgusu üretir (=~"a|b|c|...") ve yavaş olabilir; önce kapsamı daraltmak için zincirlenmiş (chained) değişkenleri düşünün.

3.4 Kod Olarak Provisioning

Dashboard’ları JSON (veya Jsonnet/grafonnet) olarak git’te saklayın, şu şekilde provision edin:

apiVersion: 1
providers:
  - name: default
    folder: Platform
    type: file
    options:
      path: /etc/grafana/provisioning/dashboards

grafonnet/jsonnet, dashboard’ları şablonlaştırmanıza olanak tanır (servisler arasında DRY paneller) — JSON’u elle düzenlemek yerine ölçekte yaygın olarak kullanılır.

3.5 Unified Alerting

Legacy dashboard alert’lerinden farklı olarak, Grafana yönetimli alert kuralları herhangi bir veri kaynağını (sadece Prometheus değil) sorgulayabilir.

# Alert rule (kavramsal)
condition: B
data:
  - refId: A
    query: sum(rate(http_requests_total{status=~"5.."}[5m]))
  - refId: B
    reduceExpression: last(A)
    threshold: "> 10"
for: 5m

Bildirim politikaları, Alertmanager’ın ağaç tabanlı routing yapısını yansıtır (label matcher’ları → contact point’ler) ve zaten Prometheus çalıştırıyorsanız Grafana, harici bir Alertmanager’a proxy yapabilir.

3.6 Bilinmesi Gereken Panel Tipleri

3.7 Exemplar’lar

Exemplar’lar, belirli bir histogram gözlemine bir trace_id ekler ve latency grafiğinin üzerinde nokta olarak render edilir — tıklayarak doğrudan Jaeger/Tempo’daki trace’e geçebilirsiniz. Gereksinimler:

  1. Client kütüphane desteği (OpenMetrics exemplar formatı)
  2. Prometheus veri kaynağında yapılandırılmış exemplarTraceIdDestinations

4. Loki

4.1 Felsefe

Loki, Elasticsearch’ten farklı olarak sadece metadata’yı (label’ları) indeksler, tam log metnini değil. Bu, ölçekte çalıştırmayı çarpıcı biçimde ucuzlatır; bedeli ise full-text arama’nın daha yavaş olmasıdır (sorgu zamanında ters indeks yerine sıkıştırılmış chunk’lar üzerinde grep yapar).

“Prometheus gibi, ama loglar için” — aynı label modeli, PromQL’den ilham alan aynı sorgu dili (LogQL).

4.2 Mimari

Promtail/Alloy/Fluent Bit → Distributor → Ingester → Chunk'lar (object storage: S3/GCS)
                                              │
                                          Compactor
                                              │
                             Querier ◄── Query Frontend ◄── Grafana

4.3 LogQL

Log Stream Selector (PromQL label matcher’ları gibi)

{app="checkout", env="prod"}
{app="checkout"} |= "error"
{app="checkout"} != "healthcheck"
{app="checkout"} |~ "error|panic|fatal"
{app="checkout"} |= "error" | json | line_format "{{.msg}}"

Operatörler: |= içerir, != içermez, |~ regex eşleşir, !~ regex eşleşmez.

Parser’lar

{app="api"} | json                                 # JSON log satırlarını label'lara ayrıştır
{app="api"} | logfmt                                # logfmt satırlarını ayrıştır
{app="api"} | pattern "<ip> - - <_> \"<method> <uri>"
{app="api"} | regexp "(?P<level>\\w+): (?P<msg>.*)"

Label Filtreleri ve Satır Formatlama

{app="api"} | json | status_code >= 500
{app="api"} | json | duration > 1s
{app="api"} | json | line_format "{{.timestamp}} {{.level}} {{.msg}}"
{app="api"} | json | label_format new_label="{{.old_label}}"

Metrik Sorguları (logları sayılara agrege etme)

sum(rate({app="api"} |= "error" [5m])) by (pod)
sum(count_over_time({app="api"}[5m])) by (level)
topk(5, sum(rate({app="api"}[5m])) by (route))

# unwrap: log satırındaki sayısal bir alanı agregasyon için değere çevirir
quantile_over_time(0.99,
  {app="api"} | json | unwrap duration_ms [5m]
) by (route)

4.4 Promtail / Grafana Alloy Yapılandırması

scrape_configs:
  - job_name: kubernetes-pods
    kubernetes_sd_configs:
      - role: pod
    pipeline_stages:
      - docker: {}
      - json:
          expressions:
            level: level
            msg: message
      - labels:
          level:
    relabel_configs:
      - source_labels: [__meta_kubernetes_pod_label_app]
        target_label: app

Grafana Alloy, Promtail’in modern halefidir (birçok pipeline için OTel Collector kullanım senaryosunu da karşılar) — yeni deployment’lar genellikle Alloy’u tercih etmelidir.

4.5 Label Kardinalitesi — Prometheus’tan Bile Daha Kritik

Loki’nin index’i label kümelerinden oluşturulur ve her benzersiz label kombinasyonu yeni bir stream yaratır. Yüksek kardinaliteli değerleri (user_id, request_id, trace_id) label olarak koymak index’i patlatır ve performansı/maliyeti mahveder. Bunun yerine:

4.6 Retention ve Compaction

limits_config:
  retention_period: 720h   # 30 gün
compactor:
  retention_enabled: true
  delete_request_store: s3

Tenant başına override’lar, kritik audit loglarını debug loglarından daha uzun süre saklamanıza olanak tanır.


5. Jaeger ve Distributed Tracing

5.1 Temel Kavramlar

Trace: abc123
├── span: gateway (0-120ms)
│   ├── span: auth-service (5-20ms)
│   └── span: order-service (25-115ms)
│       ├── span: db-query (30-70ms)
│       └── span: payment-service (75-110ms)

5.2 Mimari

Uygulama (SDK/OTel) → Jaeger Agent/OTel Collector → Jaeger Collector → Storage (Elasticsearch/Cassandra/Kafka buffer) → Query Service → Jaeger UI / Grafana

Modern öneri: kodu OpenTelemetry SDK’ları ile (vendor-neutral) enstrümante edin, OTLP üzerinden bir OpenTelemetry Collector‘a export edin; bu collector da Jaeger’a (veya Grafana Tempo’ya) yönlendirir. Uygulama kodunu artık Jaeger’ın native client kütüphanelerine bağlamayın — bunlar bakım modundadır; standart artık OTel’dir.

5.3 Enstrümantasyon

// Go + OpenTelemetry örneği
tracer := otel.Tracer("order-service")
ctx, span := tracer.Start(ctx, "ProcessOrder")
defer span.End()

span.SetAttributes(
    attribute.String("order.id", orderID),
    attribute.Int("order.item_count", len(items)),
)

if err != nil {
    span.RecordError(err)
    span.SetStatus(codes.Error, err.Error())
}

5.4 Sampling Stratejileri

Ölçekte her isteği trace etmek pahalıdır (storage + overhead). Seçenekler:

StratejiAçıklamaTrade-off
Head-based, probabilisticTrace’lerin X%‘ini trace başlangıcında örnekleBasit, ama nadir hataları kaçırabilir
Rate limitingServis başına saniyede N traceÖngörülebilir maliyet
Tail-based samplingTüm trace’i buffer’la, sonucu gördükten sonra karar ver (ör. hataları/yavaş trace’leri her zaman tut)Daha iyi sinyal, buffer yapan bir collector gerektirir (OTel Collector tail_sampling processor) — daha fazla altyapı
Adaptive samplingJaeger collector, hedef hacme ulaşmak için endpoint başına oranı ayarlarDüşük ve yüksek trafikli servisleri dengeler

Best practice: düşük head-based sample oranı (ör. %1-10) ile, head kararından bağımsız olarak hata trace’lerini ve belirli bir gecikme eşiğinin üzerindekileri her zaman tutan tail-based sampling kurallarını birleştirin.

5.5 Context Propagation Formatları

5.6 Trace’leri Metrik/Loglarla İlişkilendirme


6. Metrik, Log ve Trace’lerin Korelasyonu

Observability araçlarının gerçek gücü, tek başına herhangi bir sütundan değil, çapraz gezinmeden (cross-navigation) gelir.

Alert tetiklenir (Prometheus/Alertmanager)
   → Dashboard hata oranı sıçramasını gösterir (Grafana)
   → Latency panelindeki exemplar noktasına tıkla → yavaş Trace'e atla (Jaeger)
   → Trace hangi span/servisin yavaş olduğunu gösterir
   → "Bu span için loglar"a tıkla → trace_id'ye göre filtrelenmiş Loki sorgusu (Loki)
   → Kök neden log satırında bulunur

Uygulama kontrol listesi:


7. SLO, SLI ve Error Budget

7.1 Tanımlar

7.2 Örnek SLI Sorguları

# Erişilebilirlik SLI
sum(rate(http_requests_total{status!~"5.."}[5m]))
  / sum(rate(http_requests_total[5m]))

# Gecikme SLI (isteklerin %'si 300ms altında)
sum(rate(http_request_duration_seconds_bucket{le="0.3"}[5m]))
  / sum(rate(http_request_duration_seconds_count[5m]))

7.3 Multi-Window, Multi-Burn-Rate Alerting

Hata oranı üzerine naif eşik alert’leri ya çok geç ateşlenir (günler süren yavaş yanma) ya da çok sık (kısa sıçramalarda gürültülü). Standart SRE deseni, önem derecesi başına iki zaman penceresi kullanır:

# Hızlı yanma: bütçenin 14.4x'ini tüketiyor → 30 günlük bütçeyi 2 saatte bitirir
- alert: ErrorBudgetBurnFast
  expr: |
    (
      job:http_error_ratio:rate5m > 14.4 * 0.001
      and
      job:http_error_ratio:rate1h > 14.4 * 0.001
    )
  labels: {severity: critical}

# Yavaş yanma: 6sa/3g üzerinden bütçenin 1x'ini tüketiyor → sadece sürdürülürse sayfala
- alert: ErrorBudgetBurnSlow
  expr: |
    (
      job:http_error_ratio:rate1h > 1 * 0.001
      and
      job:http_error_ratio:rate6h > 1 * 0.001
    )
  labels: {severity: warning}

Bu, doğrudan Google’ın SRE workbook’undaki multi-window burn-rate metodolojisinden gelir — tespit hızı ile hassasiyet (yanlış sayfaları önleme) arasında denge kurar.


8. Production Kontrol Listesi

Prometheus

Grafana

Loki

Jaeger/Tracing

Genel


Rehberin sonu. İki dilli takımlar için İngilizce eşlik eden belge ile birlikte kullanın.