Eylül 27 2024

Linux Sistem Performans Analizi: perf, strace ve eBPF Araçları

Bir sabah Slack’ten gelen o korkunç alarm sesiyle uyandınız: “Production veritabanı sunucusunda latency uçtu, CPU %99!” Hemen sunucuya SSH ile bağlandınız, top komutunu çalıştırdınız ve evet, CPU gerçekten can çekişiyor. Ancak top veya htop gibi geleneksel araçlar size sadece “yangın olduğunu” söyler; yangının hangi odada, hangi kibritle başladığını göstermez. İşte bu noktada modern bir SRE (Site Reliability Engineer) gibi düşünmeli ve linux çekirdeğinin (kernel) derinliklerine inmeliyiz. Bu yazıda, modern altyapılarda performance sorunlarını iğne deliğinden geçirir gibi analiz etmenizi sağlayacak üç silahşörü inceleyeceğiz: perf, strace ve devrim niteliğindeki ebpf tabanlı bpftrace.

1. Donanımın Nabzını Tutmak: perf (CPU & Hardware Profiling)

perf, Linux çekirdeği ile doğrudan entegre çalışan inanılmaz güçlü bir profil çıkarma (profiling) aracıdır. CPU döngülerini (cycles), cache miss (önbellek kaçırma) oranlarını ve CPU talimatlarını (instructions) donanımsal sayaçları (PMU – Performance Monitoring Unit) kullanarak analiz eder.

Neden Sadece CPU Yüzdesine Bakmak Yanıltıcıdır?

Çoğu mühendis CPU %100 olduğunda uygulamanın çok yoğun hesaplama yaptığını düşünür. Ancak durum her zaman bu değildir. CPU, RAM’den veri beklerken de (stall cycles) %100 meşgul görünebilir. Biz buna Memory-Bound (bellek sınırlandırılmış) deriz. Eğer CPU gerçekten matematiksel işlemler yapıyorsa buna da Compute-Bound denir.

Bu ayrımı görmek için ilk yapmamız gereken şey perf stat komutunu çalıştırmaktır:

# Belirli bir PID'yi 5 saniye boyunca analiz edelim
perf stat -p 1234 sleep 5

Karşımıza şöyle bir çıktı gelecektir:

 Performance counter stats for process id '1234':

       2001.45 msec task-clock                #    0.400 CPUs utilized          
              1234  context-switches          #    0.617 K/sec                  
                45  cpu-migrations            #    0.022 K/sec                  
               105  page-faults               #    0.052 K/sec                  
        4002938102  cycles                    #    2.000 GHz                    
        2001467291  instructions              #    0.50  insn per cycle         
         120495839  branches                  #   60.204 M/sec                  
           4958102  branch-misses             #    4.11% of all branches        

       5.002345123 seconds time elapsed

Burada odaklanmamız gereken en kritik metrik insn per cycle (IPC) değeridir. IPC (Instructions Per Cycle), CPU’nun her döngüde kaç talimat çalıştırdığını gösterir.

  • IPC < 1.0 ise: Uygulamanız muhtemelen I/O veya memory erişimi bekliyor (Memory-Bound). CPU boşta (stall) bekliyor demektir.
  • IPC > 1.5 ise: CPU gerçekten yoğun şekilde kodunuzu çalıştırıyor demektir (Compute-Bound).

Sıcak Noktaları Bulmak: perf record ve perf report

Uygulamanın tam olarak hangi fonksiyonunun CPU’yu sömürdüğünü bulmak için örnekleme (sampling) yapmamız gerekir. Bu işlem production ortamlarında genellikle %1 ila %5 arasında çok düşük bir overhead (ek yük) ile yapılabilir.

# Saniyede 99 frekansla (overkill olmaması için idealdir) 10 saniye boyunca kayıt alalım
# -g parametresi call-graph (çağrı ağacı) kaydetmesini sağlar
perf record -F 99 -p 1234 -g -- sleep 10

Bu komut geçerli dizinde perf.data adında bir dosya oluşturur. Bu dosyayı analiz etmek için terminalden şu komutu veririz:

perf report -n --stdio

Karşınıza çıkan interaktif arayüzde hangi fonksiyonun (sembolün) CPU döngülerinin yüzde kaçını harcadığını hiyerarşik bir şekilde görebilirsiniz. Eğer fonksiyon isimleri yerine [hexadecimal] adresler görüyorsanız, uygulamanızın “debug symbols” (hata ayıklama sembolleri) eksiktir demektir. Go kullanıyorsanız binary’nizi strip etmeyin, C++/Rust kullanıyorsanız -g flag’i ile derlediğinizden emin olun.

2. Karanlıkta Kalan Sistem Çağrıları: strace

Uygulamanız çalışıyor ama hiçbir şey yapmıyor gibi mi görünüyor? Dosya okumaya çalışırken kilitlenmiş olabilir mi? Yoksa DNS çözümlemesi yaparken timeout mu yaşıyor? Bu gibi durumlarda, uygulamanın kernel ile olan iletişimini yani System Calls (syscalls) trafiğini izlememiz gerekir.

İşte strace bu işin mutfağıdır.

Production Uyarısı: strace’i Dikkatli Kullanın!

Geliştirme ortamında strace my_app yazıp geçmek harikadır ancak bunu canlı production ortamında sakın yapmayın! strace, izlediği process’in her syscall yaptığında durdurulmasını (ptrace ile) sağlar. Bu durum, uygulamanın performansını %100 ila %1000 oranında düşürebilir.

