Log Tabanlı Uyarı Sistemi Kurulumu
Sunuculardan akan log verisi, çoğu zaman bir felaketin habercisini gizli tutar. Disk dolmaya başlar, uygulama hatalar fırlatır, birisi sisteme brute-force dener; ama siz sabah kahvenizi içerken bunların hiçbirinden haberiniz olmaz. İşte bu yüzden pasif log izleme yetmez. Logları aktif olarak analiz eden, anlamlı olayları yakalayan ve sizi anında uyaran bir sistem kurmak, modern bir sysadmin’in temel sorumluluklarından biridir.
Bu yazıda sıfırdan işe yarar bir log tabanlı uyarı sistemi kuracağız. Rsyslog, Logwatch, Fail2Ban, Loki + Grafana ve özel Bash betikleri kullanarak gerçek dünya senaryolarına dayalı bir altyapı oluşturacağız.
Neden Log Tabanlı Uyarı?
Metrik tabanlı izleme araçları (Prometheus, Zabbix gibi) CPU kullanımını, bellek doluluk oranını, disk I/O’sunu takip eder. Bunlar harika araçlar. Ancak bazı kritik olaylar sayısal metriklerde iz bırakmaz ya da bıraktığında iş işten geçmiş olur.
Örneğin:
/var/log/auth.logiçindeFailed passwordsatırları birikiyorsa biri sisteme girmeye çalışıyor demektir- Uygulama logunda
NullPointerExceptionsayısı artıyorsa deployment bozuk gitmiştir kern.logiçindeOut of memory: Kill processgörüyorsanız OOM killer çalışmış, bir servis düşmüştür- Nginx access logunda 502 oranı yükseliyorsa backend sağlıksızdır
Bunların hiçbiri bir CPU grafiğine direkt yansımaz. Ama hepsi loglarda açık açık yazar.
Sistem Mimarisine Genel Bakış
Kuracağımız sistemin temel katmanları şöyle:
- Log Toplama: Rsyslog ile merkezi log yönlendirme
- Kural Tabanlı Uyarı: Bash + cron ile özel log analizi
- Otomatik Engelleme: Fail2Ban ile brute-force koruması
- Görsel İzleme ve Uyarı: Loki + Grafana stack’i
- Bildirim Katmanı: E-posta, Slack webhook ve Telegram bot entegrasyonu
Ortam olarak Ubuntu 22.04 / Debian tabanlı sistemleri baz alıyoruz, ancak çoğu konfigürasyon RHEL/CentOS’ta da minimal değişiklikle çalışır.
Rsyslog ile Merkezi Log Toplama
Birden fazla sunucunuz varsa logları tek noktada toplamak, uyarı sistemini çok daha yönetilebilir kılar. Rsyslog bu iş için hala en güvenilir araçlardan biridir.
Sunucu Tarafı Konfigürasyonu
Log toplama sunucusunda (logserver) şu konfigürasyonu yapıyoruz:
# /etc/rsyslog.conf dosyasına ekle veya /etc/rsyslog.d/server.conf oluştur
# UDP ve TCP dinleme aktif et
module(load="imudp")
input(type="imudp" port="514")
module(load="imtcp")
input(type="imtcp" port="514")
# Gelen logları kaynak IP'ye göre ayrı dosyalara yaz
$template RemoteLogs,"/var/log/remote/%HOSTNAME%/%PROGRAMNAME%.log"
*.* ?RemoteLogs
& ~
# Dizin izinlerini ayarla
mkdir -p /var/log/remote
chown syslog:adm /var/log/remote
chmod 755 /var/log/remote
# Rsyslog'u yeniden başlat
systemctl restart rsyslog
# Firewall kuralı ekle
ufw allow 514/tcp
ufw allow 514/udp
İstemci Tarafı Konfigürasyonu
Log gönderecek sunucularda ise şunu yapıyoruz:
# /etc/rsyslog.d/remote.conf
# Tüm logları merkezi sunucuya ilet
# @ UDP, @@ TCP demektir
*.* @@logserver.sirketim.com:514
# Bağlantı kopuksa kuyruğa al
$ActionQueueFileName queue
$ActionQueueMaxDiskSpace 1g
$ActionQueueSaveOnShutdown on
$ActionQueueType LinkedList
$ActionResumeRetryCount -1
Artık tüm sunucuların logları tek bir yerde toplanıyor. Uyarı sistemimizi bu merkezi sunucu üzerinde çalıştıracağız.
Bash ile Özel Log İzleme Betiği
Hazır araçlara geçmeden önce, log izlemenin temelini anlayalım. Basit ama etkili bir izleme betiği şu mantıkla çalışır: düzenli aralıklarla logu tara, belirlenen kalıpları bul, eşik aşıldıysa uyar.
#!/bin/bash
# /usr/local/bin/log-monitor.sh
# Log tabanlı uyarı betiği
# Konfigürasyon
LOG_FILE="/var/log/nginx/error.log"
ALERT_EMAIL="[email protected]"
THRESHOLD=10
CHECK_INTERVAL=300 # 5 dakika (saniye cinsinden)
STATE_FILE="/tmp/log-monitor-state"
# Son kontrol zamanını al
if [ -f "$STATE_FILE" ]; then
LAST_CHECK=$(cat "$STATE_FILE")
else
LAST_CHECK=$(date -d "5 minutes ago" +%s)
fi
CURRENT_TIME=$(date +%s)
# Son kontrol zamanından bu yana yeni hataları say
ERROR_COUNT=$(awk -v last="$LAST_CHECK" '
{
# Log satırından epoch zamanını parse et (nginx format)
# Nginx format: 2024/01/15 14:23:01 [error] ...
cmd = "date -d ""$1" "$2"" +%s 2>/dev/null"
cmd | getline epoch
close(cmd)
if (epoch > last && /[error]/) count++
}
END { print count+0 }
' "$LOG_FILE")
echo "Tespit edilen hata sayisi: $ERROR_COUNT"
# Eşik kontrolü
if [ "$ERROR_COUNT" -gt "$THRESHOLD" ]; then
HOSTNAME=$(hostname -f)
MESSAGE="UYARI: $HOSTNAME sunucusunda son 5 dakikada $ERROR_COUNT Nginx hatasi tespit edildi!"
# E-posta gönder
echo "$MESSAGE" | mail -s "[ALARM] Nginx Hata Esigi Asildi - $HOSTNAME" "$ALERT_EMAIL"
# Syslog'a yaz
logger -p local0.warning -t log-monitor "$MESSAGE"
echo "$(date): Uyari gonderildi - $ERROR_COUNT hata" >> /var/log/log-monitor.log
fi
# Durum dosyasını güncelle
echo "$CURRENT_TIME" > "$STATE_FILE"
Bu betiği cron ile çalıştırıyoruz:
# Crontab'a ekle: crontab -e
*/5 * * * * /usr/local/bin/log-monitor.sh >> /var/log/log-monitor.log 2>&1
Fail2Ban ile Brute-Force Uyarısı
Fail2Ban sadece IP engellemez, aynı zamanda mükemmel bir log tabanlı uyarı aracıdır. Birisi SSH’a brute-force yaparken hem engeller hem sizi haberdar eder.
# Fail2Ban kurulumu
apt install fail2ban -y
# /etc/fail2ban/jail.local dosyası oluştur
# /etc/fail2ban/jail.local
[DEFAULT]
# Genel ayarlar
bantime = 3600
findtime = 600
maxretry = 5
destemail = [email protected]
sendername = Fail2Ban-Alert
mta = sendmail
action = %(action_mwl)s
[sshd]
enabled = true
port = ssh
logpath = %(sshd_log)s
maxretry = 3
bantime = 7200
[nginx-http-auth]
enabled = true
filter = nginx-http-auth
logpath = /var/log/nginx/error.log
maxretry = 6
[nginx-limit-req]
enabled = true
filter = nginx-limit-req
action = iptables-multiport[name=ReqLimit, port="http,https", protocol=tcp]
logpath = /var/log/nginx/error.log
maxretry = 10
findtime = 60
bantime = 7200
Fail2Ban’ın e-posta ile Slack bildirimi göndermesi için özel bir action dosyası oluşturalım:
# /etc/fail2ban/action.d/slack-notify.conf
[Definition]
actionstart =
actionstop =
actioncheck =
actionban = curl -s -X POST
-H 'Content-type: application/json'
--data '{"text":"*[FAIL2BAN UYARI]* `<ip>` adresi `<name>` servisine brute-force nedeniyle engellendi. Sunucu: '"$(hostname)"'"}'
https://hooks.slack.com/services/XXXXX/XXXXX/XXXXX
actionunban = curl -s -X POST
-H 'Content-type: application/json'
--data '{"text":"*[FAIL2BAN]* `<ip>` engeli kaldirildi. Servis: `<name>`"}'
https://hooks.slack.com/services/XXXXX/XXXXX/XXXXX
[Init]
name = default
Grafana Loki ile Gelişmiş Log Uyarısı
Loki, Prometheus’un log dünyasındaki kardeşidir. Logları indekslemek yerine etiketler kullanır, bu da onu son derece hafif ve hızlı yapar. Grafana ile entegre olduğunda görsel bir uyarı sistemi elde edersiniz.
Loki Kurulumu
# Loki binary indir
cd /tmp
wget https://github.com/grafana/loki/releases/download/v2.9.0/loki-linux-amd64.zip
unzip loki-linux-amd64.zip
mv loki-linux-amd64 /usr/local/bin/loki
chmod +x /usr/local/bin/loki
# Promtail (log agent) indir
wget https://github.com/grafana/loki/releases/download/v2.9.0/promtail-linux-amd64.zip
unzip promtail-linux-amd64.zip
mv promtail-linux-amd64 /usr/local/bin/promtail
chmod +x /usr/local/bin/promtail
# Konfigürasyon dizini oluştur
mkdir -p /etc/loki /etc/promtail /var/lib/loki
# /etc/loki/loki-config.yaml
auth_enabled: false
server:
http_listen_port: 3100
grpc_listen_port: 9096
common:
path_prefix: /var/lib/loki
storage:
filesystem:
chunks_directory: /var/lib/loki/chunks
rules_directory: /var/lib/loki/rules
replication_factor: 1
ring:
instance_addr: 127.0.0.1
kvstore:
store: inmemory
schema_config:
configs:
- from: 2020-10-24
store: boltdb-shipper
object_store: filesystem
schema: v11
index:
prefix: index_
period: 24h
ruler:
alertmanager_url: http://localhost:9093
# Alert kuralları dizini
ruler:
storage:
type: local
local:
directory: /var/lib/loki/rules
rule_path: /var/lib/loki/rules-temp
alertmanager_url: http://localhost:9093
ring:
kvstore:
store: inmemory
enable_api: true
Promtail Konfigürasyonu
Promtail, log dosyalarını okuyup Loki’ye gönderen agent’tır:
# /etc/promtail/promtail-config.yaml
server:
http_listen_port: 9080
grpc_listen_port: 0
positions:
filename: /tmp/positions.yaml
clients:
- url: http://localhost:3100/loki/api/v1/push
scrape_configs:
- job_name: system
static_configs:
- targets:
- localhost
labels:
job: varlogs
host: __HOSTNAME__
__path__: /var/log/*log
- job_name: nginx
static_configs:
- targets:
- localhost
labels:
job: nginx
host: __HOSTNAME__
__path__: /var/log/nginx/*.log
pipeline_stages:
- regex:
expression: '(?P<status_code>d{3}) d+'
- labels:
status_code:
- job_name: auth
static_configs:
- targets:
- localhost
labels:
job: auth
host: __HOSTNAME__
__path__: /var/log/auth.log
Loki Uyarı Kuralları
LogQL ile uyarı kuralları tanımlayabiliriz:
# /var/lib/loki/rules/default/rules.yaml
groups:
- name: log-alerts
rules:
# SSH brute force uyarisi
- alert: SSHBruteForce
expr: |
sum(count_over_time({job="auth"} |= "Failed password" [5m])) > 20
for: 1m
labels:
severity: critical
annotations:
summary: "SSH Brute Force Saldirisi"
description: "Son 5 dakikada 20'den fazla basarisiz SSH girisi tespit edildi."
# Nginx 5xx hatalari
- alert: NginxHighErrorRate
expr: |
sum(rate({job="nginx", filename=~".*access.*"} |= "" 5" [5m])) > 10
for: 2m
labels:
severity: warning
annotations:
summary: "Nginx Yuksek 5xx Hata Orani"
description: "Nginx sunucusunda 5xx hata orani dakikada 10'u gecti."
# OOM Killer uyarisi
- alert: OOMKillerTriggered
expr: |
count_over_time({job="varlogs"} |= "Out of memory: Kill process" [10m]) > 0
for: 0m
labels:
severity: critical
annotations:
summary: "OOM Killer Devreye Girdi"
description: "Sunucuda bellek yetersizligi nedeniyle bir proses olduruldu."
Telegram Bot ile Anlık Bildirim
E-posta bildirimlerini kaçırabilirsiniz, ama Telegram bildirimi telefona anında düşer. Üstelik kurmak çok kolay.
Önce @BotFather’dan bir bot oluşturun, token alın ve chat ID’nizi öğrenin. Ardından:
#!/bin/bash
# /usr/local/bin/telegram-alert.sh
# Kullanim: telegram-alert.sh "Mesaj metni" [severity]
BOT_TOKEN="1234567890:ABCDEFGHIJKLMNOP"
CHAT_ID="-100123456789" # Grup ID'si veya bireysel chat ID
SEVERITY=${2:-"INFO"}
HOSTNAME=$(hostname -f)
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
# Severity'e gore emoji belirle
case "$SEVERITY" in
"CRITICAL") EMOJI="🔴" ;;
"WARNING") EMOJI="🟡" ;;
"INFO") EMOJI="🟢" ;;
*) EMOJI="⚪" ;;
esac
MESSAGE="${EMOJI} *[${SEVERITY}]* ${TIMESTAMP}
*Sunucu:* `${HOSTNAME}`
*Mesaj:* ${1}"
curl -s -X POST "https://api.telegram.org/bot${BOT_TOKEN}/sendMessage"
-d chat_id="${CHAT_ID}"
-d parse_mode="Markdown"
-d text="${MESSAGE}"
> /dev/null 2>&1
# Cikis kodu kontrol
if [ $? -eq 0 ]; then
logger -p local0.info -t telegram-alert "Bildirim gonderildi: $1"
else
logger -p local0.error -t telegram-alert "Bildirim gonderilemedi!"
exit 1
fi
Bu betiği log izleme sistemimizle birleştirelim. Kritik bir hata yakalandığında hem e-posta hem Telegram bildirimi gönderelim:
#!/bin/bash
# /usr/local/bin/critical-log-watcher.sh
# Kritik log desenlerini gercek zamanli izle
WATCH_PATTERNS=(
"CRITICAL"
"Out of memory"
"disk quota exceeded"
"Connection refused"
"Segmentation fault"
"kernel: BUG"
"Authentication failure"
)
LOG_DIR="/var/log/remote"
ALERT_COOLDOWN=300 # Ayni uyari icin 5 dakika bekleme suresi
COOLDOWN_DIR="/tmp/alert-cooldown"
mkdir -p "$COOLDOWN_DIR"
send_alert() {
local pattern="$1"
local log_line="$2"
local log_file="$3"
# Cooldown kontrolu (ayni pattern icin spam onle)
local cooldown_file="$COOLDOWN_DIR/$(echo "$pattern" | md5sum | cut -d' ' -f1)"
if [ -f "$cooldown_file" ]; then
local last_alert=$(cat "$cooldown_file")
local now=$(date +%s)
if [ $((now - last_alert)) -lt $ALERT_COOLDOWN ]; then
return 0 # Cooldown suresi dolmamis, uyari gonderme
fi
fi
# Uyari gonder
local message="Log eslesme: '$pattern'
Dosya: $log_file
Satir: $log_line"
/usr/local/bin/telegram-alert.sh "$message" "CRITICAL"
echo "$log_line" | mail -s "[KRITIK] Log Alarmi: $pattern" [email protected]
# Cooldown zamanini guncelle
date +%s > "$cooldown_file"
logger -p local0.crit -t critical-log-watcher "Alert gonderildi: $pattern"
}
# Tail ile gercek zamanli izleme
tail -F "$LOG_DIR"/*/*.log 2>/dev/null | while read -r line; do
for pattern in "${WATCH_PATTERNS[@]}"; do
if echo "$line" | grep -qi "$pattern"; then
# Hangi dosyadan geldigini bul
send_alert "$pattern" "$line" "stream"
break
fi
done
done
Logwatch ile Günlük Özet Raporları
Anlık uyarıların yanı sıra, her sabah bir özet raporu almak için Logwatch mükemmel bir araçtır:
# Logwatch kurulumu
apt install logwatch -y
# Gunluk rapor icin cron ayari
# /etc/cron.daily/00logwatch dosyasini duzenle
/usr/sbin/logwatch
--output mail
--mailto [email protected]
--detail high
--range yesterday
--service All
--format html
Logwatch’a özel bir servis tanımı ekleyelim. Nginx için özel bir analiz modülü:
# /etc/logwatch/scripts/services/nginx-custom
#!/usr/bin/perl
use strict;
use warnings;
my %status_codes;
my %top_ips;
my $total_requests = 0;
my $error_count = 0;
while (defined(my $line = <STDIN>)) {
chomp $line;
# Access log parse
if ($line =~ /^(S+) .* "(S+) (S+) S+" (d{3})/) {
my ($ip, $method, $uri, $status) = ($1, $2, $3, $4);
$status_codes{$status}++;
$top_ips{$ip}++;
$total_requests++;
$error_count++ if $status >= 500;
}
}
print "Toplam istek: $total_requestsn";
print "5xx hata sayisi: $error_countnn";
print "HTTP Durum Kodlari:n";
foreach my $code (sort keys %status_codes) {
printf " %s: %dn", $code, $status_codes{$code};
}
print "nEn cok istek yapan IP'ler (ilk 10):n";
my $count = 0;
foreach my $ip (sort { $top_ips{$b} <=> $top_ips{$a} } keys %top_ips) {
last if $count++ >= 10;
printf " %-20s: %d istekn", $ip, $top_ips{$ip};
}
Gerçek Dünya Senaryosu: E-Ticaret Sunucusunda Kriz Yönetimi
Bir e-ticaret şirketinde çalıştığınızı düşünün. Cuma gecesi yoğun satış sezonu başlamış. Sistemler normal görünüyor ama aşağıdaki log satırları birikmekte:
[error] 1234#1234: *45678 upstream timed out (110: Connection timed out)
php-fpm: ERROR: [pool www] child 12345 exited with code 255
mysql: InnoDB: page_cleaner: 1000ms intended loop took 4521ms
Log tabanlı uyarı sisteminiz çalışıyor olsaydı:
- Nginx upstream timeout sayısı eşiği geçtiğinde (dakikada 5’ten fazla) ilk uyarı gelir
- PHP-FPM child çöküşleri tespit edilir, ikinci uyarı tetiklenir
- MySQL InnoDB yavaşlama uyarısı üçüncü sinyali verir
Bunların hepsi aynı anda geldiğinde “veritabanı darboğazı var, PHP-FPM havuzu bitmiş” tanısını 2 dakikada koyarsınız. Sistemsiz bir ortamda bunu anlamak 30-45 dakika sürebilir.
Bu senaryoya özel bir uyarı kuralı:
# /etc/loki/rules/ecommerce-alerts.yaml
groups:
- name: ecommerce
rules:
- alert: UpstreamTimeout
expr: |
sum(count_over_time({job="nginx"} |= "upstream timed out" [3m])) > 15
for: 1m
labels:
severity: critical
team: backend
annotations:
summary: "Backend upstream timeout artisi"
description: "Son 3 dakikada 15'ten fazla upstream timeout. Backend saglik kontrolu yapilmali."
runbook: "https://wiki.sirketim.com/runbooks/upstream-timeout"
Sonuç
Log tabanlı uyarı sistemi kurmak tek seferlik bir iş değil, sürekli iyileştirilen bir yapıdır. Başlangıçta çok fazla uyarı alabilirsiniz; bu normaldir. Eşik değerlerini sisteminizin normal davranışına göre kalibre edin.
Önemli birkaç nokta:
- Alarm yorgunluğuna dikkat edin. Çok fazla uyarı, önemli olanların göz ardı edilmesine neden olur. Her uyarı aksiyona dönüşmeli.
- Cooldown mekanizması şart. Aynı sorun için dakikada 50 bildirim almak yerine 5 dakikada bir özet alın.
- Runbook bağlantısı ekleyin. Her uyarıya “bu durumda ne yapılır” sorusunun cevabını veren bir wiki linki ekleyin.
- Log rotasyonunu ihmal etmeyin. İzlediğiniz log dosyaları rotate olduğunda
tail -Fve Promtail bunu otomatik yakalar, ama bazı araçlar dosya tanımlayıcısını kaybeder. Test edin. - Merkezi log sunucusunu da izleyin. Log sunucunuz çökerse hiçbir şeyden haberiniz olmaz. Meta izleme, yani izleme sistemini izlemek, kritik öneme sahiptir.
Kurduğunuz sistem ne kadar karmaşık olursa olsun, amacı basit kalmalı: doğru kişiye, doğru zamanda, aksiyona dönüştürülebilir bilgi ulaştırmak. Bu sağlandığında, gece 3’te patlayan bir kriz yerine sabah kahvenizle birlikte “dün gece şu uyarı geldi, çözüldü” raporunu okursunuz.
