ELK Stack ile Alert ve Bildirim Yapılandırması
Geçen ay bir müşterimizin üretim ortamında ciddi bir sorun yaşadık. Disk doluluk oranı %95’i geçmişti ama kimse farkında değildi çünkü alertlar düzgün yapılandırılmamıştı. ELK Stack kuruluydu, loglar akıyordu, Kibana dashboardlar harikalar köyünü anlatıyordu, ama kimse gece 3’te ekrana bakmıyordu. İşte bu yazı tam olarak o boşluğu kapatmak için.
ELK Stack’te alert mekanizması Elasticsearch’ün içinde yatıyor. Elastic 8.x ile birlikte Watcher (eski adıyla X-Pack Alerting) artık daha olgun bir hale geldi, ama doğru yapılandırmak hala biraz deneyim istiyor. Gelin bu deneyimi aktaralım.
Temel Kavramlar: Watcher Nasıl Çalışır?
Watcher döngüsel olarak çalışan bir mekanizma. Kaba hatlarıyla şöyle açıklayabilirim: belirli aralıklarla bir sorgu çalıştırıyor, sonucu bir koşulla karşılaştırıyor, koşul sağlandığında bir eylem tetikliyor. Bu döngünün bileşenlerine bakalım.
Trigger: Watch’ın ne zaman çalışacağını belirler. Genellikle bir schedule kullanılır.
Input: Veri kaynağı. Çoğunlukla Elasticsearch sorgusu, ama HTTP isteği veya statik veri de olabilir.
Condition: Input’tan gelen veriyle karşılaştırma yapılır. Koşul sağlanırsa actions devreye girer.
Actions: E-posta, Slack, webhook, PagerDuty gibi bildirim kanalları veya başka Elasticsearch işlemleri tetiklenebilir.
Transform: Condition ile Actions arasında veriyi şekillendirmek için kullanılır, zorunlu değildir.
Basit bir yapı gibi görünüyor ama üretim ortamlarında ince ayarlar yapmak gerekiyor. Önce basit bir örnekle başlayalım.
İlk Watch: Hata Oranı İzleme
Uygulama loglarındaki ERROR seviyesindeki girişleri izleyen basit bir watch yazalım. Bu örnek Kibana Dev Tools üzerinden çalıştırılabilir.
PUT _watcher/watch/error_rate_watch
{
"trigger": {
"schedule": {
"interval": "5m"
}
},
"input": {
"search": {
"request": {
"indices": ["app-logs-*"],
"body": {
"query": {
"bool": {
"must": [
{"match": {"log.level": "ERROR"}},
{"range": {"@timestamp": {"gte": "now-5m"}}}
]
}
},
"aggs": {
"error_count": {
"value_count": {"field": "_id"}
}
}
}
}
}
},
"condition": {
"compare": {
"ctx.payload.hits.total.value": {
"gt": 10
}
}
},
"actions": {
"send_email": {
"email": {
"to": ["[email protected]"],
"subject": "UYARI: Son 5 dakikada {{ctx.payload.hits.total.value}} hata",
"body": {
"text": "Uygulama loglarında anormal hata oranı tespit edildi.nnToplam hata sayısı: {{ctx.payload.hits.total.value}}nnLütfen Kibana'yı kontrol edin."
}
}
}
}
}
Bu watch 5 dakikada bir çalışıyor, son 5 dakikadaki ERROR sayısını sayıyor ve 10’dan fazlaysa e-posta gönderiyor. Basit ama işe yarıyor.
E-posta Bildirimi Yapılandırması
Watch yazmadan önce Elasticsearch’ün e-posta gönderebilmesi gerekiyor. elasticsearch.yml dosyasına mail sunucu ayarlarını ekleyelim.
# elasticsearch.yml
xpack.notification.email.account:
gmail_account:
profile: gmail
smtp:
auth: true
starttls.enable: true
host: smtp.gmail.com
port: 587
user: [email protected]
password: uygulama_sifresi
Şifreyi düz metin olarak yazmamanızı öneririm. Elasticsearch Keystore kullanmak çok daha güvenli:
# Keystore'a şifre ekle
bin/elasticsearch-keystore add xpack.notification.email.account.gmail_account.smtp.secure_password
# Keystore içeriğini listele
bin/elasticsearch-keystore list
# elasticsearch.yml'de password yerine keystore referansı kullan
# xpack.notification.email.account.gmail_account.smtp.secure_password: "${SMTP_PASSWORD}"
Kurumsal SMTP sunucusu kullananlar için ayrı bir yapılandırma:
# elasticsearch.yml - Kurumsal SMTP
xpack.notification.email.account:
exchange_account:
profile: standard
email_defaults:
from: [email protected]
smtp:
auth: true
host: mail.sirketim.com
port: 587
user: [email protected]
Slack Entegrasyonu
E-posta alertları gerçek zamanlı değil. Özellikle kritik sistemlerde Slack veya benzeri bir anlık mesajlaşma platformuna bildirim atmak çok daha efektif. Slack’te incoming webhook oluşturduktan sonra:
# elasticsearch.yml
xpack.notification.slack:
account:
monitoring:
url: https://hooks.slack.com/services/TXXXXXXXX/BXXXXXXXX/XXXXXXXXXXXXXXXXXXXXXXXX
message_defaults:
from: Elasticsearch Alert
icon: ":bell:"
channel: "#ops-alerts"
Sonra watch’a Slack action ekleyelim:
PUT _watcher/watch/kritik_hata_slack
{
"trigger": {
"schedule": {"interval": "2m"}
},
"input": {
"search": {
"request": {
"indices": ["app-logs-*"],
"body": {
"query": {
"bool": {
"must": [
{"match": {"log.level": "CRITICAL"}},
{"range": {"@timestamp": {"gte": "now-2m"}}}
]
}
}
}
}
}
},
"condition": {
"compare": {
"ctx.payload.hits.total.value": {"gt": 0}
}
},
"actions": {
"slack_alert": {
"slack": {
"account": "monitoring",
"message": {
"channel": "#ops-alerts",
"text": ":red_circle: *KRİTİK HATA ALARMASI*",
"attachments": [
{
"color": "danger",
"title": "Kritik Hata Tespit Edildi",
"text": "Son 2 dakikada {{ctx.payload.hits.total.value}} kritik hata oluştu.n*Ortam:* Üretimn*Zaman:* {{ctx.execution_time}}",
"footer": "Elasticsearch Watcher"
}
]
}
}
}
}
}
Bu yapı gerçekten işe yarıyor. Slack bildirimlerinde renk kodlaması kullanmak alert önceliğini anında anlamayı sağlıyor: danger kırmızı, warning sarı, good yeşil.
Gelişmiş Condition: Throttle ve Deduplikasyon
Burası çoğu insanın atladığı yer. Alert fırtınasına yakalandıysanız bilirsiniz, aynı sorun için saatte 60 alert almak kimseyi uyandırmaz, aksine herkes alarmları kapatmaya başlar.
Throttle ile aynı watch’ın ne kadar sıklıkla tetiklenebileceğini sınırlayabiliriz:
PUT _watcher/watch/disk_doluluk_watch
{
"trigger": {
"schedule": {"interval": "10m"}
},
"input": {
"search": {
"request": {
"indices": ["metricbeat-*"],
"body": {
"query": {
"bool": {
"must": [
{"term": {"metricset.name": "filesystem"}},
{"range": {"system.filesystem.used.pct": {"gte": 0.85}}},
{"range": {"@timestamp": {"gte": "now-10m"}}}
]
}
},
"_source": ["host.name", "system.filesystem.mount_point", "system.filesystem.used.pct"]
}
}
}
},
"condition": {
"compare": {
"ctx.payload.hits.total.value": {"gt": 0}
}
},
"throttle_period": "1h",
"actions": {
"disk_alert": {
"throttle_period": "30m",
"slack": {
"account": "monitoring",
"message": {
"channel": "#infrastructure",
"text": ":warning: Disk kullanımı %85 üzerinde olan sunucular var. Kibana'dan kontrol edin."
}
}
}
}
}
throttle_period: "1h" ayarı bu watch’ın tetiklenmiş bile olsa aynı action’ı saatte en fazla bir kez çalıştırmasını sağlıyor. Alert fırtınası derdinize çare.
Script Condition ile Karmaşık Koşullar
Basit compare yeterli olmadığında Painless script devreye giriyor. Örneğin belirli bir sunucudan gelen hataları izlemek ve hata oranı belirli bir eşiği geçtiğinde alarm vermek istiyoruz:
PUT _watcher/watch/hata_orani_hesaplama
{
"trigger": {
"schedule": {"interval": "5m"}
},
"input": {
"search": {
"request": {
"indices": ["app-logs-*"],
"body": {
"query": {
"range": {"@timestamp": {"gte": "now-5m"}}
},
"aggs": {
"toplam": {"value_count": {"field": "_id"}},
"hatalar": {
"filter": {"term": {"log.level": "ERROR"}}
}
},
"size": 0
}
}
}
},
"condition": {
"script": {
"source": """
def toplam = ctx.payload.aggregations.toplam.value;
def hata = ctx.payload.aggregations.hatalar.doc_count;
if (toplam == 0) return false;
double oran = (double) hata / (double) toplam;
return oran > 0.1;
""",
"lang": "painless"
}
},
"actions": {
"email_alert": {
"email": {
"to": ["[email protected]"],
"subject": "Hata Oranı Eşiği Aşıldı",
"body": {
"text": "Son 5 dakikada hata oranı %10'u geçti. Lütfen inceleyin."
}
}
}
}
}
Script condition’da toplam log sayısı sıfır olduğunda division by zero hatasını da kontrol etmeyi unutmayın. Üretimde bunu atlayıp gece uyandırılan insanlar bilir o acıyı.
Kibana Alerting ile Modern Yaklaşım
Elastic 7.7’den itibaren Kibana kendi alert motoru olan Kibana Alerting’i getirdi. Bu Watcher’ın yerini almıyor ama daha kullanıcı dostu bir arayüz sunuyor. Stack Management > Rules and Connectors’dan yapılandırılabiliyor.
Connector oluşturmak için Kibana API kullanabiliriz:
# Slack connector oluştur
curl -X POST "https://kibana:5601/api/actions/connector"
-H "kbn-xsrf: true"
-H "Content-Type: application/json"
-u elastic:sifre
-d '{
"name": "Slack Ops Channel",
"connector_type_id": ".slack",
"config": {},
"secrets": {
"webhookUrl": "https://hooks.slack.com/services/XXX/YYY/ZZZ"
}
}'
Connector ID’yi aldıktan sonra rule oluşturalım:
# Index Threshold rule oluştur
curl -X POST "https://kibana:5601/api/alerting/rule"
-H "kbn-xsrf: true"
-H "Content-Type: application/json"
-u elastic:sifre
-d '{
"name": "Yüksek Hata Oranı Kuralı",
"rule_type_id": ".index-threshold",
"consumer": "alerts",
"schedule": {"interval": "5m"},
"params": {
"index": ["app-logs-*"],
"timeField": "@timestamp",
"aggType": "count",
"groupBy": "all",
"timeWindowSize": 5,
"timeWindowUnit": "m",
"thresholdComparator": ">",
"threshold": [50]
},
"actions": [
{
"id": "CONNECTOR_ID_BURAYA",
"group": "threshold met",
"params": {
"message": "Son 5 dakikada 50'den fazla hata: {{context.value}} adet"
}
}
]
}'
Gerçek Dünya Senaryosu: Çoklu Kanal Stratejisi
Üretim ortamlarında tek kanal yetmez. Bizim kullandığımız yapıyı anlatayım. Alertları önem seviyesine göre sınıflandırıyoruz ve her seviye farklı bir kanala gidiyor.
PUT _watcher/watch/coklu_kanal_alarm
{
"trigger": {
"schedule": {"interval": "1m"}
},
"input": {
"chain": {
"inputs": [
{
"kritik_hatalar": {
"search": {
"request": {
"indices": ["app-logs-*"],
"body": {
"query": {
"bool": {
"must": [
{"term": {"log.level": "CRITICAL"}},
{"range": {"@timestamp": {"gte": "now-1m"}}}
]
}
},
"size": 0
}
}
}
}
}
]
}
},
"condition": {
"compare": {
"ctx.payload.kritik_hatalar.hits.total.value": {"gt": 0}
}
},
"actions": {
"slack_ops": {
"slack": {
"account": "monitoring",
"message": {
"channel": "#ops-kritik",
"text": ":rotating_light: KRİTİK: {{ctx.payload.kritik_hatalar.hits.total.value}} adet kritik hata"
}
}
},
"email_yonetim": {
"email": {
"to": ["[email protected]", "[email protected]"],
"subject": "KRİTİK SİSTEM ALARMI - Hemen Müdahale Gerekli",
"body": {
"html": "<h2 style='color:red'>Kritik Sistem Alarmı</h2><p>{{ctx.payload.kritik_hatalar.hits.total.value}} adet kritik hata tespit edildi.</p>"
}
}
},
"webhook_pagerduty": {
"webhook": {
"scheme": "https",
"host": "events.pagerduty.com",
"port": 443,
"method": "post",
"path": "/v2/enqueue",
"headers": {
"Content-Type": "application/json",
"Authorization": "Token token=PAGERDUTY_API_KEY"
},
"body": "{"routing_key": "PAGERDUTY_ROUTING_KEY", "event_action": "trigger", "payload": {"summary": "Kritik hata: {{ctx.payload.kritik_hatalar.hits.total.value}} adet", "severity": "critical", "source": "elasticsearch-watcher"}}"
}
}
}
}
Bu yapıda kritik hatalar hem Slack’e hem e-postaya hem de PagerDuty’e gidiyor. PagerDuty nöbetçi mühendisi uyandırıyor, Slack ve e-posta da kalıcı kayıt için.
Watch’ları Yönetmek
Watch’larınızı komut satırından yönetmek için bazı faydalı API çağrıları:
# Tüm watch'ları listele
GET _watcher/watch/_search
# Belirli bir watch'ı görüntüle
GET _watcher/watch/error_rate_watch
# Watch'ı manuel olarak tetikle (test için çok kullanışlı)
POST _watcher/watch/error_rate_watch/_execute
{
"ignore_condition": true,
"action_modes": {
"_all": "simulate"
}
}
# Watch'ı devre dışı bırak
PUT _watcher/watch/error_rate_watch/_deactivate
# Watch'ı tekrar etkinleştir
PUT _watcher/watch/error_rate_watch/_activate
# Watch'ı sil
DELETE _watcher/watch/error_rate_watch
# Watcher istatistiklerini görüntüle
GET _watcher/stats
# Watch geçmişini sorgula
GET .watcher-history-*/_search
{
"query": {
"match": {
"watch_id": "error_rate_watch"
}
},
"sort": [{"@timestamp": {"order": "desc"}}],
"size": 10
}
Watch geçmişini sorgulamak debug sürecinde altın değerinde. Bir watch neden tetiklenmedi sorusunu çoğunlukla burada cevaplıyoruz.
Sık Yapılan Hatalar ve Çözümleri
Zaman dilimi sorunu: Elasticsearch UTC kullanıyor ama loglar local time ile geliyorsa sorgular yanlış çalışır. Index mapping’de date tipini doğru yapılandırın ve her zaman now-5m gibi relative time kullanın.
Çok hassas eşikler: gt: 1 gibi çok düşük eşikler alert fırtınası yaratır. Üretim verilerinize bakarak makul bir baseline belirleyin, sonra %20-30 üzerini eşik olarak alın.
Watch çakışması: Aynı problemi izleyen birden fazla watch varsa her ikisi de tetiklenebilir. Watch’larınızı bir envanterle takip edin.
Input timeout: Büyük veri kümeleri üzerinde yapılan sorgular zaman aşımına uğrayabilir. Input’ta request_timeout parametresini artırın.
Action başarısız ama watch yeşil görünüyor: Watch condition doğru çalışmış ama action başarısız olabilir. Watcher geçmişini düzenli kontrol edin, sadece trigger sayısına bakmayın.
Sonuç
ELK Stack’te alert yapılandırması bir kere kurup unutan türden değil. Sisteminiz değiştikçe, yük profiliniz değiştikçe, threshold’larınızı ve watch’larınızı da güncellemeniz gerekiyor. Ayda bir kez watch geçmişine bakıp hangi watch’ların hiç tetiklenmediğini, hangilerinin çok sık tetiklendiğini gözden geçirmenizi öneririm.
Kritik nokta: alert sistemini test etmeyi ihmal etmeyin. Üretim ortamında kasıtlı olarak küçük bir anomali oluşturup alertın gelip gelmediğini kontrol edin. Çalışmayan alarm sistemi, alarm sisteminin hiç olmamasından daha tehlikeli çünkü güvenli olduğunuzu sanırsınız.
PagerDuty, Opsgenie gibi harici servislerle entegrasyon da değerlendirilmesi gereken bir seçenek. Özellikle nöbet (on-call) rotasyonu olan ekipler için bu servisler Watcher’ın yapamadığı escalation ve acknowledge mekanizmaları sunuyor.
Son olarak: Alert yorgunluğu gerçek bir problem. Ekibiniz sürekli anlamsız alert alıyorsa er ya da geç tüm bildirimleri kapatmaya başlar. Kaliteli az alert, gürültülü çok alert’ten her zaman iyidir.
