Webhook ile Monitoring Sistemi Entegrasyonu
Monitoring sistemleri güzel çalışıyor, alert’ler geliyor, dashboard’lar yeşil yanıyor… ama bir problem var: her şey izole. Grafana kendi köşesinde, Prometheus kendi dünyasında, Slack kanalınız da tamamen habersiz. İşte tam bu noktada webhook entegrasyonu devreye giriyor ve monitoring stack’inizi gerçekten konuşan bir sisteme dönüştürüyor.
Bu yazıda production ortamında webhook tabanlı monitoring entegrasyonunu nasıl kurduğumu, hangi hatalarla karşılaştığımı ve nasıl aştığımı anlatacağım. Teorik değil, elleri kirletilmiş notlar bunlar.
Webhook Mantığını Anlamak
Webhook, en basit tanımıyla “bir olay gerçekleştiğinde şu URL’e HTTP POST at” demek. Polling’in tersine, siz sistemi sürekli sorgulamak yerine sistem sizi bilgilendiriyor. Monitoring bağlamında bu şu anlama geliyor: CPU %95’e çıktığında Grafana beklemeden Slack’e, PagerDuty’e veya kendi yazdığınız bir servise haber gönderiyor.
Basit görünüyor, ama üretim ortamında bunu güvenilir hale getirmek ciddi iş. Retry mekanizmaları, secret doğrulama, payload şeması, rate limiting… hepsini düşünmek gerekiyor.
Temel Webhook Alıcı Servisi Yazmak
İlk adım olarak kendi webhook alıcınızı yazın. Bu sayede neyin geldiğini tam anlarsınız. Ben genellikle Python Flask ile başlıyorum:
pip install flask requests python-dotenv
cat > webhook_receiver.py << 'EOF'
from flask import Flask, request, jsonify
import hmac
import hashlib
import json
import logging
from datetime import datetime
app = Flask(__name__)
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
WEBHOOK_SECRET = "supersecretkey123"
def verify_signature(payload_body, signature_header):
"""Grafana veya Alertmanager'dan gelen imzayi dogrula"""
if not signature_header:
return False
expected = hmac.new(
WEBHOOK_SECRET.encode('utf-8'),
payload_body,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(f"sha256={expected}", signature_header)
@app.route('/webhook/alerts', methods=['POST'])
def receive_alert():
# Imza dogrulama
signature = request.headers.get('X-Grafana-Signature', '')
if not verify_signature(request.data, signature):
logger.warning("Gecersiz imza, istek reddedildi")
return jsonify({"error": "Unauthorized"}), 401
payload = request.get_json()
logger.info(f"Alert alindi: {json.dumps(payload, indent=2)}")
# Alert islemini buradan tetikle
process_alert(payload)
return jsonify({"status": "received"}), 200
def process_alert(payload):
alerts = payload.get('alerts', [])
for alert in alerts:
status = alert.get('status', 'unknown')
name = alert.get('labels', {}).get('alertname', 'Bilinmiyor')
severity = alert.get('labels', {}).get('severity', 'info')
logger.info(f"[{severity.upper()}] {name} - Durum: {status}")
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5001, debug=False)
EOF
Bu servisi systemd ile ayağa kaldırmak en temizi:
cat > /etc/systemd/system/webhook-receiver.service << 'EOF'
[Unit]
Description=Webhook Receiver Service
After=network.target
[Service]
Type=simple
User=webhook
WorkingDirectory=/opt/webhook
ExecStart=/usr/bin/python3 /opt/webhook/webhook_receiver.py
Restart=always
RestartSec=5
Environment=PYTHONUNBUFFERED=1
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now webhook-receiver
Grafana Alerting’i Webhook’a Bağlamak
Grafana 9+ ile Unified Alerting kullanıyorsanız yapılandırma şöyle:
Contact Points bölümünden yeni bir webhook contact point ekleyin. URL olarak http://your-server:5001/webhook/alerts girin.
Ama önemli bir nokta: Grafana’nın gönderdiği payload formatını tam bilmeden iş yapmaya çalışmak vakit kaybı. Önce bir test isteği yapın:
curl -X POST http://localhost:5001/webhook/alerts
-H "Content-Type: application/json"
-H "X-Grafana-Signature: test"
-d '{
"receiver": "webhook-test",
"status": "firing",
"alerts": [
{
"status": "firing",
"labels": {
"alertname": "HighCPU",
"severity": "critical",
"instance": "web-server-01:9100"
},
"annotations": {
"summary": "CPU kullanimi yuzde 95 ustu",
"description": "web-server-01 makinesi son 5 dakikadır yuzde 95 uzerinde CPU kullaniyor"
},
"startsAt": "2024-01-15T10:30:00Z"
}
]
}'
Grafana tarafında da Notification Policies kısmında bu contact point’i doğru alert’lere yönlendirdiğinizden emin olun. Severity etiketine göre routing yapmak çok işe yarıyor.
Prometheus Alertmanager ile Webhook Entegrasyonu
Alertmanager doğrudan Prometheus alert’lerini webhook’a iletmek için çok daha güçlü bir araç. alertmanager.yml yapılandırması:
cat > /etc/alertmanager/alertmanager.yml << 'EOF'
global:
resolve_timeout: 5m
http_config:
follow_redirects: true
route:
receiver: 'default-webhook'
group_by: ['alertname', 'cluster', 'service']
group_wait: 30s
group_interval: 5m
repeat_interval: 4h
routes:
- match:
severity: critical
receiver: 'critical-webhook'
continue: true
- match:
severity: warning
receiver: 'warning-webhook'
receivers:
- name: 'default-webhook'
webhook_configs:
- url: 'http://localhost:5001/webhook/alerts'
send_resolved: true
http_config:
bearer_token: 'your-bearer-token'
max_alerts: 10
- name: 'critical-webhook'
webhook_configs:
- url: 'http://localhost:5001/webhook/critical'
send_resolved: true
timeout: 10s
- name: 'warning-webhook'
webhook_configs:
- url: 'http://localhost:5001/webhook/warnings'
send_resolved: false
inhibit_rules:
- source_match:
severity: 'critical'
target_match:
severity: 'warning'
equal: ['alertname', 'instance']
EOF
alertmanager --config.file=/etc/alertmanager/alertmanager.yml --check-config
inhibit_rules kısmı çok kritik. Critical bir alert varken aynı instance için warning gönderme demek. Yoksa alarm bombardımanına tutulursunuz.
Slack Entegrasyonu: Pratik Yaklaşım
Slack incoming webhook URL’inizi aldıktan sonra webhook alıcınızı Slack’e bağlayın. Ben bu işi doğrudan Alertmanager üzerinden değil, kendi servisim üzerinden yapıyorum çünkü mesajı özelleştirme ihtiyacı kaçınılmaz oluyor:
cat > slack_notifier.py << 'EOF'
import requests
import json
from datetime import datetime
SLACK_WEBHOOK_URL = "https://hooks.slack.com/services/XXXXX/YYYYY/ZZZZZ"
SEVERITY_COLORS = {
"critical": "#FF0000",
"warning": "#FFA500",
"info": "#36a64f",
"resolved": "#2ecc71"
}
SEVERITY_EMOJIS = {
"critical": ":red_circle:",
"warning": ":large_yellow_circle:",
"info": ":large_green_circle:",
"resolved": ":white_check_mark:"
}
def send_slack_alert(alert_data):
status = alert_data.get('status', 'unknown')
labels = alert_data.get('labels', {})
annotations = alert_data.get('annotations', {})
severity = labels.get('severity', 'info')
if status == 'resolved':
severity = 'resolved'
alertname = labels.get('alertname', 'Bilinmeyen Alert')
instance = labels.get('instance', 'Bilinmiyor')
summary = annotations.get('summary', 'Detay yok')
description = annotations.get('description', '')
emoji = SEVERITY_EMOJIS.get(severity, ':question:')
color = SEVERITY_COLORS.get(severity, '#888888')
payload = {
"attachments": [
{
"color": color,
"blocks": [
{
"type": "header",
"text": {
"type": "plain_text",
"text": f"{emoji} {alertname}"
}
},
{
"type": "section",
"fields": [
{
"type": "mrkdwn",
"text": f"*Durum:*n{status.upper()}"
},
{
"type": "mrkdwn",
"text": f"*Sunucu:*n{instance}"
},
{
"type": "mrkdwn",
"text": f"*Ozet:*n{summary}"
},
{
"type": "mrkdwn",
"text": f"*Zaman:*n{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
}
]
}
]
}
]
}
if description:
payload["attachments"][0]["blocks"].append({
"type": "section",
"text": {
"type": "mrkdwn",
"text": f"*Detay:*n{description}"
}
})
response = requests.post(
SLACK_WEBHOOK_URL,
data=json.dumps(payload),
headers={'Content-Type': 'application/json'},
timeout=10
)
if response.status_code != 200:
raise Exception(f"Slack isteği başarisiz: {response.status_code}, {response.text}")
return True
EOF
Retry ve Güvenilirlik Mekanizması
Production’da webhook’ların kaybolması gerçek bir problem. Karşı taraf geçici olarak erişilemez olabilir, timeout olabilir. Bu yüzden retry mekanizması şart:
cat > reliable_webhook.py << 'EOF'
import time
import requests
import logging
from functools import wraps
logger = logging.getLogger(__name__)
def retry_with_backoff(max_retries=3, backoff_factor=2, exceptions=(requests.exceptions.RequestException,)):
"""Exponential backoff ile retry decorator"""
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
last_exception = None
for attempt in range(max_retries):
try:
return func(*args, **kwargs)
except exceptions as e:
last_exception = e
wait_time = backoff_factor ** attempt
logger.warning(
f"Deneme {attempt + 1}/{max_retries} başarisiz: {e}. "
f"{wait_time} saniye bekleniyor..."
)
time.sleep(wait_time)
logger.error(f"Tum denemeler bitti. Son hata: {last_exception}")
raise last_exception
return wrapper
return decorator
@retry_with_backoff(max_retries=3, backoff_factor=2)
def send_webhook(url, payload, headers=None, timeout=10):
"""Guvenilir webhook gonderici"""
default_headers = {
'Content-Type': 'application/json',
'User-Agent': 'MonitoringWebhook/1.0'
}
if headers:
default_headers.update(headers)
response = requests.post(
url,
json=payload,
headers=default_headers,
timeout=timeout
)
# 5xx hatalari retry icin exception firlatir
if response.status_code >= 500:
raise requests.exceptions.HTTPError(
f"Server hatasi: {response.status_code}"
)
# 4xx hatalari retry yapma, dogrudan logla
if response.status_code >= 400:
logger.error(f"Client hatasi {response.status_code}: {response.text}")
return False
return True
EOF
Nginx ile Webhook Proxy ve Rate Limiting
Webhook alıcınızı doğrudan internete açmak yerine Nginx arkasına alın ve rate limiting ekleyin. Bu hem güvenlik hem de DDoS koruması açısından önemli:
cat > /etc/nginx/sites-available/webhook << 'EOF'
# Rate limiting zone tanimla
limit_req_zone $binary_remote_addr zone=webhook_limit:10m rate=30r/m;
upstream webhook_backend {
server 127.0.0.1:5001;
keepalive 8;
}
server {
listen 443 ssl http2;
server_name webhook.yourcompany.com;
ssl_certificate /etc/ssl/certs/webhook.crt;
ssl_certificate_key /etc/ssl/private/webhook.key;
ssl_protocols TLSv1.2 TLSv1.3;
# Sadece POST kabul et
location /webhook/ {
limit_req zone=webhook_limit burst=10 nodelay;
limit_req_status 429;
# Sadece bilinen IP'lerden kabul et (Grafana ve Alertmanager IP'leri)
allow 10.0.0.10; # Grafana sunucusu
allow 10.0.0.11; # Alertmanager sunucusu
deny all;
if ($request_method != POST) {
return 405;
}
# Maksimum payload boyutu: 1MB
client_max_body_size 1m;
proxy_pass http://webhook_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_connect_timeout 5s;
proxy_read_timeout 10s;
}
}
EOF
nginx -t && systemctl reload nginx
Gerçek Dünya Senaryosu: Disk Dolması Otomasyonu
Sadece bildirim göndermekle kalmayan, aksiyon alan bir senaryo anlatayım. Disk %90’ı aştığında hem Slack’e bildirim gidip hem de otomatik log temizleme başlasın:
cat > /opt/webhook/handlers/disk_handler.py << 'EOF'
import subprocess
import logging
import os
from slack_notifier import send_slack_alert
logger = logging.getLogger(__name__)
def handle_disk_alert(alert):
labels = alert.get('labels', {})
instance = labels.get('instance', '')
mountpoint = labels.get('mountpoint', '/')
severity = labels.get('severity', 'warning')
logger.info(f"Disk alert isleniyor: {instance} - {mountpoint}")
# Once bildirimi gonder
send_slack_alert(alert)
# Critical ise otomatik temizlik baslat
if severity == 'critical' and mountpoint == '/var/log':
logger.info(f"Otomatik log temizleme basliyor: {instance}")
cleanup_result = run_log_cleanup(instance, mountpoint)
# Temizlik sonucunu raporla
follow_up = {
'status': 'info',
'labels': {
'alertname': 'AutoCleanupCompleted',
'instance': instance,
'severity': 'info'
},
'annotations': {
'summary': 'Otomatik log temizleme tamamlandi',
'description': f"Temizlenen alan: {cleanup_result.get('freed_mb', 0)} MB"
}
}
send_slack_alert(follow_up)
def run_log_cleanup(instance, mountpoint):
"""
Dikkat: Bunu production'da cok iyi test edin!
Bu ornek sadece yerel makinede calisir.
Remote cleanup icin SSH veya Ansible kullanin.
"""
freed_mb = 0
try:
# 7 gunden eski compressed log'lari sil
result = subprocess.run(
['find', mountpoint, '-name', '*.gz', '-mtime', '+7', '-delete', '-print'],
capture_output=True,
text=True,
timeout=60
)
deleted_files = result.stdout.strip().split('n') if result.stdout.strip() else []
logger.info(f"{len(deleted_files)} dosya silindi")
# journalctl log boyutunu kisit
subprocess.run(
['journalctl', '--vacuum-time=7d'],
capture_output=True,
timeout=30
)
freed_mb = len(deleted_files) * 5 # kabaca tahmin
except subprocess.TimeoutExpired:
logger.error("Temizleme islemi zaman asimina ugradi")
except Exception as e:
logger.error(f"Temizleme hatasi: {e}")
return {'freed_mb': freed_mb}
EOF
Webhook Loglarını İzlemek
Webhook trafiğini kayıt altına almadan yönetmek kör uçmak gibi. Basit bir log rotasyonu ve analiz scripti:
cat > /opt/webhook/analyze_logs.sh << 'EOF'
#!/bin/bash
# Son 24 saatteki webhook istatistikleri
LOG_FILE="/var/log/webhook/receiver.log"
DATE=$(date -d "yesterday" +%Y-%m-%d)
echo "=== Webhook Istatistikleri: $DATE ==="
echo ""
echo "Toplam istek sayisi:"
grep "$DATE" "$LOG_FILE" | grep "Alert alindi" | wc -l
echo ""
echo "Severity dagilimi:"
grep "$DATE" "$LOG_FILE" | grep -oP 'severity.*?(w+)' | sort | uniq -c | sort -rn
echo ""
echo "En cok alert ureten instance'lar:"
grep "$DATE" "$LOG_FILE" | grep -oP 'instance": "K[^"]+' | sort | uniq -c | sort -rn | head -10
echo ""
echo "Basarisiz webhook gonderim sayisi:"
grep "$DATE" "$LOG_FILE" | grep -c "ERROR|FAILED"
EOF
chmod +x /opt/webhook/analyze_logs.sh
echo "0 8 * * * /opt/webhook/analyze_logs.sh | mail -s 'Webhook Raporu' [email protected]" | crontab -
Yaygın Sorunlar ve Çözümleri
Production’da defalarca karşılaştığım sorunları listeleyelim:
Imza dogrulaması sürekli başarısız oluyor:
- Alertmanager ve Grafana farklı secret formatları kullanır. Hangisinin ne gönderdiğini wireshark veya tcpdump ile kontrol edin.
tcpdump -i lo -A port 5001ile lokal trafiği izleyin.
Alert’ler bazen gelmiyor:
- Timeout değerlerini artırın. Flask’ın varsayılan timeout’u çok düşük olabilir.
group_waitvegroup_intervaldeğerlerini Alertmanager’da gözden geçirin.- Webhook alıcınız bir exception fırlatıyorsa 500 döner ve Alertmanager retry yapar ama bu loglanmıyorsa fark etmezsiniz.
Duplicate alert’ler geliyor:
inhibit_rulestanımlarınızı kontrol edin.repeat_intervaldeğeri çok düşük ayarlanmış olabilir.continue: truekullandığınız route’larda dikkatli olun, aynı alert birden fazla receiver’a gidebilir.
Rate limit aşımı:
- Nginx’te
burstdeğerini artırın ama dikkatli olun, bir incident sırasında onlarca alert gelebilir. - Alertmanager’da
group_byile alert’leri gruplandırarak tek mesaj haline getirin.
Monitoring Stack Sağlığını İzlemek
Bir de “monitoring’i kim izleyecek” sorusu var. Webhook alıcısının kendisinin çalışıp çalışmadığını kontrol etmek için Prometheus’a metric’ler ekleyin:
cat >> webhook_receiver.py << 'EOF'
from prometheus_client import Counter, Histogram, generate_latest, CONTENT_TYPE_LATEST
WEBHOOK_RECEIVED = Counter('webhook_alerts_received_total', 'Toplam alinan alert sayisi', ['status', 'severity'])
WEBHOOK_PROCESSING_TIME = Histogram('webhook_processing_seconds', 'Alert isleme suresi')
@app.route('/metrics')
def metrics():
return generate_latest(), 200, {'Content-Type': CONTENT_TYPE_LATEST}
# process_alert fonksiyonunu guncelle:
def process_alert_with_metrics(payload):
with WEBHOOK_PROCESSING_TIME.time():
alerts = payload.get('alerts', [])
for alert in alerts:
status = alert.get('status', 'unknown')
severity = alert.get('labels', {}).get('severity', 'info')
WEBHOOK_RECEIVED.labels(status=status, severity=severity).inc()
EOF
Sonra Prometheus’a bu endpoint’i scrape ettirirsiniz ve webhook alıcınızın sağlığını da Grafana’dan izlemiş olursunuz. Döngüsel bir güzellik.
Sonuç
Webhook tabanlı monitoring entegrasyonu başta basit görünüyor: “URL’e POST at, tamam.” Ama güvenilir, güvenli ve sürdürülebilir bir sistem kurmak gerçekten dikkat istiyor.
En kritik üç nokta:
- Imza dogrulaması hiç atlanmamalı. Secret olmayan bir webhook, internete açık bir API endpoint’inden farksız.
- Retry mekanizması olmadan alert kaybedersiniz. Murphy yasası gereği, tam kritik bir incident sırasında karşı taraf geçici olarak erişilemez olur.
- Alert gruplama kurulmadan alert bombardımanı kaçınılmaz. Alertmanager’ın
group_byveinhibit_rulesözelliklerini iyi öğrenin.
Bu sistemi bir kez doğru kurduğunuzda, monitoring stack’iniz sadece izlemekle kalmayıp gerçekten konuşmaya ve hatta aksiyon almaya başlıyor. Gece 3’te telefonunuz çaldığında ne olduğunu bilmek, nereye bakacağınızı bilmek ve belki de sorunun yarısı zaten çözülmüş olmak… bu konforu bir kez tatttıktan sonra webhook olmadan monitoring yapmak garip geliyor.
