Şubat 5 2026

Bash’te Hata Yönetimi: set -euo pipefail, Trap ve Bats ile Defansif Scripting

Gece saat 03:00. PagerDuty çalıyor. Sorun: Kubernetes cluster’ına yeni bir deployment çıkılırken pipeline yarıda kalmış ama deploy başarılıymış gibi davranıp eski stabil replicaları da temizlemiş. Loglara bakıyorsunuz; kritik bir environment variable tanımlanmadığı için script sessizce patlamış, fakat exit code 0 döndüğü için CI/CD pipeline’ı her şeyin yolunda olduğunu varsayarak devam etmiş. Hepimiz bu filmi en az bir kere izledik. Günümüz modern altyapılarında bash scripting ve linux tabanlı otomasyon süreçleri, devops mühendisliğinin görünmez omurgasını oluşturuyor. Ancak bu omurga genellikle oldukça kırılgan temeller üzerine kurulu.

Bu makalede, Bash betiklerinizi kurumsal seviyede, hata toleransı yüksek ve test edilebilir yapılara dönüştürmenin yollarını inceleyeceğiz. Temel kavramları bir kenara bırakıp, doğrudan production ortamlarında hayat kurtaran pratik yaklaşımlara odaklanacağız.

Sessiz Katilleri Durdurun: “set -euo pipefail” Anatomisi

Bash, varsayılan olarak aşırı iyimser bir kabuktur. Bir komut hata verse de, bir değişken tanımlanmamış olsa da “yola devam et” felsefesini benimser. Bu felsefe interaktif terminal kullanımı için harika olsa da, otomasyon scriptleri için tam bir felakettir. Bu felaketi önlemenin ilk adımı, script’lerin başına o meşhur üçlüyü eklemektir:

set -euo pipefail

Peki bu parametreler arka planda tam olarak ne yapar ve neden hayati önem taşırlar? Tek tek inceleyelim.

1. set -e (errexit)

Bu parametre, script içindeki herhangi bir komut non-zero (sıfır dışı) bir exit code ile sonuçlandığında script’in anında sonlandırılmasını sağlar. Varsayılan davranışta Bash, komut hata alsa bile bir sonraki satıra geçmeye çalışır.

Neden kritik? Aşağıdaki senaryoyu düşünün:

cd /tmp/non_existent_directory
rm -rf *

Eğer ilk satırdaki cd komutu dizin bulunamadığı için başarısız olursa ve set -e aktif değilse, script bir sonraki satıra geçer ve o an hangi dizinde bulunuyorsa (muhtemelen script’in çalıştığı root dizini) oradaki tüm dosyaları siler. set -e bu felaketi engeller.

2. set -u (nounset)

Eğer tanımlanmamış bir değişkeni okumaya çalışırsanız, Bash bunu boş bir string olarak kabul eder ve hata vermez. set -u, tanımlanmamış her değişkeni ölümcül bir hata (fatal error) olarak kabul eder ve script’i durdurur.

Neden kritik?

# set -u aktif değilse:
TARGET_DIR="" # ya da yanlışlıkla typo yapılmış bir değişken
rm -rf "${TARGET_DIR}/bin" # Sistem "rm -rf /bin" komutunu çalıştırır!

set -u aktif olduğunda, Bash bu satıra geldiğinde TARGET_DIR: unbound variable hatası fırlatacak ve execution’ı hemen durduracaktır.

3. set -o pipefail

Bash’te pipeline (boru) işlemlerinde varsayılan olarak sadece son komutun exit code’u dikkate alınır. Örneğin komut1 | komut2 | komut3 zincirinde komut1 patlasa bile, komut3 başarılı olursa tüm pipeline başarılı (exit 0) sayılır.

Neden kritik?

# pipefail aktif değilse:
curl -s https://invalid-url-destination.tar.gz | tar -xzf -
echo $? # Çıktı: 0

Yukarıdaki örnekte curl başarısız olacak ve hiçbir veri indiremeyecektir. Ancak tar komutu boş girdi aldığında (veya pipe kapandığında) hata üretse de, exit code bazen yanıltıcı olabilir. Daha da kötüsü, aradaki kritik log analiz araçlarında hatalar yutulur. set -o pipefail aktif edildiğinde, pipeline içindeki en sağdaki non-zero exit code tüm pipeline’ın dönüş kodu kabul edilir.

İstisnaları Yönetmek: set -e Altında Güvenli Hata Toleransı

set -e kullanmaya başladığınızda karşılaşacağınız ilk sorun, bazı komutların hata vermesinin aslında beklenen bir durum olmasıdır. Örneğin bir dizinin varlığını kontrol etmek veya grep ile bir log dosyasında spesifik bir kelimeyi aramak gibi durumlarda, exit code’un 1 gelmesi script’i patlatmamalıdır.

Hata Kodunu Güvenli Bir Şekilde Absorbe Etmek

Eğer bir komutun başarısız olabileceğini biliyor ve bunun script’i durdurmasını istemiyorsanız, OR (||) operatörünü kullanarak hata durumunu bypass edebilirsiniz:

# grep satır bulamazsa normalde exit 1 verir ve script set -e yüzünden durur.
# || true kullanarak bunu engelliyoruz.
result=$(grep "ERROR" /var/log/app.log || true)

# Veya alternatif olarak lokal bir hata değişkenine atayabilirsiniz:
grep "CRITICAL" /var/log/app.log || local_exit=$?
if [ ${local_exit:-0} -ne 0 ]; then
    echo "Kritik log bulunamadı, ama yola devam ediyoruz."
fi

Bir diğer profesyonel yaklaşım ise conditional block’lar kullanmaktır. if, elif, while veya until ifadelerinin içinde çalıştırılan komutlar, hata verseler dahi set -e engeline takılmazlar:

# Bu blok set -e aktif olsa bile güvenle çalışır
if ! ping -c 1 8.8.8.8 > /dev/null 2>&1; then
    echo "İnternet bağlantısı yok, ancak script devam ediyor."
fi

Trap Mekanizması: Kaynakları Temizlemek ve Graceful Shutdown

Script’iniz ister başarıyla tamamlansın, ister yarıda hata alıp dursun; arkasında çöp bırakmamalıdır. Geçici dosyalar (temp files), oluşturulan socket’ler, kilit dosyaları (lock files) veya aktif edilen SSH tünelleri her durumda temizlenmelidir. İşte bu noktada Linux kernel sinyallerini yakalayan trap devreye girer.

Aşağıdaki boilerplate şablonu, production seviyesindeki script’lerinizde güvenle kullanabilirsiniz:

#!/usr/bin/env bash

set -euo pipefail