Bunun yerine, production dostu parametrelerle nokta atışı yapmalıyız:

# Uygulamanın en çok hangi sistem çağrısında ne kadar zaman harcadığını özetleyelim
# Bu işlem ham log akıtmaya göre çok daha az overhead yaratır
strace -c -p 1234

Çıktı bize muhteşem bir özet sunacaktır:

% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 89.45    1.204958         120     10041           read
 10.12    0.136120          13     10200      1050 openat
  0.43    0.005789          11       500           write
------ ----------- ----------- --------- --------- ----------------
100.00    1.346867                 20741      1050 total

Yukarıdaki tabloda net bir şekilde görüyoruz ki, uygulama zamanının %89’unu read sistem çağrısında harcıyor ve ciddi miktarda openat (dosya açma hatası) alıyor. Hangi dosyayı açamadığını bulmak için sadece hatalı çağrıları filtreleyebiliriz:

# Sadece başarısız olan (errors) openat çağrılarını göster
strace -e trace=openat -Z -p 1234

3. Modern Çağın Sihirli Değneği: eBPF ve bpftrace

Geleneksel araçların kısıtlamalarından sıkıldıysanız, sahneye eBPF (Extended Berkeley Packet Filter) çıkıyor. eBPF, Linux çekirdeğinin kodunu değiştirmeden veya kernel modülü yüklemeden, güvenli sandbox’lar içinde doğrudan çekirdekte kod çalıştırmamızı sağlar.

bpftrace ise eBPF dünyasının “awk” dilidir. Son derece hafif, pratik ve neredeyse sıfır overhead ile çalışan tek satırlık (one-liners) script’ler yazmamıza olanak tanır.

Örnek 1: Disk I/O Latency Analizi (Biolatency)

Diskinizin yavaş olduğunu düşünüyorsunuz ama hangi process’in ne kadarlık bir gecikmeye sebep olduğunu bulamıyorsunuz. Geleneksel iostat size sadece ortalama değerler verir. bpftrace ile gerçek zamanlı bir histogram çizelim:

# Disk I/O tamamlanma sürelerini mikrosaniye cinsinden histogram olarak gösterir
bpftrace -e '
kprobe:vfs_read { @start[tid] = nsecs; } 
kretprobe:vfs_read /@start[tid]/ { 
    @latency = hist((nsecs - @start[tid]) / 1000); 
    delete(@start[tid]); 
}'

Bu script, her vfs_read (sanal dosya sistemi okuma) başladığında bir zaman damgası alır ve bittiğinde aradaki farkı hesaplayıp logaritmik bir grafik çizer:

@latency: 
[2, 4)                12 |@@@@                                        |
[4, 8)                85 |@@@@@@@@@@@@@@@@@@@@@@@@                    |
[8, 16)              142 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@|
[16, 32)              45 |@@@@@@@@@@@@                                |
[32, 64)               8 |@@                                          |

Bu grafiğe bakarak, okuma işlemlerinin büyük çoğunluğunun 8-16 mikrosaniye arasında tamamlandığını, yani diskimizin sağlıklı çalıştığını saniyeler içinde anlayabiliriz.

Örnek 2: Bellek Sızıntısı (Memory Leak) Avı

Sisteminizde gizemli bir bellek şişmesi var. Hangi uygulamanın sürekli malloc çağırıp free etmediğini bulmak istiyorsunuz. eBPF ile kullanıcı alanındaki (user-space) bellek allocation çağrılarını takip edebiliriz:

# libc içindeki malloc çağrılarının byte boyutlarına göre dağılımını izleyelim
bpftrace -e 'usdt:/lib/x86_64-linux-gnu/libc.so.6:libc:memory_malloc_retry { @[arg0] = count(); }'

Bu sayede production ortamında çalışan uygulamanıza hiçbir kütüphane enjekte etmeden veya onu durdurmadan bellek tüketim kalıplarını yakalayabilirsiniz.

Özet ve Doğru Aracı Seçme Rehberi

Sistem tıkandığında hangi aracı ne zaman kullanacağınıza karar vermek, tecrübeli bir mühendisi amatörden ayıran en önemli özelliktir. İşte size pratik bir başucu tablosu:

Senaryo / Belirti Kullanılacak Araç Neden?
Yüksek CPU kullanımı var, hangi kod satırının yavaş olduğunu bulmak istiyorum. perf record / report Düşük overhead ile CPU çağrı ağacını (call graph) çıkarır.
Uygulama kilitlendi (stuck), log üretmiyor, ne yaptığını göremiyorum. strace -p <PID> Çekirdeğe gönderdiği sistem çağrılarını (network, dosya erişimi) anlık listeler.
Disk performansından şüpheleniyorum ama genel istatistikler yetersiz kalıyor. bpftrace (eBPF) Kernel seviyesinde I/O kuyruk gecikmesini histogram olarak gösterir.
Sistemin donanımsal düzeyde (L1/L2 Cache, CPU Cycles) analizine ihtiyacım var. perf stat İşlemcinin donanımsal sayaçlarına doğrudan erişim sağlar.

Performans analizi yaparken her zaman en az invaziv (sisteme en az müdahale eden) yöntemden başlayın. Önce perf stat ve bpftrace ile genel resmi görün, gerekirse ve güvenliyse strace ile derinlemesine inceleme yapın. Unutmayın, iyi bir SRE sadece sorunu çözen değil, sorunu çözerken production sistemini ayakta tutabilen kişidir!

