Zabbix’te Webhook ile Özel Alarm Entegrasyonu Oluşturma

Bir gün gece yarısı kritik bir sunucu çöktü ve sen bundan haberdar olamadın. Sabah geldiğinde müşteri zaten seni aramış, sistemi elle yeniden başlatmak zorunda kalmış. Bu senaryoyu yaşadıysan, Zabbix’in varsayılan e-posta bildirimlerinin yeterli olmadığını anlamışsındır. İşte bu yüzden webhook entegrasyonları hayat kurtarıcı oluyor. Slack, Teams, Telegram veya kendi geliştirdiğin sistemlerle Zabbix’i konuşturmak, alarm yönetimini tamamen farklı bir boyuta taşıyor.

Bu yazıda sıfırdan bir webhook entegrasyonu nasıl kurulur, özel parametreler nasıl aktarılır ve gerçek senaryolarda nasıl kullanılır bunları adım adım ele alacağız.

Webhook Nedir ve Neden Kullanmalıyız

Zabbix’in kendi bildirim mekanizması işlevsel ama kısıtlı. E-posta gönderebilir, SMS atabilir ama modern ekipler artık Slack kanallarında çalışıyor, Teams’te koordinasyon sağlıyor, ya da kendi ticketing sistemlerine direkt entegrasyon istiyor.

Webhook, temel olarak bir HTTP POST isteği. Bir olay gerçekleştiğinde Zabbix belirli bir URL’ye JSON verisi gönderiyor, karşı taraf bu veriyi alıp işliyor. Bu kadar basit. Ama bu basitlik arkasında inanılmaz bir esneklik barındırıyor.

Webhook kullanmanın avantajları:

  • Gerçek zamanlı bildirim, e-posta gecikmesi yok
  • Özel payload formatı oluşturabilirsin
  • Birden fazla sistemi aynı anda tetikleyebilirsin
  • Alert acknowledge işlemlerini uzak sistemden yapabilirsin
  • Kendi otomasyon scriptlerini tetikleyebilirsin

Ön Hazırlık ve Ortam Kurulumu

Başlamadan önce Zabbix versiyonunu kontrol etmek önemli. Webhook media type Zabbix 4.4 ile geldi, 5.x ve 6.x sürümlerinde çok daha olgunlaştı. Bu yazıdaki örnekler Zabbix 6.x üzerinde test edildi.

# Zabbix server versiyonunu kontrol et
zabbix_server --version

# Zabbix servis durumu
systemctl status zabbix-server

# Log dosyasını izle (test sürecinde işine yarar)
tail -f /var/log/zabbix/zabbix_server.log

Test için basit bir webhook receiver kuralım. Python ile çok hızlı bir şekilde bunu yapabilirsin:

#!/usr/bin/env python3
# webhook_receiver.py
from http.server import HTTPServer, BaseHTTPRequestHandler
import json
import logging
from datetime import datetime

logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(message)s',
    handlers=[
        logging.FileHandler('/tmp/webhook_test.log'),
        logging.StreamHandler()
    ]
)

class WebhookHandler(BaseHTTPRequestHandler):
    def do_POST(self):
        content_length = int(self.headers['Content-Length'])
        post_data = self.rfile.read(content_length)
        
        try:
            payload = json.loads(post_data.decode('utf-8'))
            logging.info(f"Gelen Payload: {json.dumps(payload, indent=2, ensure_ascii=False)}")
        except json.JSONDecodeError:
            logging.error(f"JSON parse hatasi: {post_data}")
        
        self.send_response(200)
        self.send_header('Content-Type', 'application/json')
        self.end_headers()
        self.wfile.write(b'{"status": "ok"}')
    
    def log_message(self, format, *args):
        pass

if __name__ == '__main__':
    server = HTTPServer(('0.0.0.0', 8080), WebhookHandler)
    logging.info("Webhook receiver 8080 portunda dinliyor...")
    server.serve_forever()