# Geçici dizin oluşturuluyor
TMP_DIR=$(mktemp -d -t my-app-XXXXXX)
echo "Geçici çalışma alanı: ${TMP_DIR}"

# Temizlik fonksiyonu
cleanup() {
    local exit_code=$?
    echo "Temizlik işlemi başlatılıyor... Son exit code: ${exit_code}"
    rm -rf "${TMP_DIR}"
    
    # Eğer script bir hata yüzünden durduysa ek bildirimler buraya yazılabilir (Slack webhook vb.)
    if [ ${exit_code} -ne 0 ]; then
        echo "Script beklenmedik bir şekilde sonlandı!" >&2
    fi
}

# EXIT sinyalini yakala. Script nasıl biterse bitsin cleanup çalışacak.
trap cleanup EXIT

Neden EXIT Sinyali?

Birçok junior geliştirici trap için sadece ERR veya INT sinyallerini tanımlar. Ancak EXIT pseudosignal’i, script normal yollarla bittiğinde de, set -e yüzünden yarıda kesildiğinde de veya bir syntax hatası alındığında da tetiklenir. Bu yüzden tek bir merkezden temizlik yönetimi için en güvenli limandır.

Exit Code Standartları: Kurumsal Entegrasyon

Yazdığınız script’lerin diğer otomasyon araçları (Ansible, Jenkins, ArgoCD vb.) tarafından doğru yorumlanabilmesi için POSIX standartlarına uygun exit code’lar dönmesi gerekir. Sadece exit 1 yazıp geçmek, hata analizi yaparken size zaman kaybettirir.

Exit Code Anlamı Kullanım Senaryosu
0 Success Script görevini başarıyla tamamladı.
1 Catchall for general errors Genel hatalar (izin yetersizliği, parse hataları vb.)
2 Misuse of shell builtins Eksik veya hatalı argüman kullanımı.
126 Command invoked cannot execute Çalıştırılmak istenen dosya execute yetkisine sahip değil.
127 Command not found Script içindeki bir binary sistemde yüklü değil.

Scriptlerinizde custom hata kodları tanımlarken 64-113 (BSD standardı) veya 128’den büyük olmayan serbest kodları (örneğin 10, 20, 30 gibi) kullanmanız, sistem hata kodlarıyla çakışmayı önler:

readonly ERR_INVALID_ARG=2
readonly ERR_DB_CONNECTION=10
readonly ERR_DISK_FULL=11