Category: Genel | LEAVE A COMMENT
Eylül 20 2024

Bash’te Hata Yönetimi: set -euo pipefail ile Güvenli Script Yazımı

Hepimiz oradaydık: Gece yarısı gelen bir PagerDuty alarmı, çöken bir production sunucusu ve kaynağı belirsiz, yarıda kesilmiş bir deploy süreci. Loglara baktığınızda ise “exit 0” ile başarıyla tamamlanmış görünen ama arkasında enkaz bırakmış bir script görüyorsunuz. Modern devops ve altyapı otomasyonu dünyasında, linux üzerinde koşan bash scripting süreçleri hala sistemlerin can damarıdır. Ancak Kubernetes manifestleri veya Terraform kodları yazarken gösterdiğimiz özeni, maalesef bu kabuk script’lerine göstermiyoruz. “Çalışıyorsa dokunma” felsefesi, ilk production kazasına kadar kulağa hoş gelir.

Bu yazıda, Bash script’lerinizi “çocuk oyuncağı” olmaktan çıkarıp, kurumsal seviyede hata toleranslı ve güvenli hale getirecek teknikleri ele alacağız. Sadece komutları sıralamayacağız; arkasındaki “neden” sorusuna yanıt arayacağız.

Sessiz Katilleri Durdurun: set -euo pipefail

Bash’in varsayılan davranışı inanılmaz derecede affedicidir. Bir satır hata verse bile, script bir sonraki satırdan neşeyle çalışmaya devam eder. Bu durum, otomasyon süreçlerinde tam bir felaket senaryosudur. Bu vurdumduymazlığı engellemenin yolu, script’in en başına o sihirli satırı eklemektir:

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

Peki bu parametreler tam olarak ne işe yarıyor? Tek tek inceleyelim ve neden hayati olduklarını görelim.

1. set -e (Exit on Error)

Varsayılan olarak Bash, sıfırdan farklı (non-zero) bir exit code ile dönen komutları önemsemez. Örneğin, veritabanı yedeği almaya çalışan bir script düşünün:

pg_dump -U admin mydb > backup.sql
tar -czf backup.tar.gz backup.sql
aws s3 cp backup.tar.gz s3://my-bucket/

Eğer pg_dump komutu yetki hatasından dolayı başarısız olursa, script durmaz. Boş bir backup.sql dosyasını sıkıştırır ve S3’e yükler. Pipeline’ınız yeşil yanar ama elinizde yedek yoktur! set -e (veya uzun adıyla set -o errexit), herhangi bir komut başarısız olduğunda script’in anında sonlanmasını sağlar.

2. set -u (Nounset / Unset Variables)

Bash’te tanımlanmamış bir environment variable kullanmaya çalışırsanız, Bash bunu sessizce boş bir string olarak kabul eder. Şu meşhur felaket senaryosuna bakalım:

TARGET_DIR="" # Bir hata sonucu boş kaldı
rm -rf "$TARGET_DIR/*"