Bu scripti çalıştır ve Zabbix’i buna yönlendir. Gerçek entegrasyona geçmeden önce payload’ın doğru geldiğini görmek çok önemli.

# Receiver'i başlat
python3 webhook_receiver.py &

# Test için curl ile deneyebilirsin
curl -X POST http://localhost:8080 
  -H "Content-Type: application/json" 
  -d '{"test": "merhaba", "severity": "high"}'

Zabbix’te Media Type Oluşturma

Şimdi asıl işe girelim. Zabbix arayüzüne gir, Administration > Media Types yolunu takip et ve Create media type butonuna tıkla.

Temel ayarlar:

  • Name: Slack-Webhook (ya da ne kullanıyorsan)
  • Type: Webhook
  • Script: Buraya JavaScript kodunu yazacağız

Zabbix, webhook scriptlerini JavaScript ile yazıyor. Bu biraz alışılmadık ama aslında oldukça güçlü. İşte temel bir webhook scripti:

var Zabbix = {
    log: function(level, message) {
        if (level <= params.debug_level) {
            Zabbix.log(level, 'Slack webhook: ' + message);
        }
    }
};

try {
    var params = JSON.parse(value);
    
    // Zorunlu parametreleri kontrol et
    if (!params.webhook_url) {
        throw 'Webhook URL parametresi eksik';
    }
    
    // Severity'ye gore renk belirle
    var color = '#439FE0'; // default mavi
    if (params.event_severity === 'High' || params.event_severity === 'Average') {
        color = '#FF4500'; // kirmizi
    } else if (params.event_severity === 'Warning') {
        color = '#FFA500'; // turuncu
    } else if (params.event_severity === 'Information') {
        color = '#36A64F'; // yesil
    }
    
    // Slack payload olustur
    var payload = {
        attachments: [
            {
                color: color,
                title: params.event_name,
                text: params.event_description,
                fields: [
                    {
                        title: 'Host',
                        value: params.host_name,
                        short: true
                    },
                    {
                        title: 'Severity',
                        value: params.event_severity,
                        short: true
                    },
                    {
                        title: 'Durum',
                        value: params.event_status,
                        short: true
                    },
                    {
                        title: 'Zaman',
                        value: params.event_time,
                        short: true
                    }
                ],
                footer: 'Zabbix Alert System',
                ts: Math.floor(Date.now() / 1000)
            }
        ]
    };
    
    var request = new HttpRequest();
    request.addHeader('Content-Type: application/json');
    
    var response = request.post(
        params.webhook_url,
        JSON.stringify(payload)
    );
    
    if (request.getStatus() !== 200) {
        throw 'HTTP ' + request.getStatus() + ' hatasi alindi. Response: ' + response;
    }
    
    return 'OK';
    
} catch (error) {
    throw 'Slack webhook hatasi: ' + error;
}

Parametre Tanımlamaları

Media Type sayfasında Parameters bölümüne şu parametreleri eklemen gerekiyor:

  • webhook_url: Slack incoming webhook URL’in, örnek: https://hooks.slack.com/services/XXX/YYY/ZZZ
  • event_name: {EVENT.NAME} makrosu
  • event_description: {EVENT.OPDATA} makrosu
  • host_name: {HOST.NAME} makrosu
  • event_severity: {EVENT.SEVERITY} makrosu
  • event_status: {EVENT.STATUS} makrosu
  • event_time: {EVENT.TIME} makrosu
  • event_id: {EVENT.ID} makrosu
  • zabbix_url: Zabbix’in dış erişim URL’i, örnek: https://zabbix.sirketin.com

Bu parametreler, script içinde params.parametre_adi şeklinde erişilen değerler. Zabbix makrolarını buraya bağlıyorsun ve her alarm tetiklendiğinde dinamik olarak dolduruluyor.

Telegram Entegrasyonu Örneği