if [ $# -lt 1 ]; then
    echo "Hata: En az bir argüman girmelisiniz." >&2
    exit ${ERR_INVALID_ARG}
fi

Bats (Bash Automated Testing System) ile Unit Test Yazımı

“Bash script’inin testi mi olur?” demeyin. Eğer yazdığınız script kritik bir deploy sürecini yönetiyorsa, her değişiklikten sonra manuel test yapmak hem riskli hem de zaman alıcıdır. Bats, Bash script’lerinizi gerçek birer yazılım projesi gibi test etmenizi sağlar.

Öncelikle test edeceğimiz basit ama fonksiyonel bir script yazalım (isim: deploy.sh):

#!/usr/bin/env bash
set -euo pipefail

deploy_artifact() {
    local env=$1
    if [ -z "${env}" ]; then
        echo "Hata: Environment belirtilmedi." >&2
        return 2
    fi
    
    if [ "${env}" == "prod" ]; then
        echo "Production deployment başarılı."
        return 0
    else
        echo "Bilinmeyen environment: ${env}" >&2
        return 10
    fi
}

# Script doğrudan çalıştırıldıysa (test import etmediyse) main'i çalıştır
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
    deploy_artifact "${1:-}"
fi

Şimdi bu script için yazacağımız unit test dosyasına bakalım (isim: deploy.bats):

#!/usr/bin/env bats

# Test öncesi script'i load et
setup() {
    source ./deploy.sh
}

@test "Parametre verilmediğinde script exit code 2 ile patlamalı" {
    run deploy_artifact ""
    [ "$status" -eq 2 ]
    [ "$output" = "Hata: Environment belirtilmedi." ]
}

@test "Prod environment sağlandığında başarılı dönmeli" {
    run deploy_artifact "prod"
    [ "$status" -eq 0 ]
    [ "$output" = "Production deployment başarılı." ]
}

@test "Bilinmeyen env verildiğinde exit code 10 dönmeli" {
    run deploy_artifact "staging"
    [ "$status" -eq 10 ]
}

Bu testleri lokalinizde veya CI/CD pipeline’ınızda çalıştırmak için tek yapmanız gereken bats binary’sini çağırmaktır:

bats deploy.bats

Sonuçlar yeşil yandığında, refactoring yaparken hiçbir şeyi bozmadığınızdan emin olarak production’a güvenle push edebilirsiniz.

Sonuç: Defansif Scripting’i Alışkanlık Haline Getirin

Bash scripting, küçümsenen ama sistem mühendisliğinin en kritik katmanlarından biridir. Script yazarken kodun sadece “mutlu senaryoda” (happy path) nasıl çalışacağını değil, nerede ve nasıl patlayabileceğini tasarlamak gerekir. set -euo pipefail ile sessiz hataların önüne geçmek, trap ile sistem kaynaklarını her durumda temiz bırakmak ve bats ile bu süreçleri test edilebilir kılmak, sizi sıradan bir script yazıcısından profesyonel bir sistem mimarına dönüştürür.

Category: Genel | LEAVE A COMMENT
Ocak 8 2026

Zabbix LLD ile Dinamik Altyapı İzleme: SRE El Kitabı

Modern karmaşık altyapılarda monitoring süreçlerini otomatize etmek artık bir lüks değil; zabbix üzerinde lld (Low-Level Discovery) kullanarak dinamik sunucu ve servis keşfi yapmak ise bu otomasyon sürecinin tam kalbinde yer alıyor. YAML dosyaları arasında kaybolduğumuz, Kubernetes cluster’larının her saniye pod döküp topladığı bir dünyada, kimsenin Zabbix arayüzüne girip elle host ekleyecek, yeni bir disk mount edildiğinde onun için manuel item tanımlayacak vakti yok. Eğer hala “Yeni makine geldi, IP’sini Zabbix’e ekleyelim” diyorsanız, bu makale operasyonel yükünüzü sıfıra indirmek için yazıldı.

Neden Standart Discovery Değil de LLD?

Zabbix kullanan ekiplerin sıklıkla düştüğü ilk hata, Network Discovery ile Low-Level Discovery (LLD) kavramlarını karıştırmaktır. Network Discovery, belirli IP bloklarını tarayıp “Burada bir cihaz var, işletim sistemi de Linux” der ve host’u sisteme ekler. Ancak o host’un içine girdikten sonra işler değişir.

LLD ise bir host’un mikroskobik anatomisini dinamik olarak çıkartır. Sunucuda o an çalışan Docker container’larını, mount edilmiş disk partition’larını, aktif dinlenen TCP portlarını veya dinamik olarak ayağa kalkan systemd servislerini otomatik olarak tespit eder. LLD’nin arkasındaki sihirli güç JSON formatıdır. Zabbix Agent veya bir external script, Zabbix Server’a standardize edilmiş bir JSON döndürür; Server ise bu JSON’ı parse ederek dinamik prototiplerden gerçek Item, Trigger ve Graph’lar üretir.

Uygulama Senaryosu: Dinamik Mikroservis Port Keşfi

Geliştirme ekibinin her hafta yeni bir mikroservis deploy ettiği, her servisin rastgele (veya belirli bir pattern’e sahip) portlardan ayağa kalktığı bir senaryoyu ele alalım. Amacımız: Sunucuda dinlenen dynamic portları keşfetmek, bunları izlemek ve port kapandığında alarm üretmek.

Adım 1: Custom LLD Scripti Yazımı

Öncelikle hedef sunucuda çalışacak ve dinlenen portları Zabbix’in anlayacağı LLD formatında (JSON) döndürecek bir Bash scriptine ihtiyacımız var. Zabbix 5.0 ve üzeri sürümler artık flat JSON formatını da destekliyor, ancak biz geriye dönük uyumluluk ve standart olması açısından klasik LLD macro array formatını kullanacağız.

Aşağıdaki scripti hedef sunucuda /etc/zabbix/scripts/discover_ports.sh yoluna kaydedelim:

#!/bin/bash

# Dinlenmekte olan 8000-9000 arasındaki portları bulalım (mikroservis port aralığı)
PORTS=$(netstat -tlnp | awk '{print $4}' | grep -o '[0-9]*$' | sort -u | awk '$1>=8000 && $1<=9000')

echo "{"
echo "  \"data\": ["

FIRST=1
for PORT in $PORTS; do
    # Servis adını systemd veya prosesten çekmeye çalışalım
    SERVICE_NAME=$(ss -tlnp | grep ":$PORT " | awk -F'pid=' '{print $2}' | cut -d, -f1 | tr -d '"')
    if [ -z "$SERVICE_NAME" ]; then
        SERVICE_NAME="unknown-service"
    fi

    if [ $FIRST -ne 1 ]; then
        echo ","
    fi
    echo "    {"
    echo "      \"{#MICROSERVICE_PORT}\": \"$PORT\","
    echo "      \"{#MICROSERVICE_NAME}\": \"$SERVICE_NAME\""
    echo "    }"
    FIRST=0
done

echo "  ]"
echo "}"

Bu scriptin çıktısı tam olarak aşağıdaki gibi bir JSON olmalıdır. Zabbix LLD motoru, anahtar kelime olarak süslü parantez ve diyez ile başlayan makroları ({#MACRO}) arar:

{
  "data": [
    {
      "{#MICROSERVICE_PORT}": "8081",
      "{#MICROSERVICE_NAME}": "auth-service"
    },
    {
      "{#MICROSERVICE_PORT}": "8085",
      "{#MICROSERVICE_NAME}": "payment-service"
    }
  ]
}

Adım 2: UserParameter Tanımlama

Zabbix Agent’ın bu scripti çalıştırıp çıktıyı sunucuya gönderebilmesi için yetkilendirilmesi gerekir. /etc/zabbix/zabbix_agentd.d/userparameter_services.conf dosyasını oluşturup şu satırı ekliyoruz:

UserParameter=custom.microservice.discovery,sudo /etc/zabbix/scripts/discover_ports.sh

Burada kritik bir detay var: Script netstat ve ss komutlarını kullandığı için proses isimlerini okurken root yetkisine ihtiyaç duyabilir. Bu yüzden /etc/sudoers dosyasına zabbix kullanıcısı için şifresiz çalıştırma izni eklemeyi unutmayın:

zabbix ALL=(ALL) NOPASSWD: /etc/zabbix/scripts/discover_ports.sh

Konfigürasyonu yaptıktan sonra agent servisini restart ediyoruz:

systemctl restart zabbix-agent

Zabbix Arayüzünde Template ve LLD Yapılandırması

Artık altyapıyı hazırladığımıza göre, Zabbix Server tarafında bu veriyi işleyecek kuralları tanımlayabiliriz.

Discovery Rule Oluşturma

  1. Zabbix Web UI’da Configuration > Templates sekmesine gidin ve yeni bir template oluşturun (Örn: Template App Microservices Dynamic).
  2. Oluşturduğunuz template’in içindeki Discovery rules sekmesine tıklayın ve Create discovery rule butonuna basın.
  3. Name: Microservice Port Discovery
  4. Type: Zabbix agent (veya zabbix agent active – mimarinize göre)
  5. Key: custom.microservice.discovery
  6. Update interval: 1h (Üretim ortamında her dakika keşif yapmak gereksiz yük bindirir, 1 saat veya duruma göre 15 dakika idealdir).

Item Prototiplerini Tanımlama

Discovery Rule oluştuktan sonra, bu kuralın altında Item prototypes sekmesine giriyoruz. Burada tanımlayacağımız her item, keşfedilen her port için otomatik olarak klonlanacak.

Create item prototype diyerek ilk prototipimizi oluşturalım:

  • Name: Service {#MICROSERVICE_NAME} on Port {#MICROSERVICE_PORT} Status
  • Key: net.tcp.service[tcp,,{#MICROSERVICE_PORT}]
  • Type: Simple check (veya Agent üzerinden kontrol etmek için agent key’leri kullanabilirsiniz)
  • Type of information: Numeric (unsigned)

Burada kritik nokta, Key alanında dinamik makromuz olan {#MICROSERVICE_PORT} ifadesini kullanmış olmamızdır. Zabbix her keşifte bu değeri gerçek port numarasıyla değiştirecektir.

Trigger Prototiplerini Tanımlama

Sadece izlemek yetmez, servis çöktüğünde alarm üretmeliyiz. Aynı Discovery Rule altındaki Trigger prototypes sekmesine gidiyoruz:

  • Name: Microservice {#MICROSERVICE_NAME} on port {#MICROSERVICE_PORT} is DOWN
  • Expression: last(/Template App Microservices Dynamic/net.tcp.service[tcp,,{#MICROSERVICE_PORT}])=0
  • Severity: Average (veya High)

Bu tanım sayesinde, örneğin 8085 portundaki `payment-service` çöktüğünde trigger otomatik olarak evaluate edilecek ve alarm başlığı dinamik olarak çözümlenerek “Microservice payment-service on port 8085 is DOWN” şeklinde Slack/PagerDuty entegrasyonlarınıza düşecektir.

SRE Gözünden LLD Optimizasyonları ve Best Practice’ler

Büyük ölçekli ortamlarda (10.000+ host, 500.000+ item) LLD kullanırken dikkat edilmezse Zabbix Server database’i ve LLD housekeeper süreçleri darboğaza girebilir. Aşağıdaki optimizasyonları mutlaka dikkate alın:

1. Discard Unchanged (Throttling) Kullanın

Keşif scriptiniz her çalıştığında aynı JSON çıktısını üretiyorsa, Zabbix Server’ın her seferinde bu veriyi veritabanına yazıp işlem yapmasına gerek yoktur. Discovery Rule konfigürasyonundaki Preprocessing sekmesine gidin ve Discard unchanged with heartbeat ekleyin. Heartbeat süresini 12h veya 24h yapın. Bu, DB write I/O oranınızı dramatik ölçüde düşürür.

2. Keep Lost Resources Period Ayarı

Bir mikroservis kapatıldığında veya taşındığında, ona ait eski item ve trigger verilerinin ne kadar süre sistemde kalacağını belirleyen parametre LLD rule içindeki Keep lost resources period seçeneğidir. Varsayılan olarak 30 gündür. Dinamik ortamlarda bu değeri 1d (1 gün) veya 12h gibi düşük sürelere çekin. Aksi takdirde, artık var olmayan yüzlerce geçici portun “Unknown” statüsündeki çöpleriyle uğraşırsınız.

3. Filtreleri Doğru Kullanın

Scriptinizden gelen tüm verileri izlemek istemeyebilirsiniz. LLD kuralındaki Filters sekmesini kullanarak sadece belirli pattern’e uyan makroları işleme alabilirsiniz. Örneğin, sadece {#MICROSERVICE_NAME} değeri “prod-” ile başlayanları izlemek için regex filtresi koyabilirsiniz:

{#MICROSERVICE_NAME} matches ^prod-.*

Özet

Zabbix LLD, altyapınızın dinamik yapısına ayak uydurabilen, kendi kendini temizleyen ve sürekli güncel kalan bir monitoring katmanı kurmanın en efektif yoludur. Statik template mantığından çıkıp, veriyi doğrudan kaynaktan (OS, API, Docker socket) çekerek otomatik kural setleri türetmek, SRE ekiplerinin operasyonel yükünü minimize eder. Bir sonraki aşamada bu LLD mekanizmasını Kubernetes API’si ile entegre ederek cluster dışındaki Zabbix cluster’ınızdan pod seviyesinde keşifler tetikleyebilirsiniz.

Category: Genel | LEAVE A COMMENT
Kasım 15 2024

Ansible ile Idempotent Playbook Yazmanın İncelikleri

Sektörde beş yılı deviren her devops mühendisinin ortak kabuslarından biri, “benden önce yazılmış” ve her çalıştığında farklı bir sürpriz sunan ansible playbook’larıdır. Hepimiz o yollardan geçtik: Sadece bir config dosyasındaki parametreyi değiştirmek için çalıştırdığınız playbook, sunucudaki üç servisi yeniden başlatır, SSL sertifikalarını sıfırlar ve deployment pipeline’ını kilitler. İşte bu noktada, modern iac (Infrastructure as Code) felsefesinin kalbi olan idempotent (eşgüçlü) kavramı devreye giriyor. Gerçek bir altyapı otomasyon süreci, aynı playbook’u ister 1 ister 1000 kez çalıştırın, hedef sistemi her zaman tam olarak hedeflediğiniz kararlı durumda (desired state) bırakmalıdır. Bu makalede, işin “YAML yazmaktan” çıkıp gerçek bir yazılım mühendisliği disiplinine dönüştüğü o ince çizgiyi inceleyeceğiz.

1. “Her Şey Yolunda” İllüzyonu: changed_when ve failed_when

Ansible modüllerinin büyük çoğunluğu idempotent çalışacak şekilde tasarlanmıştır. Örneğin ansible.builtin.apt veya ansible.builtin.template modülleri, hedef sistemin durumunu kontrol eder ve bir değişiklik gerekmiyorsa yeşil yanarak yoluna devam eder. Ancak iş shell veya command modüllerine geldiğinde Ansible körleşir. Bu modüller doğası gereği her çalıştığında sisteme bir etki ettiklerini varsayarlar ve her zaman sarı (changed: true) dönerler.

Bu durum sadece göz zevkimizi bozmakla kalmaz; Ansible handler’larının (örneğin servis restart işlemleri) gereksiz yere tetiklenmesine yol açarak production ortamında kesintilere sebep olur. İşte bu kontrolsüzlüğü dizginlemek için elimizdeki en güçlü silahlar: changed_when ve failed_when.

İyi, Kötü ve Çirkin: shell Modülünü Terbiye Etmek

Farz edelim ki bir uygulamanın CLI aracıyla bir konfigürasyon yapacaksınız. Eğer bu konfigürasyon zaten yapılmışsa, komutu tekrar çalıştırmamalı veya çalıştırsak bile Ansible’a “hey, burada yeni bir şey yapmadın, sakin ol” demeliyiz.

Aşağıdaki kötü pratiğe bir göz atalım:

# KÖTÜ PRATİK (Her çalışmada "changed" döner, handler'ları tetikler)
- name: Enable custom application plugin
  ansible.builtin.shell: "myapp-cli plugin enable prometheus"
  register: plugin_output
  notify: Restart MyApp

Şimdi bunu profesyonelce revize edelim. Önce eklentinin durumunu sorgulayalım, ardından sadece eklenti aktif değilse işlem yapalım ve durumu Ansible’a doğru şekilde bildirelim:

# İYİ PRATİK (Idempotent ve güvenli)
- name: Check if prometheus plugin is already enabled
  ansible.builtin.shell: "myapp-cli plugin list | grep -E '^prometheus.*enabled'"
  register: plugin_status
  failed_when: false
  changed_when: false

- name: Enable custom application plugin if not enabled
  ansible.builtin.shell: "myapp-cli plugin enable prometheus"
  when: plugin_status.rc != 0
  register: enable_result
  changed_when: "'plugin successfully enabled' in enable_result.stdout"
  notify: Restart MyApp

Burada ne yaptık? İlk task’ta failed_when: false ve changed_when: false diyerek, sadece bir durum sorguladığımızı ve bu sorgunun sistemi değiştirmediğini, ayrıca grep başarısız olsa bile (yani plugin kurulu değilse) pipeline’ın kırılmaması gerektiğini belirttik. İkinci task’ta ise sadece plugin aktif değilse çalıştık ve çıktıdaki spesifik bir loga bakarak değişikliğin gerçekten gerçekleşip gerçekleşmediğini teyit ettik.

2. Sırları Sızdırmadan Yönetmek: Ansible Vault ve Best Practice’ler

IaC kodlarınızı Git reposunda tutuyorsanız (ki tutmalısınız), API key’ler, veritabanı şifreleri veya SSL private key’ler gibi hassas verileri (secrets) asla düz metin (plain text) olarak commit etmemelisiniz. Ansible bu problemi çözmek için dahili bir şifreleme mekanizması olan Ansible Vault‘u sunar.

Ancak projelerde sıklıkla yapılan hata, tüm group_vars/all.yml dosyasını toptan şifrelemektir. Bu, kod incelemelerinde (Code Review) hangi değişkenlerin değiştiğini görmeyi imkansız hale getirir. Bunun yerine, sadece hassas değerleri şifreleyip referans vermek çok daha temiz bir yaklaşımdır.

Tek Satır Şifreleme (vault_encrypted) Pratiği

Tüm dosyayı şifrelemek yerine, sadece gizli tutmak istediğiniz değeri terminalde şifreleyin:

ansible-vault encrypt_string 'super_secret_db_password' --name 'vault_db_password'

Bu komut size YAML formatında şifrelenmiş bir blok verecektir. Bu bloğu değişken dosyanıza güvenle yapıştırabilirsiniz:

# group_vars/production.yml
db_username: "app_user" # Düz metin olarak kalmasında sakınca yok
db_password: "{{ vault_db_password }}" # Şifrelenmiş değere referans

# Şifrelenmiş blok:
vault_db_password: !vault |
          $ANSIBLE_VAULT;1.1;AES256
          3565393065646134373463326139613661646231363539303362333633643730306265353866
          62376663363236373961633535366430333766396662366264370a3736363339386333343632
          3431663030313534663831633633636636316262333066366432366164343164376435383561
          32303533663731306132373335623233390a3666363435303964313032393335396161613936
          653063616664653838323630

Playbook’u çalıştırırken Vault şifresini güvenli bir şekilde okutmak için --vault-password-file parametresini veya çevresel değişkenleri (environment variables) kullanabilirsiniz. CI/CD pipeline’larında bu şifreyi runner’a bir secret olarak tanımlamak en güvenli yoldur.

3. Altyapıyı Test Etmek: Molecule ile TDD Yaklaşımı

“Yazdığım playbook production’da çalışır mı?” sorusunun cevabı hiçbir zaman “deneyip görelim” olmamalıdır. Yazılım dünyasındaki Unit/Integration test kavramının IaC dünyasındaki karşılığı Molecule‘dur. Molecule; Ansible rollerinizi izole ortamlarda (genellikle Docker veya Podman üzerinde, bazen de Vagrant/AWS’te) otomatik olarak ayağa kaldırır, playbook’unuzu çalıştırır (idempotency testi dahil) ve ardından ortamı temizler.

Molecule Kurulumu ve Örnek Senaryo

Molecule’ü projenize dahil etmek için Docker driver’ı ile birlikte yükleyin:

pip install molecule molecule-plugins[docker] ansible-lint

Bir Ansible rolü içinde Molecule senaryosu başlatmak için:

molecule init scenario --driver-name docker

Bu komut, rolünüzün altında molecule/default adında bir klasör oluşturur. Buradaki en kritik dosya testlerin nasıl koşulacağını belirleyen molecule.yml dosyasıdır:

# molecule/default/molecule.yml
dependency:
  name: galaxy
driver:
  name: docker
platforms:
  - name: test-ubuntu-target
    image: geerlingguy/docker-ubuntu2204-ansible:latest
    pre_build_image: true
    volumes:
      - /sys/fs/cgroup:/sys/fs/cgroup:rw
    cgroupns_mode: host
    privileged: true
provisioner:
  name: ansible
  playbooks:
    converge: ${MOLECULE_PLAYBOOK:-converge.yml}
verifier:
  name: ansible

molecule test komutunu çalıştırdığınızda şu adımlar sırasıyla işletilir:

  1. Dependency: Gerekli harici roller indirilir.
  2. Lint: Ansible-lint ile kod standartları taranır.
  3. Destroy/Create: Eski test container’ları silinir ve yenileri ayağa kaldırılır.
  4. Converge: Playbook’unuz ilk kez çalıştırılır (kurulumlar yapılır).
  5. Idempotence: Playbook ikinci kez çalıştırılır. Eğer herhangi bir task “changed” dönerse test başarısız sayılır! İşte gerçek idempotent testi budur.
  6. Verify: Belirlediğiniz test script’leri (örneğin servis gerçekten çalışıyor mu kontrolü) koşturulur.

4. Performans Optimizasyonu: Dakikaları Saniyelere İndirmek

Ansible, mimarisi gereği her bir task için hedef makineye SSH bağlantısı açar, modülü transfer eder, çalıştırır ve sonucu geri alır. Yüzlerce sunucudan oluşan bir envanterde bu durum ciddi bir performans darboğazı (bottleneck) yaratır. Neyse ki birkaç ince ayarla bu süreyi dramatik şekilde azaltabiliriz.

SSH Pipelining Aktifleştirme

Ansible’ın varsayılan davranışında, modül dosyaları önce hedef makineye kopyalanır ve ardından SSH üzerinden çalıştırılır. SSH Pipelining aktifleştirildiğinde ise bu işlemler kopyalama adımı atlanarak doğrudan SSH oturumu üzerinden (piped) gerçekleştirilir.

# ansible.cfg
[ssh_connection]
pipelining = True
ssh_args = -o ControlMaster=auto -o ControlPersist=60s

Not: Pipelining kullanabilmek için hedef sunuculardaki /etc/sudoers dosyasında requiretty seçeneğinin aktif olmaması gerekir (modern dağıtımlarda varsayılan olarak pasiftir).

Fact Gathering Mekanizmasını Optimize Etmek

Ansible her playbook başlangıcında hedef sisteme dair tüm sistem bilgilerini (IP, CPU, RAM, disk durumları vb.) toplar (Gathering Facts). Eğer playbook’unuzda bu değişkenleri (örn: ansible_distribution) kullanmıyorsanız, bu adımı tamamen kapatın:

- hosts: webservers
  gather_facts: false
  tasks:
    # ...

Eğer bazı task’lar için bu bilgilere ihtiyacınız varsa, akıllı cache (fact caching) mekanizmasını devreye sokarak her çalıştırmada bu süreyi tekrar ödemekten kurtulabilirsiniz:

# ansible.cfg
[defaults]
gathering = smart
fact_caching = jsonfile
fact_caching_connection = /tmp/ansible_fact_cache
fact_caching_timeout = 86400 # 24 saat cache'le

Özet ve Kapanış

Yazması kolay, yönetmesi zor bir araçtır Ansible. Onu sıradan bir shell script tetikleyicisinden profesyonel bir iac aracına dönüştüren şey, sizin yazdığınız playbook’lardaki detay seviyesidir. changed_when ile kontrolü elinizde tutmak, secrets yönetiminde hassas davranmak, Molecule ile test güvencesi sağlamak ve performans parametrelerini optimize etmek sizi ekipten bir adım öne çıkaracaktır. Altyapınızın her zaman tahmin edilebilir, kararlı ve hızlı kalması dileğiyle!

Category: Genel | LEAVE A COMMENT
Ağustos 23 2024

Bash Script’lerinde Kurşun Geçirmez Hata Yönetimi: set -euo pipefail ve Ötesi

Hepimiz oradaydık: Gece yarısı gelen bir PagerDuty alarmı, yarıda kesilmiş bir CI/CD pipeline’ı ve sessizce hata fırlatıp “başarılı” (exit 0) olarak sonlanan bir bash script’i. Linux sistemlerde scripting yaparken, özellikle modern devops ve otomasyon süreçlerinde, hata yönetimi lüks değil bir zorunluluktur. Varsayılan olarak Bash, adeta bir nihilist gibi davranır: Adımlardan biri başarısız olsa bile, hiçbir şey olmamış gibi bir sonraki satıra geçer. Bu makalede, bu vurdumduymazlığı nasıl dizginleyeceğimizi ve prod-ready scriptler yazacağımızı inceleyeceğiz.

5+ yıllık deneyime sahip bir mühendis olarak muhtemelen set -e komutunu duymuş, hatta şablonlarınıza eklemişsinizdir. Ancak bu buzdağının sadece görünen kısmı. Gelin, işi profesyonel seviyeye taşıyalım.

Sihirli Üçlü: set -euo pipefail

Bash script’lerinizin başına ekleyeceğiniz bu sihirli satır, hata yönetiminizin temel taşıdır. Peki ama neden bu üç parametre birlikte kullanılmalı? Her birinin arka planda çözdüğü spesifik problemi inceleyelim.

#!/usr/bin/env bash
set -euo pipefail

1. set -e (errexit)

Herhangi bir komut sıfırdan farklı bir exit code ile dönerse, script’in çalışmasını anında durdurur.

Neden gerekli? Varsayılan senaryoda, diskte yer kalmadığı için başarısız olan bir tar komutundan sonra script çalışmaya devam eder ve bir sonraki adımdaki silme işlemini tetikleyebilir. set -e bunu engeller. Ancak alt kabuklarda (subshell) veya mantıksal operatörlerde (&&, ||) her zaman beklediğiniz gibi davranmaz. Bu yüzden tek başına kurtarıcı değildir.

2. set -u (nounset)

Tanımlanmamış bir değişken kullanılmaya çalışıldığında script’i hata vererek durdurur.

Neden gerekli? Şu felaket senaryosunu düşünün:

# set -u OLMADAN
TARGET_DIR="" # Bir hata sonucu boş kaldı
rm -rf "$TARGET_DIR/*" # Tebrikler, kök dizini (root) sildiniz.

Eğer set -u aktif olsaydı, Bash daha rm komutuna geçmeden TARGET_DIR is not set hatasıyla işlemi sonlandıracaktı.

3. set -o pipefail

Pipeline (boru hattı) içerisindeki herhangi bir komut hata aldığında, tüm pipeline’ın exit code’unu en son hata alan komutun kodu olarak belirler.

Neden gerekli? Bash varsayılan olarak sadece pipeline’ın en sonundaki komutun exit code’una bakar.

# set -o pipefail OLMADAN
non_existent_command | echo "Merhaba"
echo $? # Çıktı: 0 (Çünkü echo başarılı oldu!)

Yukarıdaki örnekte ilk komut crash olsa bile pipeline başarılı sayılır. set -o pipefail ile bu durum engellenir ve ilk komutun hatası tüm hattı başarısız kılar.

İstisnaları Yönetmek: “Peki ya hata almasını bekliyorsam?”

set -e kullandığınızda, bazen hata vermesi normal olan komutlar da script’inizi sonlandırır. Örneğin, bir portun açık olup olmadığını nc ile kontrol etmek istiyorsunuz:

#!/usr/bin/env bash
set -euo pipefail

# nc başarısız olursa script burada ölecektir.
nc -z -w3 10.0.0.5 80

echo "Bu satıra asla ulaşılamayacak."

Bunu aşmanın “temiz” yolu, komutun sonuna mantıksal || true eklemek veya hata durumunu inline olarak handle etmektir:

# Güvenli yaklaşım 1: || true ile bypass etmek
nc -z -w3 10.0.0.5 80 || true

# Güvenli yaklaşım 2: Hata durumunda alternatif aksiyon almak
if ! nc -z -w3 10.0.0.5 80; then
    echo "Port kapalı, alternatif akışa geçiliyor..."
    # Burası script'i durdurmaz çünkü if bloğu içindeyiz
fi

Trap Mekanizması ile Graceful Degradation ve Cleanup

Script’iniz yarıda kesildiğinde (ister hata sebebiyle, ister kullanıcı CTRL+C’ye bastığında), arkasında çöp bırakmamalıdır. Geçici dosyalar (temporary files), kilit mekanizmaları (lock files) veya açık SSH tünelleri mutlaka temizlenmelidir. İşte burada trap devreye girer.

Bash’teki sinyalleri (signals) yakalayarak, script sonlanırken mutlaka çalıştırılacak bir cleanup fonksiyonu tanımlayabiliriz:

#!/usr/bin/env bash
set -euo pipefail

# Geçici bir dizin oluşturalım
TMP_DIR=$(mktemp -d)
echo "Geçici dizin oluşturuldu: ${TMP_DIR}"

# Cleanup fonksiyonumuz
cleanup() {
    local exit_code=$?
    echo "Temizlik yapılıyor: ${TMP_DIR} siliniyor..."
    rm -rf "${TMP_DIR}"
    
    if [ $exit_code -ne 0 ]; then
        echo "Script HATA ile sonlandı! Exit code: ${exit_code}"
    else
        echo "Script başarıyla tamamlandı."
    fi
    exit $exit_code
}

# EXIT sinyalini yakala (Hata olsun ya da olmasın, script çıkarken çalışır)
trap cleanup EXIT

# İş yükü simülasyonu
echo "İşlem yapılıyor..."
touch "${TMP_DIR}/important_data.tmp"

# Hata simülasyonu (Burada script patlayacak ama trap çalışacak!)
grep "olmayan_pattern" "${TMP_DIR}/important_data.tmp"

Standartlara Uygun Exit Code Kullanımı

Gelişigüzel exit 1 yazıp geçmek, üst seviye bir otomasyonda kabul edilemez. Script’inizin çağıran sisteme (örneğin Jenkins, GitHub Actions veya bir cron job) neyin yanlış gittiğini net bir şekilde söylemesi gerekir.

POSIX standartlarına ve /usr/include/sysexits.h yapısına uygun olarak şu kodları tercih edin:

  • 0: Başarılı operasyon.
  • 64 (EX_USAGE): Yanlış CLI argüman kullanımı.
  • 69 (EX_UNAVAILABLE): Servis veya kaynak ulaşılamaz durumda.
  • 70 (EX_SOFTWARE): İçsel yazılım hatası (Internal error).
  • 74 (EX_IOERR): Giriş/Çıkış (I/O) hatası (Disk dolu, dosya yazılamadı).
# Örnek kullanım
if [[ $# -lt 2 ]]; then
    echo "Hata: Eksik parametre. Kullanım: $0  " >&2
    exit 64
fi

Script’lerinizi Test Edin: BATS (Bash Automated Testing System)

SRE dünyasında “test edilmeyen kod çalışmıyordur” kuralı geçerlidir. Bash script’lerinizi manuel test etmek yerine, declarative testler yazmak için bats-core kullanabilirsiniz.

Önce basit bir script’imiz olsun (deploy.sh):

#!/usr/bin/env bash
# deploy.sh
set -euo pipefail

check_env() {
    if [[ -z "${APP_ENV:-}" ]]; then
        echo "Hata: APP_ENV tanımlı değil!" >&2
        return 64
    fi
}

check_env
echo "Deploying to ${APP_ENV}"

Şimdi bu script için yazacağımız BATS test dosyası (deploy.bats):

#!/usr/bin/env bats

# Test 1: APP_ENV tanımlı değilse script 64 koduyla hata vermeli
@test "APP_ENV set edilmediğinde script hata fırlatmalı" {
  run ./deploy.sh
  [ "$status" -eq 64 ]
  [[ "$output" =~ "Hata: APP_ENV tanımlı değil!" ]]
}

# Test 2: APP_ENV tanımlı olduğunda başarılı olmalı
@test "APP_ENV set edildiğinde script başarılı olmalı" {
  export APP_ENV="production"
  run ./deploy.sh
  [ "$status" -eq 0 ]
  [[ "$output" =~ "Deploying to production" ]]
}

Bu testleri CI pipeline’ınızda bats deploy.bats komutuyla çalıştırarak, script’lerinizin gelecekteki değişikliklerde kırılmasını engelleyebilirsiniz.

Özet

Bash’te hata yönetimi, sadece set -e yazıp şansımıza güvenmekten ibaret değildir. Profesyonel otomasyon süreçlerinde, pipefail ile boru hatlarını güvenceye almak, trap ile arkamızı temizlemek, standart exit code’lar ile çağıran sistemlere anlamlı yanıtlar dönmek ve nihayetinde BATS ile bu davranışları test etmek gerekir. Unutmayın, iyi bir DevOps mühendisi sadece çalışan kod değil, güvenle patlayan kod yazar.

Category: Genel | LEAVE A COMMENT
Temmuz 26 2024

Zabbix’te LLD ile Dinamik Monitoring Otomasyonu: Custom Script ve Trigger Entegrasyonu

Modern altyapılarda statik konfigürasyon dosyalarıyla veya arayüzden tek tek host ekleyerek hayatta kalmak imkansız. Hele ki her gün onlarca microservice’in, disk partition’ının veya network interface’inin ayağa kalkıp kapandığı dinamik bir Kubernetes ya da bare-metal cluster yönetiyorsanız, manuel izleme (monitoring) tam bir kabusa dönüşür. Bu noktada imdadımıza yetişen en güçlü mekanizmalardan biri, şüphesiz Zabbix LLD (Low-Level Discovery) tabanlı dinamik monitoring otomasyonu mimarisidir.

Bu makalede, teorik tanımları bir kenara bırakıp doğrudan production ortamında çalışan, özelleştirilmiş (custom) bir LLD senaryosunu sıfırdan inşa edeceğiz. Amacımız, host üzerindeki dinamik Docker container’larını otomatik keşfetmek, bunlara ait metrikleri toplamak ve dinamik trigger prototipleri ile akıllı alarmlar üretmek.

Neden Standart Discovery Değil de LLD?

Zabbix’te iki tip keşif mekanizması bulunur: Network Discovery ve Low-Level Discovery (LLD). Network Discovery, ağdaki IP bloklarını tarayıp “Burada yeni bir sunucu var mı?” sorusuna yanıt ararken; LLD, “Bu sunucunun içinde izlemem gereken ve sürekli değişen hangi alt bileşenler var?” sorusunu yanıtlar. Diskler, network arayüzleri, CPU çekirdekleri, veritabanı instance’ları veya Docker container’ları LLD’nin ana hedefidir.

LLD’nin arkasındaki sihir JSON formatındadır. Zabbix Agent veya bir script, Zabbix Server’a belirli bir yapıda JSON döndürür. Server bu JSON’ı parse eder, içindeki makroları (`{#MACRO}`) ayıklar ve tanımladığınız prototiplere göre otomatik olarak Item, Trigger ve Graph oluşturur.

Adım 1: Custom LLD Scripti Yazımı (JSON Formatı)

Zabbix’in bir yapıyı LLD ile keşfedebilmesi için çıktının mutlaka `data` array’i içeren veya doğrudan düz bir JSON array formatında olması gerekir. Biz, sunucuda çalışan aktif Docker container’larını adlarıyla keşfedecek bir Python scripti yazacağız. Bu scripti Zabbix Agent’ın kurulu olduğu hedef makinede konumlandıracağız.

Aşağıdaki scripti hedef sunucuda /etc/zabbix/scripts/discover_containers.py olarak kaydedin:

#!/usr/bin/env python3
import json
import subprocess

def get_containers():
    try:
        # Çalışan container adlarını alıyoruz
        cmd = "docker ps --format '{{.Names}}'"
        result = subprocess.check_output(cmd, shell=True).decode('utf-8')
        containers = [line.strip() for line in result.split('\n') if line.strip()]
        
        # Zabbix LLD formatına uygun JSON şeması oluşturuyoruz
        lld_data = []
        for container in containers:
            lld_data.append({
                "{#CONTAINERNAME}": container
            })
        
        # Zabbix 5.x ve sonraki sürümler doğrudan array kabul eder
        print(json.dumps(lld_data))
    except Exception as e:
        print(json.dumps([]))

if __name__ == "__main__":
    get_containers()

Scriptin çalıştırılabilir olduğundan emin olun:

chmod +x /etc/zabbix/scripts/discover_containers.py

Adım 2: Zabbix Agent Konfigürasyonu (UserParameter)

Yazdığımız scripti Zabbix Server’ın tetikleyebilmesi için Zabbix Agent üzerinde bir UserParameter tanımlamamız gerekiyor. Neden böyle yapıyoruz? Çünkü Agent’ın yeteneklerini dışarıdan güvenli bir şekilde genişletmenin en performanslı yolu budur.

/etc/zabbix/zabbix_agentd.d/userparameter_docker.conf dosyasını oluşturun ve şu satırı ekleyin:

UserParameter=docker.container.discovery,/etc/zabbix/scripts/discover_containers.py
UserParameter=docker.container.cpu[*],docker stats --no-stream --format "{{.CPUPerc}}" $1 | cut -d'%' -f1
UserParameter=docker.container.status[*],docker inspect -f '{{.State.Running}}' $1

Burada ne yaptık?
1. docker.container.discovery anahtarı ile LLD JSON çıktısını sağladık.
2. docker.container.cpu[*] parametrik anahtarı ile keşfettiğimiz container’ın anlık CPU tüketimini çekeceğiz.
3. docker.container.status[*] ile de container’ın çalışıp çalışmadığını (true/false) denetleyeceğiz.

Değişikliklerin devreye girmesi için Zabbix Agent servisini yeniden başlatın:

systemctl restart zabbix-agent

Şimdi Zabbix Server veya proxy üzerinden bu mekanizmanın çalışıp çalışmadığını test edelim:

zabbix_get -s 127.0.0.1 -k docker.container.discovery

Eğer her şey doğru yapılandırıldıysa, ekranda şu tarz bir JSON görmelisiniz:

[{"{#CONTAINERNAME}": "nginx-prod"}, {"{#CONTAINERNAME}": "postgres-db"}]

Adım 3: Zabbix UI Üzerinde Template ve LLD Rule Tanımlama

Sistem katmanındaki hazırlığımız bitti. Şimdi Zabbix Web UI üzerinde bu veriyi işleyecek şablonu (Template) hazırlayalım.

1. Discovery Rule Oluşturma

Zabbix UI’da Configuration > Templates adımına gidin ve yeni bir template oluşturun (Örn: Template App Docker Custom).

  • Oluşturduğunuz template içinde Discovery rules sekmesine gelin ve Create discovery rule butonuna tıklayın.
  • Name: Docker Container Discovery
  • Type: Zabbix agent (veya agent active)
  • Key: docker.container.discovery
  • Update interval: 1m (Geliştirme aşamasında hızlı görmek için 1 dakika, production’da 10m veya 30m idealdir).

2. Item Prototypes (Öğe Prototipleri) Oluşturma

Keşfedilen her bir container için hangi metrikleri toplayacağımızı burada tanımlıyoruz. Discovery rule içindeki Item prototypes sekmesine geçin ve Create item prototype deyin.

CPU Metriği İçin:

  • Name: Container {#CONTAINERNAME}: CPU Usage
  • Key: docker.container.cpu[{#CONTAINERNAME}]
  • Type: Zabbix agent
  • Type of information: Numeric (float)
  • Units: %

Status Metriği İçin:

  • Name: Container {#CONTAINERNAME}: Status
  • Key: docker.container.status[{#CONTAINERNAME}]
  • Type: Zabbix agent
  • Type of information: Character (veya boolean/numeric dönüşümü yapabilirsiniz, inspect çıktımız ‘true’/’false’ döneceği için Character seçip trigger’da regex ile kontrol etmek pratik bir yoldur).

Adım 4: Dinamik Trigger Prototipleri ile Akıllı Alarmlar

Sadece veri toplamak yetmez, bu verilerin anomali durumlarında nöbetçi mühendisi ayağa kaldırması gerekir. Ancak her yeni container için manuel alarm yazamayız. LLD burada devreye girerek her container için bağımsız trigger’lar üretir.

Discovery rule altındaki Trigger prototypes sekmesine gelin ve Create trigger prototype butonuna tıklayın.

Senaryo: Container Stop Olduysa Alarm Üret

  • Name: Container {#CONTAINERNAME} is not running on {HOST.NAME}
  • Expression:
    last(/Template App Docker Custom/docker.container.status[{#CONTAINERNAME}])="false"
  • Severity: High veya Average

Bu ifade sayesinde, sistemde yeni bir container (örneğin redis-cache) ayağa kalktığı anda Zabbix bunu LLD ile keşfedecek, ardından docker.container.status[redis-cache] item’ını oluşturacak ve eğer bu container stop durumuna düşerse otomatik olarak “Container redis-cache is not running on Server-01” başlığıyla alarm üretecektir.

Production İpuçları ve “Neden Böyle?” Analizi

Büyük ölçekli ortamlarda LLD kullanırken sistem kaynaklarını tüketmemek ve false-positive alarmların önüne geçmek için şu mimari kararları uygulamalısınız:

Keep Lost Resources Period (Kayıp Kaynakları Tutma Süresi)

LLD rule konfigürasyonunda Keep lost resources period adında bir parametre göreceksiniz. Varsayılan değeri 30 gündür. Eğer dinamik ortamlarda (örn. Jenkins agent’ları gibi sürekli açılıp kapanan container’lar) çalışıyorsanız, bu değeri 30 gün bırakırsanız Zabbix veritabanınız çöpe döner. Silinen container’ların monitoring öğelerinin sistemden hızlıca temizlenmesi için bu değeri 1h (1 saat) veya en fazla 1d (1 gün) olarak ayarlayın.

Discard Unchanged (Değişmeyen Veriyi Yoksayma)

Container status metriği her dakika “true” dönecektir. Veritabanında her dakika aynı veriyi yazarak I/O tüketmemek için Item Prototype ayarlarındaki Preprocessing sekmesine gidin. Buraya Discard unchanged with heartbeat ekleyin ve süresini 1h yapın. Böylece durum değişmediği sürece veritabanına sadece saatte bir yazılır, ancak durum “false” olduğu an hemen kayıt düşülür.

Zabbix LLD, altyapınızı kodla yönettiğiniz (Infrastructure as Code) modern dünyada izleme katmanının da bu hıza ayak uydurmasını sağlayan en kritik araçtır. Doğru kurgulanmış bir LLD şablonu, operasyonel iş yükünüzü kalıcı olarak azaltır.

Category: Genel | LEAVE A COMMENT