Eğer set -u (veya set -o nounset) aktif değilse, bu komut rm -rf /* olarak çalışacak ve sisteminizi silecektir. Bu parametre açık olduğunda, tanımlanmamış bir değişken kullanıldığı anda Bash çalışmayı durdurur ve unbound variable hatası verir.

3. set -o pipefail

İşte en çok gözden kaçan parametre. set -e tek başına pipeline (boru hattı) kullanan komutlardaki hataları yakalayamaz. Örneğin:

non_existent_command | grep "foo"

Burada ilk komut hata verecektir (exit code 127). Ancak pipeline’ın toplam exit code’u, en son komutun (yani grep’in) exit code’udur. Grep başarıyla çalıştığı için (veya eşleşme bulamadığı için) tüm satır başarılı kabul edilir. set -o pipefail eklediğimizde, pipeline içindeki herhangi bir komut hata verirse, tüm zincir başarısız kabul edilir.

Geri Temizlik (Cleanup) ve Trap Mekanizması

Script’imiz hata aldığında veya yarıda kesildiğinde (örneğin kullanıcı Ctrl+C yaptığında), arkasında geçici dosyalar, kilit (lock) dosyaları veya açık portlar bırakabilir. Linux dünyasında bu durum “resource leak” olarak adlandırılır. Bash’in sunduğu trap mekanizması, script nasıl sonlanırsa sonlansın (ister başarıyla, ister hata ile) çalışacak temizlik rutinleri yazmamızı sağlar.

Aşağıdaki örneği inceleyelim:

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

# Geçici bir dosya oluşturalım
TEMP_FILE=$(mktemp /tmp/api_response.XXXXXX)

# Temizlik fonksiyonu
cleanup() {
    echo "⚙️ Temizlik yapılıyor: ${TEMP_FILE} siliniyor..."
    rm -f "$TEMP_FILE"
}

# EXIT sinyalini yakala ve cleanup fonksiyonunu çalıştır
trap cleanup EXIT

# Script ana gövdesi
echo "Veri çekiliyor..."
curl -s https://api.kertenkerem.net/status > "$TEMP_FILE"

# Eğer burada bir hata olursa bile, trap sayesinde cleanup çalışacaktır.
grep -q "SUCCESS" "$TEMP_FILE"
echo "İşlem başarıyla tamamlandı."

Burada trap cleanup EXIT tanımı sayesinde, script normal bir şekilde bittiğinde veya aradaki bir komut hata verip script’i sonlandırdığında cleanup fonksiyonu otomatik olarak tetiklenir.

Hata Detaylarını Yakalamak: ERR Sinyali

Sadece temizlik yapmak yetmez, bazen hatanın hangi satırda ve hangi fonksiyon içinde gerçekleştiğini loglamak isteriz. Bunun için ERR sinyalini yakalayabiliriz:

failure_handler() {
    local exit_code=$?
    local line_no=$1
    echo "❌ HATA: Script ${line_no}. satırda, exit code ${exit_code} ile çöktü!" >&2
    # Buraya Slack/Teams webhook entegrasyonu eklenebilir.
}

trap 'failure_handler ${LINENO}' ERR

Exit Code Standartları

Yazdığınız script’lerin birer “iyi vatandaş” olması gerekir. Yani başka bir program (örneğin Jenkins, GitHub Actions veya GitLab CI) sizin script’inizi çağırdığında, neyin yanlış gittiğini sadece loglardan değil, exit code’dan da anlayabilmelidir.

Her zaman sadece exit 1 kullanmak tembelliktir. POSIX standartlarına göre bazı exit code’ların özel anlamları vardır:

  • 0: Başarılı sonlanma.
  • 1: Genel bilinmeyen hatalar.
  • 2: Hatalı argüman veya CLI parametresi kullanımı (Misuse of shell builtins).
  • 126: Komut çalıştırılamadı (Permission denied / Not executable).
  • 127: Komut bulunamadı (Command not found).
  • 128+n: Fatal error signal “n” (Örn: Ctrl+C ile sonlandırma için 130).

Kendi script’lerinizde özel hata durumları için 64-113 arasındaki değerleri kullanabilirsiniz. Örneğin:

readonly ERR_DB_CONNECTION=74
readonly ERR_INVALID_CONFIG=78

if ! ping -c 1 "$DB_HOST" &> /dev/null; then
    echo "Veritabanına erişilemiyor!" >&2
    exit "$ERR_DB_CONNECTION"
fi

Bash Script Test Edilir mi? Karşınızda: Bats (Bash Automated Testing System)

“Script’i yazdım, bir kere manuel çalıştırdım, çalışıyor” mantığı modern DevOps pratiklerine aykırıdır. Altyapı kodunuz değiştikçe, yazdığınız script’lerin de test edilmesi gerekir. Bunun için en popüler framework Bats-core‘dur.

Bats, script’lerinizi gerçek assertion’lar ile test etmenizi sağlayan TAP (Test Anything Protocol) uyumlu bir test aracıdır.

Örnek Bir Bats Testi

Öncelikle test etmek istediğimiz küçük bir fonksiyonumuz olsun (helper.sh):

# helper.sh
is_semver() {
    local version=$1
    if [[ $version =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
        return 0
    else
        return 1
    fi
}

Şimdi bu fonksiyon için yazacağımız test dosyası (helper.bats):

# helper.bats
setup() {
    source ./helper.sh
}

@test "Geçerli semver formatını doğrula" {
    run is_semver "1.2.3"
    [ "$status" -eq 0 ]
}

@test "Geçersiz semver formatını reddet" {
    run is_semver "v1.2"
    [ "$status" -eq 1 ]
}

CI/CD pipeline’ınızda bats helper.bats komutunu çalıştırarak bu testleri otomatik hale getirebilirsiniz. Böylece birisi script’i refactor ettiğinde bir şeylerin kırılıp kırılmadığını anında görebilirsiniz.

Özet: Kurşun Geçirmez Bir Bash Şablonu

Tüm bu öğrendiklerimizi bir araya getiren, yeni projelerinizde doğrudan kopyalayıp kullanabileceğiniz güvenli bir Bash şablonu ile yazıyı sonlandıralım:

#!/usr/bin/env bash

# Güvenlik flag'leri
set -euo pipefail
IFS=$'\n\t'

# Script dizinini bul (Path bağımsız çalışabilmek için)
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

# Global Hata Yakalama
error_handler() {
    local exit_code=$?
    local line_no=$1
    echo "❌ Hata oluştu! Satır: ${line_no} | Exit Code: ${exit_code}" >&2
    cleanup
    exit "$exit_code"
}

cleanup() {
    echo "🧹 Geçici kaynaklar temizleniyor..."
    # Temizlik komutları buraya
}

# Sinyalleri dinle
trap 'error_handler ${LINENO}' ERR
trap cleanup EXIT

# Ana Kod
main() {
    echo "🚀 Script başlatılıyor, dizin: ${SCRIPT_DIR}"
    # Uygulama mantığınız buraya gelecek
}

main "$@"

Bash scripting, doğru yapılandırılmadığında production ortamlarında saatli bir bombaya dönüşebilir. Ancak yukarıdaki pratikleri benimseyerek, kodunuzun öngörülebilir, test edilebilir ve en önemlisi güvenli olmasını sağlayabilirsiniz.

Category: Genel | LEAVE A COMMENT
Eylül 13 2024

Elastic Fleet ile Merkezi Agent Yönetimi: Elveda Config Karmaşası!

DevOps dünyasında monitoring ve log yönetimi denince akla gelen ilk isimlerden biri şüphesiz Elastic Stack. Ancak her sunucuya ayrı Filebeat, Metricbeat, Heartbeat kurup, Ansible playbook’ları içinde YAML indentasyon hatalarıyla boğuştuğumuz günler geride kaldı. Modern altyapılarda artık “single binary” felsefesi ve merkezi yönetim ön planda. İşte bu noktada sahneye elastic stack’in kurtarıcısı olan fleet ve onun her işe koşan askeri agent çıkıyor.

Bu makalede, production ortamlarında hayat kurtaran Elastic Fleet mimarisini, Fleet Server kurulumunu, policy yönetimini ve sisteme dinamik entegrasyonlar eklemeyi derinlemesine inceleyeceğiz. Çayınızı kahvenizi alın; Ansible playbook’larınızdaki Beats rollerini silmeye hazırlanıyoruz.

Neden Elastic Fleet? Eski Usul Beats vs. Modern Agent

Eski günleri hatırlayalım. Bir web sunucunuz var ve hem sistem metriklerini (CPU, RAM) hem de Nginx loglarını toplamak istiyorsunuz. Süreç kabaca şöyle işliyordu:

  • Sunucuya Metricbeat indir, kur, metricbeat.yml konfigüre et, servisi başlat.
  • Filebeat indir, kur, Nginx modülünü aktif et, filebeat.yml içinde Elasticsearch adresini gir, servisi başlat.
  • Elasticsearch cluster adresi veya şifresi değiştiğinde, tüm sunuculardaki o meşhur YAML dosyalarını CM (Configuration Management) araçlarıyla güncelle.

Bu yaklaşım ölçeklendikçe tam bir kabusa dönüşüyor. Elastic Agent ise bu karmaşayı tek bir binary altında topluyor. Agent arka planda gereken tüm “Beat” işlevlerini barındırıyor ve en önemlisi, konfigürasyonunu lokal bir dosyadan değil, doğrudan Kibana üzerinde koşan Fleet Server’dan dinamik olarak çekiyor. Siz Kibana arayüzünden tek bir tıkla yeni bir log kaynağı eklediğinizde, hedef sunucudaki Elastic Agent saniyeler içinde yeni policy’yi uyguluyor. Ne restart gerekiyor, ne de sunucuya SSH atmak.

Fleet Server Mimarisi: Olayın Arkasındaki Beyin

Fleet yapısını kurmadan önce mimariyi anlamak şart. Fleet, merkezi yönetim katmanıdır ve Kibana ile entegre çalışır. Ancak Kibana, binlerce agent ile doğrudan ham veri alışverişi yapacak şekilde tasarlanmamıştır. Bu yüzden araya Fleet Server girer.

Fleet Server, Elastic Agent’ların bağlandığı, policy güncellemelerini aldığı ve durum raporladığı bir kontrol merkezidir. Kendisi de aslında özel bir policy ile çalışan bir Elastic Agent’tır. Agent’lar Fleet Server’a HTTPS üzerinden bağlanır (genellikle port 8220). Fleet Server ise aldığı durumları ve metrikleri doğrudan Elasticsearch’e yazar.

Adım Adım Fleet Server Kurulumu (Self-Hosted)

Eğer Elastic Cloud kullanıyorsanız Fleet Server sizin için otomatik olarak yönetilir. Ancak gerçek DevOps mühendisleri olarak biz self-hosted yapıları severiz (veya mecbur kalırız). Gelin, kendi altyapımızda Fleet Server’ı Docker üzerinde ayağa kaldıralım.

Adım 1: Elasticsearch ve Kibana Hazırlığı

Öncelikle halihazırda HTTPS korumalı bir Elasticsearch cluster’ınız ve Kibana’nız olduğunu varsayıyoruz. Fleet Server’ın Elasticsearch ile haberleşebilmesi için bir “Service Token” üretmemiz gerekiyor. Kibana Console (Dev Tools) üzerinden şu komutla token’ımızı alalım:

POST _security/service/elastic/fleet-server/token/my-fleet-token

Dönen yanıttaki value değerini güvenli bir yere kaydedin. Bu bizim FLEET_SERVER_ELASTICSEARCH_SERVICE_TOKEN değerimiz olacak.

Adım 2: Fleet Server’ı Docker ile Çalıştırma

Aşağıdaki Docker komutu ile Fleet Server’ı ayağa kaldırabiliriz. Üretim ortamında mutlaka geçerli SSL sertifikaları kullanmalısınız, ancak test ortamı için doğrulama adımlarını esnetebiliriz.

docker run -d \
  --name fleet-server \
  -p 8220:8220 \
  -e FLEET_SERVER_ENABLE=1 \
  -e FLEET_SERVER_ELASTICSEARCH_HOST=https://elasticsearch.local:9200 \
  -e FLEET_SERVER_ELASTICSEARCH_SERVICE_TOKEN=AAEAAWVsYXN0aWMvZmxlZXQtc2VydmVyL3Rva2VuLTE2N... \
  -e FLEET_URL=https://fleet-server.local:8220 \
  -e FLEET_SERVER_CERT=/usr/share/elastic-agent/certs/fleet.crt \
  -e FLEET_SERVER_KEY=/usr/share/elastic-agent/certs/fleet.key \
  -v /path/to/certs:/usr/share/elastic-agent/certs \
  docker.elastic.co/beats/elastic-agent:8.11.1

Bu komutla Fleet Server, 8220 portundan agent bağlantılarını kabul etmeye hazır hale gelecektir. Kibana arayüzünde Management > Fleet sekmesine gittiğinizde Fleet Server’ın “Healthy” durumuna geçtiğini görmelisiniz.

Elastic Agent Kurulumu ve Fleet’e Enroll Etme

Sırada log ve metrik toplayacağımız hedef sunuculara (örneğin bir Linux VM) Elastic Agent kurmak var. Agent kurulumu son derece basittir çünkü tek bir binary indirip çalıştırmaktan ibarettir.

Adım 1: Agent Paketini İndirme

Hedef sunucuda terminale çıkıp işletim sistemimize uygun paketi indiriyoruz:

curl -L -O https://artifacts.elastic.co/downloads/beats/elastic-agent/elastic-agent-8.11.1-linux-x86_64.tar.gz
tar xzvf elastic-agent-8.11.1-linux-x86_64.tar.gz
cd elastic-agent-8.11.1-linux-x86_64

Adım 2: Enrollment (Kayıt) İşlemi

Agent’ı sisteme kurarken onu Fleet Server’a “enroll” etmemiz, yani kaydetmemiz gerekir. Bunun için Kibana > Fleet > Enrollment Tokens sekmesinden hedef policy için üretilmiş token’ı alıyoruz. Ardından şu komutla kurulumu tetikliyoruz:

sudo ./elastic-agent install \
  --url=https://fleet-server.local:8220 \
  --enrollment-token=U0ZVMWhvMEJGUnp6eE5iNUp0eXU6cUpnZXA3V... \
  --certificate-authorities=/path/to/ca.crt

Bu komut arka planda şunları yapar:

  • Agent’ı sistem servisi olarak kaydeder (systemd).
  • Gerekli dizin yapılarını (/etc/elastic-agent, /var/log/elastic-agent) oluşturur.
  • Fleet Server ile el sıkışarak ilk policy’sini indirir ve servisi başlatır.

Servisin durumunu doğrulamak için:

sudo systemctl status elastic-agent

Policy Yönetimi: Tek Merkezden Gücü Dağıtmak

Elastic Fleet’in en güçlü yanı policy tabanlı yönetimdir. Bir policy; hangi logların toplanacağını, hangi metriklerin hangi sıklıkla çekileceğini belirten bir kurallar bütünüdür. Sunucularınızı rollerine göre gruplayabilirsiniz (örn: web-servers-policy, db-servers-policy).

Bir sunucunun davranışını değiştirmek istediğinizde, tek yapmanız gereken Kibana arayüzünden ilgili policy’yi editlemektir. Örneğin, tüm web sunucularında Apache log takibini kapatıp yerine Nginx log takibini açmak istiyorsunuz. Bu değişikliği policy üzerinde yaptığınız an, o policy’ye bağlı olan 500 sunucudaki Elastic Agent saniyeler içinde kendini günceller.

Pratik Örnek: Nginx Entegrasyonu Ekleme

Gelin, çalışan bir Elastic Agent’a Kibana üzerinden nasıl dinamik entegrasyon ekleyeceğimize bakalım:

  1. Kibana > Fleet > Agent Policies sayfasına gidin.
  2. Agent’ınızın bağlı olduğu policy’yi seçin.
  3. Add Integration butonuna tıklayın.
  4. Arama çubuğuna Nginx yazın ve entegrasyonu seçin.
  5. Nginx log dosyalarının yollarını (varsayılan: /var/log/nginx/access.log) ve metrik endpoint’ini (stub_status) konfigüre edin.
  6. Save Integration butonuna basarak kaydedin.

Arkanıza yaslanın. Hedef sunucudaki agent, konfigürasyon değişikliğini algılayacak, arka planda Nginx loglarını okumaya başlayacak ve Kibana’da hazır gelen “Nginx Overview” dashboard’larını verilerle dolduracaktır. Sıfır konfigürasyon dosyası editlendi, sıfır servis restart edildi!

Prodüksiyon Tavsiyeleri ve Savaş Hikayeleri

Her şey kağıt üzerinde harika görünse de, production ortamlarında Fleet koştururken dikkat etmeniz gereken bazı can alıcı noktalar var:

1. Resource Limits (Kaynak Sınırları)

Elastic Agent, eski Beats’lere göre biraz daha fazla memory tüketebilir çünkü arka planda birden fazla süreci yönetir. Özellikle çok sayıda log dosyası (wildcard ile binlerce log) izleniyorsa, agent memory limitlerine takılabilir. Sunucularınızda elastic-agent‘ın CPU ve RAM tüketimini mutlaka monitor edin.

2. SSL/TLS Sertifikaları

Fleet Server ile Agent arasındaki iletişim kesinlikle HTTPS olmalıdır. Self-signed sertifika kullanıyorsanız, agent kurulumu sırasında --certificate-authorities parametresiyle CA sertifikanızı belirtmeyi unutmayın. Aksi takdirde agent’lar “x509: certificate signed by unknown authority” hatasıyla kaydolmayacaktır.

3. Fleet Server Ölçekleme

Tek bir Fleet Server kabaca 1000 ila 2000 agent’a kadar sorunsuz hizmet verebilir. Ancak binlerce agent’ın olduğu büyük altyapılarda, Fleet Server’ı arkasında bir Load Balancer (HAProxy, AWS ALB vb.) olacak şekilde multi-node mimaride kurmalısınız.

Fleet Server’ları ölçeklerken şu parametre ile JVM heap boyutunu ve max connection limitlerini optimize edebilirsiniz:

# Örnek environment değişkeni
-e FLEET_SERVER_MAX_CONNECTIONS=5000

Özet

Elastic Fleet ve Agent ikilisi, modern DevOps dünyasındaki monitoring operasyonlarını büyük ölçüde kolaylaştırıyor. Altyapınızı kodla yönetirken (IaC), sunuculara sadece tek bir agent kurup geri kalan tüm konfigürasyonu UI veya Fleet API’leri üzerinden yönetmek, operasyonel yükü ciddi oranda azaltır. Eğer hala eski usul Beats konfigürasyonlarıyla uğraşıyorsanız, Fleet’e geçiş planını hemen sprint’inize eklemenizi öneririm.

Category: Genel | LEAVE A COMMENT
Eylül 6 2024

Logstash Pipeline Tasarımı: Filter ve Grok Desenlerinde Uzmanlık

Production ortamında koşan yüzlerce mikroservisin loglarını merkezi bir elk stack’e toplamak işin sadece başlangıcıdır. Gerçek kaos, o anlamsız string yığınlarını anlamlı metrikler ve aranabilir alanlar haline getirmek istediğinizde, yani log parsing aşamasında başlar. Bu yazıda, logstash üzerinde performans canavarı bir pipeline nasıl tasarlanır, karmaşık log formatları grok desenleri ile CPU’yu ağlatmadan nasıl dize getirilir ve parsing hatalarıyla nasıl başa çıkılır derinlemesine inceleyeceğiz. Kahvenizi alın, çünkü regex (regular expressions) derinliklerine iniyoruz.

Grok ile Savaşmak: Regex Cehenneminden Nasıl Kurtuluruz?

Logstash dünyasında Grok, yapılandırılmamış (unstructured) metin verilerini yapılandırılmış (structured) JSON formatına dönüştürmek için kullandığımız en güçlü silahtır. Ancak yanlış ellerde bu silah, pipeline üzerinde ciddi darboğazlara (bottleneck) yol açabilir. Grok aslında arkada regex çalıştırır. Eğer yazdığınız regex deseni verimsiz ise, Logstash JVM heap limitlerini zorlar ve CPU tüketiminiz tavan yapar.

Farz edelim ki elimizde şöyle karmaşık, custom bir Java uygulama logu var:

2023-11-23T14:32:10.123Z [thread-pool-4] INFO  c.k.service.UserService [TX-98123] - User login failed for user 'kertenkerem_devops' from IP 192.168.1.50 - Reason: Invalid credentials

Bu logu parse etmek için yazacağımız klasik, hantal ve performansı düşük Grok filtresi genelde şuna benzer:

# TAVSİYE EDİLMEYEN YÖNTEM (Yavaş ve hantal)
filter {
  grok {
    match => { "message" => "%{TIMESTAMP_ISO8601:timestamp} \[%{DATA:thread}\] %{LOGLEVEL:level}  %{NOTSPACE:class} \[%{DATA:tx_id}\] - %{GREEDYDATA:log_message}" }
  }
}

Neden tavsiye etmiyoruz? Çünkü %{DATA} ve %{GREEDYDATA} desenleri regex motorunu çok yorar. Logstash, eşleşme bulabilmek için metnin sonuna kadar gider, bulamazsa geri döner (backtracking). Bu da milyonlarca log satırında CPU fanlarınızın jet motoru gibi bağırmasına neden olur.

Doğru ve Optimize Grok Tasarımı

Aynı logu daha spesifik desenlerle, sınırları (anchors) belirleyerek parse edelim. Ayrıca kendi custom pattern’lerimizi tanımlayarak okunabilirliği artıralım:

filter {
  grok {
    pattern_definitions => {
      "THREAD_NAME" => "[a-zA-Z0-9-]+"
      "TX_ID" => "TX-[0-9]+"
    }
    match => { 
      "message" => "^%{TIMESTAMP_ISO8601:timestamp} \[%{THREAD_NAME:thread}\] %{LOGLEVEL:level}\s+%{NOTSPACE:class} \[%{TX_ID:tx_id}\] - (?<action>[a-zA-Z\s]+) '%{USER:username}' from IP %{IP:client_ip} - Reason: %{GREEDYDATA:reason}$" 
    }
  }
}

Neden bu daha iyi?

  • Satır başı (^) ve satır sonu ($) işaretleyicilerini kullanarak regex motorunun tüm satırı gereksiz yere tekrar tekrar taramasını engelledik.
  • %{DATA} yerine özel olarak tanımladığımız %{THREAD_NAME} ve regex limitli capture gruplarını kullandık.
  • \s+ ile boşluk miktarı değişse bile eşleşmenin kırılmamasını sağladık.

Conditional Filters: Akıllı Pipeline Yönlendirmesi

Logstash pipeline’ınızda tüm log türlerini aynı Grok filtresinden geçirmek tam bir kaynak israfıdır. Nginx logu ile Java logu aynı pipeline’da karşılaşabilir. Çözüm, conditional (koşullu) filtreler kullanarak logları doğru parser’lara yönlendirmektir.

Aşağıdaki örnekte log kaynağına göre (örneğin Kubernetes namespace veya log tipi) nasıl filtreleme yapacağımızı görebilirsiniz:

filter {
  # Kubernetes metadata'sından gelen namespace bilgisine göre ayırma
  if [kubernetes][namespace] == "production" {
    mutate { add_tag => [ "prod_environment" ] }
    
    if [kubernetes][container_name] == "nginx-ingress" {
      grok {
        match => { "message" => "%{NGINXACCESS}" }
        remove_tag => [ "_grokparsefailure" ] # Başarılıysa default failure tag'ini ez
      }
    } else if [kubernetes][container_name] == "payment-api" {
      grok {
        match => { "message" => "%{TIMESTAMP_ISO8601:timestamp} %{LOGLEVEL:level} %{GREEDYDATA:msg}" }
      }
    }
  } else {
    # Non-prod veya diğer loglar için hafif bir parser
    mutate { add_tag => [ "non_prod" ] }
  }
}

Bu yaklaşım sayesinde, sadece ilgili konteynerden gelen loglar ilgili Grok desenine maruz kalır. Boş yere tüm regex kuralları her log satırı için koşturulmaz.

Dead Letter Queue (DLQ) ve Parse Hatalarının Yönetimi

Günün birinde yazılımcı ekibinden biri log formatını değiştirir (çünkü neden değiştirmesinler ki?) ve pipeline patlar. Logstash bu durumda _grokparsefailure tag’ini basar ve logu Elasticsearch’e hatalı haliyle yazar. Eğer kritik bir audit logu parse edilemediği için kaybolursa başınız ağrıyabilir.

Logstash, teslim edilemeyen veya işlenemeyen event’ler için bir Dead Letter Queue (DLQ) mekanizması sunar. Bu mekanizmayı aktifleştirmek için logstash.yml dosyanızda şu ayarı yapmanız gerekir:

# logstash.yml
dead_letter_queue.enable: true
dead_letter_queue.max_bytes: 1024mb
path.dead_letter_queue: "/var/lib/logstash/dead_letter_queue"

DLQ’ya düşen bu bozuk logları okumak ve analiz etmek için ayrı bir Logstash pipeline’ı tasarlayabilirsiniz. Örneğin, hatalı logları tespit edip Slack’e alert atan veya bunları incelemek üzere ayrı bir “failed-logs” index’ine basan bir pipeline kurabiliriz:

# dlq_pipeline.conf
input {
  dead_letter_queue {
    path => "/var/lib/logstash/dead_letter_queue"
    commit_offsets => true
    pipeline_id => "main"
  }
}

output {
  elasticsearch {
    hosts => ["https://elasticsearch.kertenkerem.local:9200"]
    index => "logstash-dlq-%{+YYYY.MM.dd}"
    user => "logstash_writer"
    password => "highly_secure_password"
    ssl => true
    cacert => "/etc/logstash/certs/ca.crt"
  }
}

Logstash Performans Optimizasyonu: canavar gibi çalışan pipeline’lar

Eğer saniyede on binlerce log event’i işliyorsanız, Logstash tuning kaçınılmaz hale gelir. İşte production ortamlarında bizzat uyguladığımız hayat kurtaran performans tüyoları:

1. Grok Yerine Dissect Kullanın

Log formatınız belirli ve sabit bir ayırıcıya (delimiter) sahipse (örneğin Virgülle ayrılmış CSV, Tab veya Pipe | karakterleri), Grok kullanmayın! Dissect plugini, regex kullanmadan sadece string operasyonları ile parse işlemi yapar ve Grok’a göre neredeyse 10 kat daha hızlıdır.

# Dissect Örneği: Log formatı [Thread] LEVEL - Message şeklindeyse
filter {
  dissect {
    mapping => {
      "message" => "[%{thread}] %{level} - %{msg}"
    }
  }
}

2. Pipeline Workers Ayarı

Logstash varsayılan olarak CPU çekirdek sayınız kadar worker thread oluşturur. Ancak yoğun I/O işlemlerinde bu yetmeyebilir. logstash.yml içinde bu değeri optimize edin:

pipeline.workers: 8 # CPU core sayınızın 1.5 - 2 katı civarında tutabilirsiniz
pipeline.batch.size: 250 # Tek seferde Elasticsearch'e gönderilecek event sayısı

Batch size değerini artırmak throughput (iş çıkarma yeteneği) artışına sebep olurken, RAM tüketimini artırır. 125 ila 500 arasında ideal noktanızı test ederek bulmalısınız.

3. ECS (Elastic Common Schema) Uyumluluğu

Modern ELK mimarisinde log alanlarının (fields) standartlaştırılması çok önemlidir. Logstash filtrelerinizde yazdığınız output’ların ECS uyumlu olması, Kibana üzerinde dashboard yaparken veya SIEM kuralları yazarken hayatınızı kurtarır. Örneğin client_ip yerine source.ip, username yerine user.name kullanmaya özen gösterin.

Özet ve Kapanış

Verimli bir logstash pipeline tasarımı, sadece logları parse etmekle kalmaz; production ortamınızın gözlemleyebilirliğini (observability) artırırken altyapı maliyetlerinizi de düşürür. Grok yazarken regex sınırlarına dikkat etmek, conditional filter’lar ile akıllı routing yapmak ve DLQ ile hata yönetimini otomatikleştirmek sizi sıradan bir sistem yöneticisinden uzman bir DevOps mühendisine dönüştürür. Loglarınız temiz, kuyruklarınız boş olsun!

Category: Genel | LEAVE A COMMENT