Slack kurumsal bir seçim ama Telegram çok daha hızlı kurulabiliyor ve ücretsiz. Bot oluşturmak için @BotFather’a mesaj at, /newbot komutunu ver ve token’ını al.

try {
    var params = JSON.parse(value);
    
    if (!params.bot_token || !params.chat_id) {
        throw 'bot_token ve chat_id parametreleri zorunlu';
    }
    
    // Emoji ile severity goster
    var emoji = '';
    switch(params.event_severity) {
        case 'Disaster':    emoji = '🔴💀'; break;
        case 'High':        emoji = '🔴'; break;
        case 'Average':     emoji = '🟠'; break;
        case 'Warning':     emoji = '🟡'; break;
        case 'Information': emoji = '🔵'; break;
        case 'Not classified': emoji = '⚪'; break;
        default: emoji = '⚠️';
    }
    
    // Resolved durumu icin yesil
    if (params.event_status === 'RESOLVED') {
        emoji = '✅';
    }
    
    var message = emoji + ' *' + params.event_status + '*n';
    message += '━━━━━━━━━━━━━━━━n';
    message += '📌 *Problem:* ' + params.event_name + 'n';
    message += '🖥 *Host:* `' + params.host_name + '`n';
    message += '📊 *Severity:* ' + params.event_severity + 'n';
    message += '🕐 *Zaman:* ' + params.event_date + ' ' + params.event_time + 'n';
    
    if (params.event_opdata && params.event_opdata !== '*UNKNOWN*') {
        message += '📈 *Deger:* ' + params.event_opdata + 'n';
    }
    
    message += 'n🔗 [Zabbix'te Goruntule](' + params.zabbix_url + 
               '/tr/zabbix.php?action=problem.view&filter_triggerid=' + 
               params.trigger_id + ')';
    
    var url = 'https://api.telegram.org/bot' + params.bot_token + '/sendMessage';
    
    var payload = {
        chat_id: params.chat_id,
        text: message,
        parse_mode: 'Markdown',
        disable_web_page_preview: true
    };
    
    var request = new HttpRequest();
    request.addHeader('Content-Type: application/json');
    
    var response = request.post(url, JSON.stringify(payload));
    var result = JSON.parse(response);
    
    if (!result.ok) {
        throw 'Telegram API hatasi: ' + result.description;
    }
    
    return 'Telegram mesaji gonderildi. Message ID: ' + result.result.message_id;
    
} catch (error) {
    throw 'Telegram webhook hatasi: ' + error;
}

Özel Ticketing Sistemi Entegrasyonu

Gerçek dünya senaryosu: Şirket içi bir ITSM veya helpdesk sistemin var ve Zabbix alarmları otomatik olarak ticket açsın istiyorsun. Bu senaryo için daha gelişmiş bir webhook yazalım:

