Alerting: Semptom Tabanlı Alarm, Alertmanager, Grafana Alerting, Runbook ve Alarm Yorgunluğu
Alarm, kullanıcının hissettiği semptoma kurulur ve her birinin bir runbook'u olur; sebepler page değil ticket'tır. Bookstore için burn-rate, süre, retry, telemetry yokluğu ve Watchdog alarmlarını Prometheus'ta yazıp Alertmanager ile yönlendiriyor, gruplama, silence ve inhibition'ı gerçek bir arıza üzerinde görüyor, API düştüğünde hata oranı alarmının neden sustuğunu ölçüyoruz.
Sürüm notu: Bu yazıdaki örnekler .NET 10, OpenTelemetry .NET 1.21, grafana/otel-lgtm 0.41.0 image'ı (Prometheus 3.16.0, Grafana 13.3.0) ve Alertmanager 0.35.0 ile denenmiştir. Grafana Alerting ve blackbox probe'ları bu yazıda çalıştırılmadı.
Önce Tahmin Et
Bookstore'un (sitedeki serilerin ortak örnek uygulaması, bir online kitapçı) ödeme sağlayıcısını kötü bir güne soktuk: denemelerin yüzde 60'ı 5 saniye sürüyor. Prometheus'ta beş alarm kuralı var: hata oranı, checkout süresi, ödeme retry'ları, telemetry'nin hiç gelmemesi ve her zaman çalan bir "Watchdog". Sorun başladıktan sonra nöbetçinin telefonuna kaç bildirim düşer, ilki kaçıncı dakikada gelir? Ardından API'yi tamamen durdurduk; checkout'ların hepsi başarısız olacak. Hata oranı alarmı bu kez ne yapar?
Semptom mu, Sebep mi?
Bir alarmın tek bir işi var: bir insanın hemen bir şey yapması gerektiğini söylemek. Google'ın SRE (Site Reliability Engineering; sistemin güvenilirliğini yazılım mühendisliğiyle yönetme yaklaşımı) kitabının Monitoring Distributed Systems bölümü bunu semptom ve sebep ayrımıyla anlatıyor: kullanıcının hissettiği şeye (checkout başarısız oluyor, checkout yavaş) alarm kur; sebeplerin (CPU yüksek, ödeme sağlayıcısı retry istiyor) her biri alarm değil, araştırmanın girdisi. CPU yüzde 90'dayken kullanıcılar etkilenmiyorsa gece 3'te birini uyandırmanın bir anlamı yok; kullanıcılar sipariş veremiyorsa CPU normal olsa da birinin kalkması gerekiyor.
İkinci ayrım bildirimin türü. Page, hemen müdahale gerektiren ve nöbetçiyi uyandıran bildirim. Ticket ise mesai içinde bakılacak bir iş kaydı. Bookstore'da kural basit: kullanıcıyı etkileyen semptomlar severity: page, sebepler ve erken uyarılar severity: ticket. Bu etiket, aşağıda Alertmanager'ın bildirimi nereye göndereceğini belirliyor.
Prometheus'ta Alarm Kuralı
Alarm kuralı, recording rule'larla aynı dosya formatında yazılıyor; farkı, sorgu bir sonuç döndürdüğünde alarmın oluşması. Bookstore'un kuralları deploy/observability/alerts.yaml dosyasında. Checkout süresi alarmı:
# Müşteri bekliyor: checkout'un p95'i 10 dakika boyunca 3 saniyenin üstünde.
- alert: CheckoutLatencyHigh
expr: bookstore:checkout_duration_seconds:p95_5m > 3
for: 10m
labels:
severity: page
service: bookstore-api
annotations:
summary: "Checkout p95 is {{ $value | humanizeDuration }}"
runbook_url: "docs/runbooks/checkout-latency.md"
expr, Prometheus yazısında yazdığımız recording rule'u okuyor; alarm her değerlendirmede pahalı yüzdelik hesabını tekrar yapmıyor. for: 10m, koşulun 10 dakika boyunca kesintisiz doğru kalmasını istiyor; o süre içinde alarm "pending" durumunda bekliyor. Bu, birkaç dakikalık geçici bir sıçramanın birini uyandırmasını önlüyor. labels yönlendirme için, annotations insan için: bildirimde görünecek özet ve runbook bağlantısı. Kurallar otel-lgtm'in içindeki Prometheus'a, image'ın kendi Prometheus config'inin bir kopyası (lgtm-prometheus.yaml) ile bağlanıyor; o dosyanın sonuna kural dosyalarını ve Alertmanager adresini ekledik, değerlendirme aralığını da 15 saniyeye indirdik. Kuralları Prometheus'a vermeden önce promtool ile (otel-lgtm image'ında da var) kontrol etmek, bir yazım hatasının alarmı sessizce devre dışı bırakmasını önlüyor:
promtool check rules alerts.yaml rules.yaml
Checking alerts.yaml
SUCCESS: 5 rules found
Checking rules.yaml
SUCCESS: 4 rules found
Burn-Rate Alarmı
Hata oranı için "yüzde 1'i geçerse alarm" gibi sabit bir eşik iki şekilde yanılır: gece trafiği azken birkaç hata oranı fırlatır, gündüz yüzde 0,8'lik sürekli bir hata ise hiç alarm üretmez. Bunun yerine bir hedef konuyor: SLO (Service Level Objective; servisin hedeflenen güvenilirlik seviyesi). Bookstore için hedef, 30 günde checkout'ların yüzde 99,5'inin 5xx ile bitmemesi. Bu, yüzde 0,5'lik bir hata bütçesi (error budget) demek. Burn rate, bütçenin hangi hızla tüketildiği: 1 ise bütçe tam 30 günde biter, 14,4 ise saatte bütçenin yüzde 2'si gider (14,4 / 720 saat) ve bütçe iki günde biter. SLO'nun nasıl seçileceği ve bütçe bitince ne yapılacağı SRE ve Production Incident'ları serisinin konusu; burada alarm tarafına bakıyoruz.
# SLO: 30 günde checkout'ların (POST /api/orders) yüzde 99,5'i 5xx ile bitmez (hata bütçesi yüzde 0,5).
# Bir saat boyunca 14,4 kat hızla yanmak 30 günlük bütçenin yüzde 2'sini harcar: page. 5 dakikalık pencere alarmı çabuk susturur.
- alert: CheckoutErrorBudgetBurn
expr: |
bookstore:orders_http_errors:ratio_rate1h > (14.4 * 0.005)
and
bookstore:orders_http_errors:ratio_rate5m > (14.4 * 0.005)
for: 2m
labels:
severity: page
service: bookstore-api
İki pencere birlikte çalışıyor. Bir saatlik pencere, alarmın gerçekten önemli bir bütçe tüketimi olduğunda çalmasını sağlıyor; 5 dakikalık pencere ise sorun bittiğinde alarmın bir saat boyunca çalmaya devam etmesini önlüyor. Bu çok pencereli yaklaşım Google'ın SRE Workbook'undaki SLO'lara alarm kurma bölümünden; aynı bölüm daha yavaş yanmalar için 6 saatlik pencere ve 6 kat burn rate ile ikinci bir page alarmı, 3 günlük pencereyle de bir ticket alarmı öneriyor. 1 saatlik oran için rules.yaml'a bir recording rule daha ekledik.
Alertmanager: Yönlendirme, Gruplama ve Bastırma
Prometheus alarmın ne zaman oluştuğuna karar veriyor; kime, ne sıklıkla ve hangi kanaldan gideceğine Alertmanager. Alertmanager'ın resmi binary'sini (0.35.0) container'ın yanında, host'ta çalıştırdık; Prometheus ona host.docker.internal:9093 üzerinden ulaşıyor. Gerçek bir nöbet aracı ya da sohbet kanalı yerine bildirimleri tools/Bookstore.AlertSink adında küçük bir uygulama alıyor ve her birini tek satır olarak yazıyor:
# Bookstore için Alertmanager (lokal). Bildirimler webhook olarak tools/Bookstore.AlertSink'e gider;
# production'da bu receiver'lar nöbet aracı, ticket kuyruğu ve bir heartbeat servisi olur.
route:
receiver: ticket
# Seri başına değil, alarm ve servis başına tek bildirim.
group_by: [alertname, service]
group_wait: 30s
group_interval: 5m
repeat_interval: 4h
routes:
- matchers: [severity="page"]
receiver: on-call
- matchers: [severity="heartbeat"]
receiver: heartbeat
group_wait: 0s
group_interval: 1m
repeat_interval: 1m
receivers:
- name: on-call
webhook_configs:
- url: http://localhost:5095/alerts/on-call
- name: ticket
webhook_configs:
- url: http://localhost:5095/alerts/ticket
- name: heartbeat
webhook_configs:
- url: http://localhost:5095/alerts/heartbeat
inhibit_rules:
# API hiç telemetry göndermiyorsa diğer alarmları gürültüdür: zaten değerlendirilemezler.
- source_matchers: [alertname="BookstoreApiTelemetryMissing"]
target_matchers: [severity=~"page|ticket"]
equal: [service]
alertmanager --config.file=deploy/observability/alertmanager.yml --storage.path=./alertmanager-data --cluster.listen-address=
amtool check-config deploy/observability/alertmanager.yml
amtool config routes test --config.file=deploy/observability/alertmanager.yml severity=page alertname=CheckoutLatencyHigh service=bookstore-api
on-call
group_by, aynı alarmın farklı serilerini (örneğin on pod'un her birinden gelen aynı alarmı) tek bir bildirimde topluyor. group_wait, ilk bildirimden önce aynı gruba başka alarm gelir mi diye 30 saniye bekliyor; repeat_interval, çözülmemiş bir alarmın ne sıklıkla tekrar hatırlatılacağı. Önce Tahmin Et'in ilk sorusunun cevabı, trafiği 20:22:30'da başlattıktan sonra AlertSink'in yazdıkları:
20:25:28 info: Program[0] [on-call] firing CheckoutErrorBudgetBurn severity=page Checkout is failing: 14.79% of orders end with 5xx runbook=docs/runbooks/checkout-errors.md
20:33:13 info: Program[0] [on-call] firing CheckoutLatencyHigh severity=page Checkout p95 is 10s runbook=docs/runbooks/checkout-latency.md
20:38:13 info: Program[0] [ticket] firing PaymentProviderRetriesHigh severity=ticket ...
Nöbetçinin telefonuna iki bildirim düştü: üçüncü dakikada hata bütçesi alarmı, on birinci dakikada süre alarmı. Retry alarmı page değil ticket olarak, on altıncı dakikada iş kuyruğuna gitti (son satırın özetini kısalttık; bu koşudan sonra özet metnini düzelttik). Üç alarm, aynı olayın üç yüzü: hepsi tek bir runbook'a, ödeme sağlayıcısına götürüyor. Retry alarmı için nöbetçi ödeme sağlayıcısıyla iletişime geçtikten sonra bir silence ekledi:
amtool --alertmanager.url=http://localhost:9093 silence add alertname=PaymentProviderRetriesHigh service=bookstore-api --duration=2h --author=emre --comment="Provider incident open, ticket filed"
389fa9ca-4f62-49b0-9471-391b5e61a07f
amtool --alertmanager.url=http://localhost:9093 alert query
Alertname Starts At Summary State
Watchdog 2026-11-14 20:21:43 UTC Alerting pipeline is alive active
CheckoutErrorBudgetBurn 2026-11-14 20:24:58 UTC Checkout is failing: 19.12% of orders end with 5xx active
CheckoutLatencyHigh 2026-11-14 20:32:43 UTC Checkout p95 is 10s active
Silence, belirli bir süre için eşleşen alarmların bildirimini durduruyor; kim, neden ve ne zamana kadar susturduğu kayıtlı. Bilinen bir sorun üzerinde çalışılırken aynı bildirimin tekrar tekrar gelmesini önlemenin doğru yolu bu, kuralı değiştirmek değil.
Önce Tahmin Et'in ikinci sorusu için 20:39:02'de API'yi durdurduk. Beklenen, hata oranı alarmının daha da yükselmesi; olan tam tersi oldu:
20:45:28 info: Program[0] [on-call] resolved CheckoutErrorBudgetBurn severity=page Checkout is failing: 19.56% of orders end with 5xx runbook=docs/runbooks/checkout-errors.md
20:46:43 info: Program[0] [on-call] firing BookstoreApiTelemetryMissing severity=page No telemetry from bookstore-api for 7 minutes runbook=docs/runbooks/api-down.md
Hata bütçesi alarmı "resolved" oldu. API durunca istek de, hata da, metric de gelmiyor; 5 dakikalık oran hesaplanacak bir şey kalmayınca boş sonuç dönüyor ve boş sonuç alarm demek değil. Durumu yakalayan tek kural absent_over_time(target_info{job="bookstore-api"}[5m]) oldu: son 5 dakikada servisten hiç target_info gelmediyse çalışıyor ve for: 2m ile birlikte yaklaşık 7 dakika sonra nöbetçiye ulaştı. OTLP (OpenTelemetry Protocol; telemetry taşıma protokolü) ile push edilen metric'lerde Prometheus'un scrape'lerde tuttuğu up serisi yok; "servis hiç konuşmuyor" sorusunu bu tür bir kural cevaplıyor.
Inhibition'ı da bu sırada gördük. Retry alarmı Prometheus'ta hâlâ firing'di, ama Alertmanager onu hem silence hem de telemetry alarmı yüzünden bastırdı. Davranışı netleştirmek için amtool alert add ile elle bir CheckoutLatencyHigh alarmı gönderdik; Alertmanager'ın API'si ikisini de "suppressed" gösterdi:
curl -s localhost:9093/api/v2/alerts | jq -c '.[] | {a: .labels.alertname, state: .status.state, inhibitedBy: (.status.inhibitedBy|length), silencedBy: (.status.silencedBy|length)}'
{"a":"BookstoreApiTelemetryMissing","state":"active","inhibitedBy":0,"silencedBy":0}
{"a":"Watchdog","state":"active","inhibitedBy":0,"silencedBy":0}
{"a":"PaymentProviderRetriesHigh","state":"suppressed","inhibitedBy":1,"silencedBy":1}
{"a":"CheckoutLatencyHigh","state":"suppressed","inhibitedBy":1,"silencedBy":0}
API düştüğünde nöbetçi tek bir bildirim alıyor ve o bildirim doğru runbook'a gidiyor: önce API'nin ayakta olup olmadığına bak. Gerisi aynı olayın yansıması.
Dead Man's Switch ve Blackbox Probe'ları
Alarm sisteminin kendisi de bozulabilir: Prometheus durur, Alertmanager'ın bildirim gönderdiği webhook değişir, biri route'u yanlış düzenler. Bu durumda hiç bildirim gelmez ve sessizlik "her şey yolunda" gibi görünür. Watchdog alarmı bunun için: expr: vector(1) ile her zaman çalıyor ve kendi route'uyla dakikada bir heartbeat receiver'ına gidiyor. Production'da bu receiver, belirli bir süre heartbeat almazsa kendisi alarm üreten harici bir servis olur. Bu, "dead man's switch" (sinyal kesilince devreye giren mekanizma) yaklaşımı. Bizim koşuda AlertSink'e dakikada bir [heartbeat] firing Watchdog satırı düştü.
Bir diğer kör nokta dışarıdan bakış. Bütün alarmlarımız uygulamanın kendi telemetry'sinden geliyor; DNS kaydı bozulduysa ya da TLS (HTTPS trafiğini şifreleyen protokol) sertifikası bittiyse istekler uygulamaya hiç ulaşmaz ve uygulamanın metric'leri bir şey söylemez. Prometheus'un blackbox exporter'ı bir URL'i dışarıdan düzenli olarak dener ve probe_success, probe_duration_seconds ve probe_ssl_earliest_cert_expiry gibi metric'ler üretir; sertifikanın bitmesine 14 günden az kaldığında ticket açan bir kural bunun en basit kullanımı. Bunu bu yazıda çalıştırmadık; sertifika yönetiminin kendisi sitedeki ilgili serinin konusu (SSL/TLS serisi).
Grafana Alerting
Grafana'nın kendi alerting'i aynı işi farklı bir yerden yapıyor; bu yazıda onu çalıştırmadık. Kurallar Grafana'da tanımlanıyor ve Prometheus'un yanında Loki, Tempo ya da bir SQL veritabanı gibi Grafana'nın sorgulayabildiği her data source'u kullanabiliyor. Alertmanager'ın route'larının karşılığı notification policy'ler, receiver'ların karşılığı contact point'ler; silence ve gruplama da var. Grafana kendi içinde bir Alertmanager çalıştırıyor, istenirse dışarıdaki bir Alertmanager'a da gönderebiliyor. Seçim pratik: kuralların metric'lerle birlikte Git'te durmasını ve Prometheus'la aynı yerde değerlendirilmesini istiyorsan Prometheus ve Alertmanager; log'a ya da birden çok data source'a dayalı alarmlara ihtiyacın varsa ve ekip zaten Grafana'da çalışıyorsa Grafana Alerting. İkisini aynı alarm için birlikte kullanma; aynı olay iki ayrı yoldan iki bildirim üretir.
Runbook ve Alarm Yorgunluğu
Her alarmın runbook_url'i var ve gösterdiği dosya repo'da, docs/runbooks/ altında. Runbook gece 3'te uyanan kişi için yazılır: alarm ne demek, kim etkileniyor, ilk 5 dakikada ne yapılır. Hata bütçesi alarmınınki kısaltılmış hâliyle:
# CheckoutErrorBudgetBurn
**What it means:** more than 7.2% of `POST /api/orders` requests end with 5xx, both over the last hour and the last
5 minutes. At this rate the 30-day error budget (0.5%) is gone in about two days.
**First 5 minutes**
1. Open the "Bookstore API (RED)" dashboard: which status codes, since when, one instance or all?
2. Look at "Payment provider: retries and timeouts". 502s with many timeouts point at the payment provider.
3. In Grafana Explore (Loki): `{service_name="bookstore-api"} | detected_level="error"`; open a line and follow its `trace_id` to Tempo.
4. Was there a deploy in the last hour? If yes and the errors started with it: roll back first, investigate later.
Runbook'lar repo'da ve ekibin ortak dilinde, bu yüzden İngilizce. Runbook'u olmayan bir alarm iki şeyden biridir: ya ne yapılacağı belli değil, o zaman alarmın kendisi tartışılmalı; ya da yapılacak şey hep aynı, o zaman otomatikleştirilmeli.
Alarm yorgunluğu, çok fazla ve işe yaramayan bildirim yüzünden ekibin bildirimleri görmezden gelmeye başlaması. Ölçülebilir: Prometheus her alarmın durumunu ALERTS serisinde tutuyor ve count by (alertname) (changes(ALERTS{alertstate="firing"}[7d])) gibi bir sorgu bir haftada hangi alarmın kaç kere açılıp kapandığını gösteriyor. Sürekli açılıp kapanan (flapping) bir alarm genellikle ya çok kısa bir for süresine ya da trend yerine anlık değere bakan bir eşiğe işaret ediyor. Haftalık kısa bir alarm review'u iyi bir alışkanlık: geçen haftanın her page'i için "bu bildirim bir eylem gerektirdi mi?" sorusunu sor; cevap hayırsa alarmı ticket'a indir, eşiğini değiştir ya da sil.
Üretimde Dikkat
Alarm kurallarını kod gibi yönet. alerts.yaml repo'da, pull request ile değişiyor ve CI'da promtool check rules ile amtool check-config çalışıyor. Kuralın kendisi için de test yazılabiliyor: promtool test rules, verilen örnek serilerle bir alarmın beklenen anda çalıp çalmadığını doğruluyor.
Prometheus ve Alertmanager'ın kendisini de izle. Prometheus'un prometheus_notifications_dropped_total ve Alertmanager'ın alertmanager_notifications_failed_total metric'leri, bildirimlerin yolda kaybolduğunu söyleyen ilk yer. Production'da Alertmanager yüksek erişilebilirlik için birden çok instance'la ve aralarında gossip ile çalışır; biz yerelde --cluster.listen-address= ile bunu kapattık.
Bildirim kanalını gerçekten test et. Nöbet aracındaki telefon numarası, webhook'un token'ı ya da kanalın adı değişir ve kimse fark etmez. Watchdog bunu sürekli test ediyor; yanında, yeni bir nöbet döneminin başında bir test alarmı göndermek ucuz bir kontrol.
Sık Düşülen Tuzaklar
Her exception'a alarm kurmak. Tek bir exception çoğu zaman bir kullanıcının tek bir isteği; alarm oranlara ve bütçeye kurulur, tekil olaylara değil. Exception'ları log'da ve trace'te ara, alarmı sonuca (checkout başarısız oluyor) kur.
Veri yokken alarmın da susacağını unutmak. Yukarıda gördüğümüz gibi API durduğunda hata oranı alarmı çalmadı, çözüldü; oran hesaplanacak bir istek kalmadı. Her servis için "telemetry gelmiyor" alarmı şart.
Sessiz kalmış alarm kanalını fark etmemek. Bir ay boyunca hiç bildirim gelmemesi iyi haber de olabilir, bozuk bir webhook da. Watchdog ve harici heartbeat servisi olmadan ikisini ayırt edemezsin.
Sebebe page kurmak. "Ödeme sağlayıcısı retry istiyor" alarmı nöbetçiyi uyandırsaydı, kullanıcıya etki etmeyen bir durum için de uyandıracaktı. Sebepler ticket; semptomlar page.
for süresini çok kısa ya da hiç vermemek. Tek bir kötü değerlendirme alarmı çaldırır ve bir sonraki değerlendirmede çözer; flapping'in en yaygın sebebi bu.
Küçük Bir Deneme
★☆☆ amtool config routes test ile severity=ticket ve severity=heartbeat etiketli alarmların hangi receiver'a gittiğini kontrol et; sonra severity etiketi olmayan bir alarmın nereye düştüğüne bak.
★★☆ SRE Workbook'taki ikinci page alarmını ekle: 6 saatlik ve 30 dakikalık pencerelerde 6 kat burn rate. Gerekli recording rule'ları yaz ve promtool test rules ile bir test dosyasında alarmın beklenen anda çaldığını doğrula.
★★★ Bookstore'a sertifika süresi için bir alarm ekle: blackbox exporter'ı çalıştır, API'nin HTTPS adresini probe et ve probe_ssl_earliest_cert_expiry - time() < 14 * 86400 koşuluyla bir ticket alarmı yaz. Kısa süreli bir test sertifikasıyla alarmın çaldığını göster.
Alarmlar artık doğru semptomlarda ve doğru kişiye gidiyor. Ama bütün bunlar yerel bir container'da çalıştı; production'da telemetry'nin bir faturası, sampling kararları, kişisel veri sorumluluğu ve Kubernetes'te yüzlerce pod var. Bir sonraki yazıda production'a çıkıyoruz: Azure Monitor ve Application Insights, sampling ve maliyet, PII (kişiyi tanımlayan veri) ve KVKK (Kişisel Verilerin Korunması Kanunu), Kubernetes'te otomatik instrumentation ve yeni bir servisin production'a çıkmadan önceki telemetry kontrol listesi.