Zabbix’te Custom Script ile Özel Metrik İzleme
Zabbix’in hazır şablonları gerçekten çok işe yarıyor, bunu kimse inkar edemez. Ama bir noktada kaçınılmaz olarak karşılaşıyorsunuz o soruyla: “Bu metriği nasıl izlerim?” Standart template’lerde yok, community’de benzer bir şey var ama tam istediğiniz gibi değil, ya da kurumunuza özgü bir uygulama var ve onun sağlığını takip etmeniz gerekiyor. İşte tam bu noktada custom script mekanizması devreye giriyor ve Zabbix’i gerçek anlamda güçlü kılan özelliklerden biri haline geliyor.
Bu yazıda UserParameter kavramından başlayıp, production ortamında gerçekten kullandığım senaryolara kadar götüreceğim sizi. Teorik kalmamaya özen göstereceğim çünkü Zabbix dokümantasyonunu zaten okuyabilirsiniz, burada amaç o boşlukları doldurmak.
UserParameter Nedir ve Nasıl Çalışır
Zabbix agent, sunucudan veri toplarken bir dizi built-in key kullanır: system.cpu.load, vm.memory.size, vfs.file.size gibi. Bunlar agent içine gömülü. UserParameter ise size kendi key’lerinizi tanımlama imkanı veriyor.
Mantık basit: Bir key tanımlıyorsunuz, o key çağrıldığında bir komut veya script çalışıyor, script’in stdout’a yazdığı şey Zabbix’e metrik olarak gönderiliyor.
Konfigürasyon /etc/zabbix/zabbix_agentd.conf içine ya da daha temiz bir yöntem olarak /etc/zabbix/zabbix_agentd.d/ dizinine ayrı bir .conf dosyası olarak ekleniyor.
# /etc/zabbix/zabbix_agentd.d/custom_metrics.conf
UserParameter=custom.disk.iops,iostat -d -x 1 1 | awk '/^sda/ {print $4+$5}'
UserParameter=app.queue.length,/usr/local/bin/check_queue.sh
UserParameter=db.active.connections,psql -U monitor -t -c "SELECT count(*) FROM pg_stat_activity WHERE state='active';" | tr -d ' '
Parametreli kullanım da mümkün, $1, $2 şeklinde argüman alabiliyorsunuz:
UserParameter=custom.process.memory[*],ps aux | awk -v proc="$1" '$11 ~ proc {sum += $6} END {print sum}'
Bu şekilde Zabbix item’ında custom.process.memory[nginx] ya da custom.process.memory[java] diyerek aynı script’i farklı parametrelerle çağırabiliyorsunuz. Tek bir UserParameter tanımıyla onlarca item üretebilirsiniz, bu ciddi bir esneklik.
Ortamı Doğru Kurmak
Script yazdınız, agent’a tanıttınız ama çalışmıyor. Bu durumun en yaygın nedeni birkaç şey:
Agent default olarak zabbix kullanıcısıyla çalışır. Yazdığınız script root yetkisi gerektiriyorsa sorun çıkar. Bunu test etmek için:
sudo -u zabbix /usr/local/bin/check_queue.sh
Eğer bu komut hata veriyorsa agent da hata verecektir. Çözüm yolları: script’i sudoers’a eklemek, script’in sahipliğini ayarlamak ya da mümkünse root yetkisi gerektirmeyen bir yöntem bulmak.
# /etc/sudoers.d/zabbix
zabbix ALL=(ALL) NOPASSWD: /usr/local/bin/check_queue.sh
zabbix ALL=(ALL) NOPASSWD: /sbin/ethtool
Bir diğer önemli nokta timeout. Agent’ın varsayılan timeout değeri 3 saniyedir. Script’iniz 3 saniyeden uzun sürüyorsa agent o isteği keser ve Zabbix’te “ZBX_NOTSUPPORTED” hatası görürsünüz. Timeout=10 şeklinde artırabilirsiniz ama dikkatli olun, her item için bu süre bekleniyorsa performans sorunları çıkabilir. Script’lerinizi optimize edin.
Script’leri test etmenin en hızlı yolu zabbix_agentd -t komutudur:
zabbix_agentd -t custom.disk.iops
zabbix_agentd -t custom.process.memory[nginx]
Bu komut agent’ı başlatmadan o key’i test etmenizi sağlar. Geliştirme sürecinde vazgeçilmez.
Gerçek Senaryo 1: Java Uygulama Sunucusu Heap Kullanımı
Bir e-ticaret firmasında çalışırken Java tabanlı bir servis vardı, periyodik olarak bellek sızıntısına giriyordu. Standart memory metrikleri yeterliydi ama JVM heap’i ayrıca izlemek istedik. JMX üzerinden yapılabilir ama basit bir alternatif de var: JVM jstat aracı.
#!/bin/bash
# /usr/local/bin/check_jvm_heap.sh
# Kullanim: check_jvm_heap.sh <pid_dosyasi> <parametre>
# Parametre: used_mb, max_mb, percent
PID_FILE=$1
PARAM=$2
if [ ! -f "$PID_FILE" ]; then
echo "0"
exit 0
fi
PID=$(cat "$PID_FILE")
if ! kill -0 "$PID" 2>/dev/null; then
echo "0"
exit 0
fi
HEAP_INFO=$(jstat -gc "$PID" 2>/dev/null | tail -1)
if [ -z "$HEAP_INFO" ]; then
echo "0"
exit 0
fi
# S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
EDEN_USED=$(echo "$HEAP_INFO" | awk '{print $6}')
OLD_USED=$(echo "$HEAP_INFO" | awk '{print $8}')
OLD_CAPACITY=$(echo "$HEAP_INFO" | awk '{print $7}')
case "$PARAM" in
"used_mb")
echo "scale=2; ($EDEN_USED + $OLD_USED) / 1024" | bc
;;
"old_percent")
echo "scale=2; ($OLD_USED / $OLD_CAPACITY) * 100" | bc
;;
*)
echo "0"
;;
esac
UserParameter tanımı:
UserParameter=jvm.heap[*],/usr/local/bin/check_jvm_heap.sh $1 $2
Zabbix’te item key olarak jvm.heap[/var/run/myapp.pid,old_percent] şeklinde kullanıyorsunuz. Old generation %85’i geçince alarm kuruyorsunuz, bu kadar.
Gerçek Senaryo 2: SSL Sertifika Geçerlilik Günü
Bu çok klasik ama hala insanların gözünden kaçabiliyor. Prod ortamında sertifika bitmiş, servis aşağıda, müşteriler erişemiyor. Bu script bunu önlüyor:
#!/bin/bash
# /usr/local/bin/check_ssl_expiry.sh
# Kullanim: check_ssl_expiry.sh <hostname> <port>
# Döndürür: Kalan gün sayisi (negatif = süresi geçmiş)
HOSTNAME=$1
PORT=${2:-443}
if [ -z "$HOSTNAME" ]; then
echo "-1"
exit 1
fi
EXPIRY_DATE=$(echo | timeout 5 openssl s_client -servername "$HOSTNAME"
-connect "${HOSTNAME}:${PORT}" 2>/dev/null |
openssl x509 -noout -enddate 2>/dev/null |
cut -d= -f2)
if [ -z "$EXPIRY_DATE" ]; then
echo "-1"
exit 0
fi
EXPIRY_EPOCH=$(date -d "$EXPIRY_DATE" +%s 2>/dev/null)
NOW_EPOCH=$(date +%s)
DAYS_LEFT=$(( (EXPIRY_EPOCH - NOW_EPOCH) / 86400 ))
echo "$DAYS_LEFT"
UserParameter=ssl.expiry.days[*],/usr/local/bin/check_ssl_expiry.sh $1 $2
Item key: ssl.expiry.days[api.sirketim.com,443]
Trigger: {host:ssl.expiry.days[api.sirketim.com,443].last()}<30 – 30 günden az kaldığında uyarı, 7 günden az kaldığında kritik. Bu kadar basit ama bu kadar değerli.
Gerçek Senaryo 3: Redis Replication Lag İzleme
Redis Sentinel veya replica kurulumlarında replication lag kritik bir metriktir. Zabbix Redis template’leri bunu bazen atlar ya da yeterince detaylandırmaz.
#!/bin/bash
# /usr/local/bin/check_redis_replication.sh
# Kullanim: check_redis_replication.sh <host> <port> <parametre>
REDIS_HOST=${1:-127.0.0.1}
REDIS_PORT=${2:-6379}
PARAM=${3:-lag}
REDIS_CLI="redis-cli -h $REDIS_HOST -p $REDIS_PORT"
case "$PARAM" in
"role")
$REDIS_CLI info replication 2>/dev/null | grep "^role:" | cut -d: -f2 | tr -d 'r'
;;
"lag")
LAG=$($REDIS_CLI info replication 2>/dev/null | grep "master_repl_offset|slave_repl_offset" | head -1)
MASTER_OFFSET=$($REDIS_CLI info replication 2>/dev/null | grep "master_repl_offset" | cut -d: -f2 | tr -d 'r')
SLAVE_OFFSET=$($REDIS_CLI info replication 2>/dev/null | grep "slave0" | grep -oP 'offset=K[0-9]+')
if [ -z "$SLAVE_OFFSET" ]; then
echo "0"
else
echo $((MASTER_OFFSET - SLAVE_OFFSET))
fi
;;
"connected_slaves")
$REDIS_CLI info replication 2>/dev/null | grep "^connected_slaves:" | cut -d: -f2 | tr -d 'r'
;;
*)
echo "0"
;;
esac
UserParameter=redis.replication[*],/usr/local/bin/check_redis_replication.sh $1 $2 $3
Low-Level Discovery ile Custom Script’leri Ölçeklendirmek
Buraya kadar anlattıklarım tek tek item yaratmak için yeterliydi. Ama production’da onlarca disk, onlarca network interface, onlarca servis olduğunda her biri için ayrı item yaratmak mantıklı değil. İşte LLD (Low-Level Discovery) devreye giriyor.
LLD’de script’iniz JSON formatında bir liste döndürür, Zabbix bu listeyi gezip her eleman için otomatik item/trigger yaratır. Format şöyle olmalı:
#!/bin/bash
# /usr/local/bin/discover_app_services.sh
# Systemd servislerini kesfeder, "app-" ile baslayanlari listeler
SERVICES=$(systemctl list-units --type=service --state=loaded --no-pager --no-legend 2>/dev/null |
awk '{print $1}' | grep "^app-" | sed 's/.service$//')
echo '{"data":['
FIRST=1
for SERVICE in $SERVICES; do
if [ $FIRST -eq 0 ]; then
echo ','
fi
echo -n "{
"{#SERVICE_NAME}": "${SERVICE}",
"{#SERVICE_UNIT}": "${SERVICE}.service"
}"
FIRST=0
done
echo ']}'
Bu script’i LLD rule olarak tanımladığınızda Zabbix {#SERVICE_NAME} macro’sunu alıp her servis için otomatik item prototype’larını oluşturur. Yeni bir app- servisi deploy ettiğinizde bir sonraki discovery döngüsünde otomatik olarak izlemeye başlar, elinizi sürmezsiniz.
UserParameter’da LLD için özel bir sözdizimi yok, normal tanım yeterli:
UserParameter=app.services.discovery,/usr/local/bin/discover_app_services.sh
Script Güvenliği ve Best Practice’ler
Production’da script yazarken birkaç kural kendiliğinden oluştu bende, paylaşayım:
Her script bir şey döndürmeli: Script hata alsa bile Zabbix’e bir şeyler göndermeli. Boş çıktı “ZBX_NOTSUPPORTED” üretiyor ve bu alarm gürültüsü yaratıyor. Hata durumunda anlamlı bir sentinel değer (0 veya -1 gibi) döndürün.
Script’leri version control’de tutun: /usr/local/bin/ altına koyduğunuz her script bir Git repo’sunda olmalı. “Bu script ne yapıyor?” sorusu geldiğinde Git log’una bakıyorsunuz.
Timeout handling: Dış kaynaklara bağlanan script’lerde (veritabanı, API, vs.) mutlaka timeout koyun:
# Yanlis:
curl http://internal-api/health | jq '.status'
# Dogru:
curl --connect-timeout 3 --max-time 5 http://internal-api/health 2>/dev/null | jq -r '.status // "unknown"'
Input validation: UserParameter’a parametre geldiğinde o parametreyi direkt komuta geçirmek güvenlik riski. En azından temel bir temizlik yapın:
#!/bin/bash
INTERFACE=$1
# Sadece alfanumerik ve tire/nokta kabul et
if ! echo "$INTERFACE" | grep -qE '^[a-zA-Z0-9._-]+$'; then
echo "0"
exit 1
fi
cat /sys/class/net/${INTERFACE}/statistics/rx_bytes 2>/dev/null || echo "0"
Script’leri bağımsız test edilebilir yap: Her script komut satırından çalışabilmeli. Argüman almıyorsa env variable ile kontrol edilebilir hale getirin. Bu debugging sürecini dramatik olarak kısaltıyor.
Zabbix Sender ile Aktif Metrik Gönderimi
Bazı senaryolarda agent’ın çekme modelini (passive check) beklemek istemeyebilirsiniz. Uzun süren bir batch job’ın bitişini bildirmek, ya da olaya dayalı (event-driven) metrik göndermek için zabbix_sender kullanabilirsiniz.
#!/bin/bash
# Uzun batch job tamamlandiktan sonra sure ve sonucu gonder
JOB_NAME="nightly_etl"
START_TIME=$(date +%s)
# Gercek is buraya
/opt/etl/run_nightly.sh
JOB_RESULT=$?
END_TIME=$(date +%s)
DURATION=$((END_TIME - START_TIME))
# Zabbix'e gonder
zabbix_sender -z zabbix.sirket.local
-s "$(hostname)"
-k "etl.job.duration[${JOB_NAME}]"
-o "$DURATION" > /dev/null 2>&1
zabbix_sender -z zabbix.sirket.local
-s "$(hostname)"
-k "etl.job.exitcode[${JOB_NAME}]"
-o "$JOB_RESULT" > /dev/null 2>&1
Bu yaklaşımda Zabbix tarafında ilgili item’ları “Zabbix trapper” tipinde oluşturmanız gerekiyor. Agent’ın periyodik polling’i yerine, iş bittiği anda metrik geldiği için çok daha anlık ve doğru bir izleme elde ediyorsunuz.
Deployment ve Bakım
Custom script’leri onlarca sunucuya dağıtmak Ansible olmadan düşünülemez artık. Basit bir role yapısı:
# roles/zabbix_custom_metrics/tasks/main.yml mantigi
# 1. Script dosyalarini kopyala
# 2. Calistirma izni ver (chmod 0755, owner root, group zabbix)
# 3. UserParameter conf dosyasini kopyala
# 4. Syntax kontrol yap: zabbix_agentd -t her key icin
# 5. Agent'i yeniden yukle: systemctl reload zabbix-agent
Deployment sonrasında her sunucuda script’lerin çalıştığını doğrulamak için Zabbix’te basit bir availability item yaratın. Her custom script için custom.script.version[script_adi] gibi bir key, script’in içinde sabit bir versiyon döndürsün. Hem script’in çalıştığını hem de doğru versiyonun deploy edildiğini tek item’la görmüş olursunuz.
Log rotation da unutulmaması gereken bir detay. Script’leriniz bir yerlere log yazıyorsa /etc/logrotate.d/ altında bir kural olmalı, zamanla disk doldurmasın.
Sonuç
Custom script mekanizması Zabbix’i gerçek bir izleme platformuna dönüştüren şey. Hazır template’lerin %80’i kapsadığı o geri kalan %20, genellikle uygulamanıza özgü en kritik metrikler. SSL sertifikası, JVM heap, replication lag, kuyruk derinliği – bunların hepsi kurumdan kuruma farklı, standart template bunları kapsayamaz.
Ama güç kademeli olarak geliyor. Önce tek bir UserParameter yazın, test edin, Zabbix’te görün. Sonra parametreli hale getirin. Sonra LLD ile ölçeklendirin. Sonra Ansible ile otomatize edin. Her adımda bir şeyler öğreniyorsunuz.
Son olarak şunu söyleyeyim: Yazdığınız script’leri belgelendirin. Altı ay sonra o trigger neden alarm verdi sorusuna cevap verecek olan script yorumları ve Git commit mesajlarıdır. “Çalışıyor, dokunmayın” kültürü yerine “anlaşılır ve bakımı yapılabilir” kültürü kurun. Zabbix custom script’leri de dahil olmak üzere her altyapı kodu için bu geçerli.