try {
    var params = JSON.parse(value);
    
    // Sadece problem durumunda ticket ac, resolved'da kapatma islemi yap
    var method = 'POST';
    var endpoint = params.api_url + '/tickets';
    
    var headers = {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer ' + params.api_token,
        'X-Source': 'Zabbix'
    };
    
    var ticketData;
    
    if (params.event_status === 'PROBLEM') {
        // Yeni ticket olustur
        ticketData = {
            title: '[ZABBIX] ' + params.event_name,
            description: 'Host: ' + params.host_name + 'n' +
                        'IP: ' + params.host_ip + 'n' +
                        'Severity: ' + params.event_severity + 'n' +
                        'Zaman: ' + params.event_date + ' ' + params.event_time + 'nn' +
                        'Detay: ' + params.event_opdata,
            priority: params.event_severity === 'Disaster' ? 'critical' :
                     params.event_severity === 'High' ? 'high' :
                     params.event_severity === 'Average' ? 'medium' : 'low',
            tags: ['monitoring', 'zabbix', params.event_severity.toLowerCase()],
            custom_fields: {
                zabbix_event_id: params.event_id,
                zabbix_host: params.host_name,
                monitoring_source: 'zabbix'
            }
        };
    } else {
        // Ticket'i coz olarak isaretleme - event_id ile eslestir
        endpoint = params.api_url + '/tickets/resolve';
        ticketData = {
            zabbix_event_id: params.event_id,
            resolution_note: 'Zabbix tarafindan otomatik cozuldu. ' + 
                           'Cozum zamani: ' + params.event_recovery_time,
            status: 'resolved'
        };
    }
    
    var request = new HttpRequest();
    
    for (var header in headers) {
        request.addHeader(header + ': ' + headers[header]);
    }
    
    var response = request.post(endpoint, JSON.stringify(ticketData));
    var statusCode = request.getStatus();
    
    if (statusCode !== 200 && statusCode !== 201) {
        throw 'API hatasi - HTTP ' + statusCode + ': ' + response;
    }
    
    var result = JSON.parse(response);
    
    Zabbix.log(4, 'Ticket islemi basarili. Ticket ID: ' + (result.id || 'N/A'));
    
    return JSON.stringify({
        status: 'success',
        ticket_id: result.id || null,
        action: params.event_status === 'PROBLEM' ? 'created' : 'resolved'
    });
    
} catch (error) {
    throw 'Ticketing entegrasyon hatasi: ' + error;
}

Action Konfigürasyonu

Media type’ı oluşturduktan sonra kullanıcıya ataman ve action oluşturman gerekiyor.

Kullanıcı Media Ayarı: Önce admin kullanıcısına ya da ilgili kullanıcıya media ekle. Administration > Users yoluna git, kullanıcıyı seç, Media sekmesine geç ve az önce oluşturduğun media type’ı ekle.

Action Oluşturma: Configuration > Actions > Trigger actions yoluna git. Yeni action oluştur:

  • Name: Kritik Sunucu Alarmları – Webhook
  • Conditions: Severity is greater than or equal to Average
  • Operations bölümü: Send message to users, Webhook media type’ını seç

Recovery ve Acknowledge operasyonlarını da eklemeyi unutma. Problem çözüldüğünde de bildirim gitmesini istiyorsun çünkü.

Test ve Debug Süreci

En sinir bozucu kısım burası. Script çalışmıyor ama neden çalışmıyor anlamak zor. İşte debug sürecini kolaylaştıran yaklaşımlar:

# Zabbix server loglarını filtrele
grep -i "webhook|media|alert" /var/log/zabbix/zabbix_server.log | tail -50

# Real-time log takibi
tail -f /var/log/zabbix/zabbix_server.log | grep -i "alert|webhook"

# Zabbix alerter process sayisini artir (yuk varsa)
grep "AlertManager|StartAlerters" /etc/zabbix/zabbix_server.conf

# Test maili gondermek yerine webhook'u test et - arayuzden Test dugmesini kullan
# Ama once connectivity kontrol et:
curl -v -X POST https://hooks.slack.com/services/YOUR/WEBHOOK/URL 
  -H "Content-Type: application/json" 
  -d '{"text":"Zabbix test mesaji"}'

Media type sayfasındaki Test butonu gerçekten çok işe yarıyor. Oraya tıkladığında parametre değerlerini manuel girebilir ve script’i anında test edebilirsin. Çıktı ve hata mesajları da ekranda görünüyor.

Yaygın Hatalar ve Çözümleri:

  • SSL sertifika hatası: Zabbix server’ın dış URL’lere erişebildiğinden emin ol. Proxy arkasındaysan http_proxy environment variable’ı set et.
  • Timeout hatası: Script içinde HttpRequest’in timeout süresini ayarla: request.setTimeout(5000)
  • JSON parse hatası: value parametresinin formatını kontrol et, bazen Zabbix makroları özel karakterler içerebilir.
  • 422 Unprocessable Entity: Gönderdiğin payload’ın karşı API’nin beklediği formatla eşleşip eşleşmediğini kontrol et.

Gelişmiş Senaryo: Maintenance Window Kontrolü

Gerçek hayatta en büyük sorunlardan biri bakım penceresi sırasında alarm yağmasıdır. Webhook scripti içine bu kontrolü ekleyebilirsin:

try {
    var params = JSON.parse(value);
    
    // Maintenance modunda bildirim gonderme
    if (params.host_maintenance_status === '1') {
        Zabbix.log(4, 'Host bakimda, bildirim atlanıyor: ' + params.host_name);
        return 'SKIPPED: Host maintenance modunda';
    }
    
    // Geceli alarmlar icin sadece Disaster ve High gonder
    var hour = parseInt(new Date().getHours());
    var isNightTime = (hour >= 23 || hour < 7);
    
    if (isNightTime) {
        var criticalSeverities = ['Disaster', 'High'];
        if (criticalSeverities.indexOf(params.event_severity) === -1) {
            Zabbix.log(4, 'Gece saati, dusuk severity bildirim atlanıyor');
            return 'SKIPPED: Gece saati filtresi';
        }
    }
    
    // Normal bildirim akisi buradan devam eder
    var request = new HttpRequest();
    request.addHeader('Content-Type: application/json');
    
    var payload = {
        text: '🚨 ' + params.event_severity + ': ' + params.event_name,
        host: params.host_name,
        time: params.event_time
    };
    
    var response = request.post(params.webhook_url, JSON.stringify(payload));
    
    if (request.getStatus() !== 200) {
        throw 'HTTP ' + request.getStatus();
    }
    
    return 'OK';
    
} catch (error) {
    throw error;
}

Birden Fazla Kanala Yönlendirme

Farklı severity seviyeleri için farklı kanallar kullanmak iyi bir pratik. Disaster alarmları hem Slack’e hem Telegram’a hem de SMS sistemine gitsin, Warning sadece Slack genel kanalına gitsin gibi.

Bunun için birden fazla media type oluşturursun ya da tek script içinde routing yaparsın:

// Severity'ye gore farkli webhook URL'leri kullan
var webhookUrl;

switch(params.event_severity) {
    case 'Disaster':
        webhookUrl = params.critical_webhook_url; // onceligli kanal
        break;
    case 'High':
    case 'Average':
        webhookUrl = params.warning_webhook_url;
        break;
    default:
        webhookUrl = params.info_webhook_url;
}

// Disaster ise ikinci bir kanala da gonder
if (params.event_severity === 'Disaster') {
    var secondRequest = new HttpRequest();
    secondRequest.addHeader('Content-Type: application/json');
    secondRequest.post(
        params.oncall_webhook_url,
        JSON.stringify({text: 'KRITIK ALARM: ' + params.event_name + ' - ' + params.host_name})
    );
}

Sonuç

Zabbix webhook entegrasyonu, izleme sistemini gerçek anlamda hayata bağlayan şey. Varsayılan e-posta bildirimleriyle başlayabilirsin ama ekibinin çalışma şekline uygun, özelleştirilmiş bir alarm akışı kurmak hem tepki süreni kısaltır hem de alarm yorgunluğunu azaltır.

Önemli noktalara bakacak olursak: Önce basit başla, tek bir kanal, temel parametreler. Çalıştığını doğrula, sonra karmaşıklaştır. Severity filtreleri, maintenance window kontrolleri, çoklu kanal yönlendirmesi hepsini zamanla eklersin. Script’leri versiyon kontrolüne al, production’a atmadan test ortamında dene. Zabbix’in kendi Test butonunu sonuna kadar kullan.

Gece yarısı telefona bakıp Telegram’da kırmızı alarm mesajı görmek, sabah gelip sistemi çökmüş bulmaktan her zaman daha iyidir. Biraz zaman harcayıp bu entegrasyonu kurduğunda, geri dönüp “bunu neden daha önce yapmadım” diyeceksin. Söz.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir