Sunucularınızda bir servis çöktüğünde ve siz bunu saatler sonra fark ettiğinizde yaşanan o “keşke izleme kurmuş olsaydım” hissini hepimiz biliriz. Üretim ortamında nginx down olmuş, PostgreSQL yanıt vermiyor ya da uygulama servisiniz sessiz sedasız ölmüş. Bu tür durumlar için profesyonel monitoring araçları harika olsa da, bazen ihtiyacınız olan şey sadece bir bash scripti ve bir cron job’dır.
Bu yazıda, servislerin sağlık durumunu kontrol eden, gerektiğinde otomatik olarak yeniden başlatan ve tüm bu olayları loglayan kapsamlı bir script sistemi kuracağız. Hem Linux hem de pratik dünyada işe yarayan çözümler üretmeye odaklanacağız.
Neden Script ile Servis Kontrolü?
Nagios, Zabbix, Prometheus gibi araçlar güçlüdür ancak her ortam için uygun değildir. Küçük bir VPS’te, gelişim ortamında ya da sadece birkaç kritik servisi izlemek istediğinizde bu araçların kurulum ve bakım yükü gereksiz gelebilir.
Bash scriptlerinin avantajları şunlardır:
- Sıfır bağımlılık: Sadece bash ve standart Unix araçları yeterli
- Tam kontrol: Neyin ne zaman nasıl kontrol edileceğini siz belirlersiniz
- Kolay özelleştirme: Her ortama göre dakikalar içinde uyarlanabilir
- Hafiflik: Sunucu kaynaklarını neredeyse hiç tüketmez
- Anlaşılırlık: Ekibinizdeki herkes scripti okuyup anlayabilir
Dezavantajlarını da görmezden gelmemek gerekir. Büyük ölçekli ortamlarda onlarca sunucuyu yönetiyorsanız, bu yaklaşım yetersiz kalacaktır. Ama 5-10 sunucu için son derece etkilidir.
Temel Yapı ve Tasarım
İyi bir servis kontrol scripti şu bileşenleri içermelidir:
- Servis durumu kontrolü
- Servisin gerçekten yanıt verip vermediğini doğrulama (sadece process varlığı değil)
- Başarısız servisleri yeniden başlatma
- Detaylı loglama
- Bildirim mekanizması (email, Slack, vb.)
- Kaç kez yeniden başlatıldığını takip etme
Hemen basit bir örnek ile başlayalım, ardından karmaşıklığı artıracağız.
Basit Servis Kontrol Scripti
#!/bin/bash
# basit_servis_kontrol.sh
# Kullanim: ./basit_servis_kontrol.sh nginx
SERVIS=$1
LOG_DOSYASI="/var/log/servis_kontrol.log"
TARIH=$(date '+%Y-%m-%d %H:%M:%S')
if [ -z "$SERVIS" ]; then
echo "Kullanim: $0 <servis_adi>"
exit 1
fi
servis_kontrol() {
if systemctl is-active --quiet "$SERVIS"; then
echo "[$TARIH] OK: $SERVIS servisi calisiyor." >> "$LOG_DOSYASI"
return 0
else
echo "[$TARIH] HATA: $SERVIS servisi calismıyor! Yeniden baslatiliyor..." >> "$LOG_DOSYASI"
systemctl start "$SERVIS"
sleep 3
if systemctl is-active --quiet "$SERVIS"; then
echo "[$TARIH] BASARILI: $SERVIS servisi yeniden baslatildi." >> "$LOG_DOSYASI"
else
echo "[$TARIH] KRITIK: $SERVIS servisi baslatılamadı!" >> "$LOG_DOSYASI"
fi
fi
}
servis_kontrol
Bu script işe yarar ama yetersizdir. Gerçek dünyada ihtiyacımız olan çok daha fazlası var.
Kapsamlı Servis Kontrol Scripti
Şimdi production ortamına uygun, tam özellikli bir script yazalım.
Konfigürasyon Dosyası Yapısı
Önce servisleri ve ayarları bir konfigürasyon dosyasında tanımlayalım:
# /etc/servis_kontrol/config.conf
# Izlenecek servisler (boslukla ayrılmıs)
SERVISLER="nginx postgresql redis-server php8.1-fpm"
# Maksimum yeniden baslatma denemesi
MAX_YENIDEN_BASLATMA=3
# Yeniden baslatmalar arasındaki bekleme suresi (saniye)
BEKLEME_SURESI=10
# Log dosyası yolu
LOG_DOSYASI="/var/log/servis_kontrol/kontrol.log"
# Email bildirimi icin
EMAIL_AKTIF=true
ADMIN_EMAIL="[email protected]"
# Slack webhook (bos birakilabilir)
SLACK_WEBHOOK=""
# Port kontrolleri (servis:port formatinda)
PORT_KONTROLLERI="nginx:80 nginx:443 postgresql:5432"
Ana Kontrol Scripti
#!/bin/bash
# /usr/local/bin/servis_saglik_kontrol.sh
# Servis saglik kontrolu ve otomatik yeniden baslatma scripti
# Versiyon: 2.0
set -euo pipefail
# Konfigürasyon dosyasını yukle
KONFIG_DOSYASI="/etc/servis_kontrol/config.conf"
if [ ! -f "$KONFIG_DOSYASI" ]; then
echo "HATA: Konfigürasyon dosyası bulunamadı: $KONFIG_DOSYASI"
exit 1
fi
source "$KONFIG_DOSYASI"
# Log dizinini olustur
LOG_DIZIN=$(dirname "$LOG_DOSYASI")
mkdir -p "$LOG_DIZIN"
# Yeniden baslatma sayaci dosyası
SAYAC_DIZIN="/var/run/servis_kontrol"
mkdir -p "$SAYAC_DIZIN"
# ==================== YARDIMCI FONKSIYONLAR ====================
log_yaz() {
local seviye=$1
local mesaj=$2
local tarih
tarih=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$tarih] [$seviye] $mesaj" | tee -a "$LOG_DOSYASI"
}
email_gonder() {
local konu=$1
local icerik=$2
if [ "$EMAIL_AKTIF" = true ] && [ -n "$ADMIN_EMAIL" ]; then
echo "$icerik" | mail -s "$konu" "$ADMIN_EMAIL" 2>/dev/null ||
log_yaz "UYARI" "Email gönderilemedi: $ADMIN_EMAIL"
fi
}
slack_bildirim() {
local mesaj=$1
if [ -n "$SLACK_WEBHOOK" ]; then
curl -s -X POST -H 'Content-type: application/json'
--data "{"text":"$mesaj"}"
"$SLACK_WEBHOOK" > /dev/null 2>&1 ||
log_yaz "UYARI" "Slack bildirimi gönderilemedi"
fi
}
sayaci_oku() {
local servis=$1
local sayac_dosyasi="$SAYAC_DIZIN/${servis}.sayac"
if [ -f "$sayac_dosyasi" ]; then
cat "$sayac_dosyasi"
else
echo "0"
fi
}
sayaci_artir() {
local servis=$1
local sayac_dosyasi="$SAYAC_DIZIN/${servis}.sayac"
local mevcut
mevcut=$(sayaci_oku "$servis")
echo $((mevcut + 1)) > "$sayac_dosyasi"
}
sayaci_sifirla() {
local servis=$1
local sayac_dosyasi="$SAYAC_DIZIN/${servis}.sayac"
echo "0" > "$sayac_dosyasi"
}
# ==================== KONTROL FONKSIYONLARI ====================
servis_aktif_mi() {
local servis=$1
systemctl is-active --quiet "$servis"
return $?
}
servis_etkin_mi() {
local servis=$1
systemctl is-enabled --quiet "$servis" 2>/dev/null
return $?
}
port_dinliyor_mu() {
local host=$1
local port=$2
local timeout=5
if command -v nc &> /dev/null; then
nc -z -w "$timeout" "$host" "$port" > /dev/null 2>&1
return $?
elif command -v timeout &> /dev/null; then
timeout "$timeout" bash -c "cat < /dev/null > /dev/tcp/$host/$port" > /dev/null 2>&1
return $?
fi
return 0
}
http_saglik_kontrol() {
local url=$1
local beklenen_kod=${2:-200}
local timeout=10
if command -v curl &> /dev/null; then
local actual_kod
actual_kod=$(curl -s -o /dev/null -w "%{http_code}"
--connect-timeout "$timeout"
--max-time "$timeout"
"$url" 2>/dev/null)
if [ "$actual_kod" = "$beklenen_kod" ]; then
return 0
else
log_yaz "UYARI" "HTTP kontrolü başarısız: $url (Beklenen: $beklenen_kod, Alınan: $actual_kod)"
return 1
fi
fi
return 0
}
Yeniden Başlatma Mantığı
# ==================== YENIDEN BASLATMA ====================
servisi_yeniden_basla() {
local servis=$1
local sayac
sayac=$(sayaci_oku "$servis")
if [ "$sayac" -ge "$MAX_YENIDEN_BASLATMA" ]; then
log_yaz "KRITIK" "$servis: Maksimum yeniden baslatma sayısına ulaşıldı ($MAX_YENIDEN_BASLATMA). Manuel müdahale gerekli!"
email_gonder
"[KRITIK] $servis servisi başlatılamıyor - $(hostname)"
"$(hostname) sunucusunda $servis servisi $MAX_YENIDEN_BASLATMA kez yeniden başlatılmaya çalışıldı ancak başarısız oldu.nnSon log çıktısı:n$(journalctl -u $servis -n 20 --no-pager 2>/dev/null)"
slack_bildirim ":rotating_light: KRITIK: $(hostname) - $servis servisi başlatılamıyor! Manuel müdahale gerekli."
return 1
fi
log_yaz "INFO" "$servis yeniden başlatılıyor... (Deneme: $((sayac + 1))/$MAX_YENIDEN_BASLATMA)"
if systemctl restart "$servis" 2>/dev/null; then
sleep "$BEKLEME_SURESI"
if servis_aktif_mi "$servis"; then
log_yaz "BASARILI" "$servis başarıyla yeniden başlatıldı."
sayaci_sifirla "$servis"
email_gonder
"[BILGI] $servis servisi yeniden başlatıldı - $(hostname)"
"$(hostname) sunucusunda $servis servisi otomatik olarak yeniden başlatıldı."
slack_bildirim ":white_check_mark: $(hostname) - $servis servisi yeniden başlatıldı."
return 0
else
sayaci_artir "$servis"
log_yaz "HATA" "$servis restart sonrası hala çalışmıyor."
return 1
fi
else
sayaci_artir "$servis"
log_yaz "HATA" "$servis restart komutu başarısız oldu."
return 1
fi
}
# ==================== ANA KONTROL DONGUSU ====================
servis_kontrol_et() {
local servis=$1
# Servisin sistemde tanımlı olup olmadığını kontrol et
if ! systemctl list-unit-files --type=service | grep -q "^${servis}.service"; then
log_yaz "UYARI" "$servis: Systemd'de tanımlı değil, atlanıyor."
return 0
fi
# Servis etkin değilse izleme (disabled servisler kasıtlı kapalı olabilir)
if ! servis_etkin_mi "$servis"; then
log_yaz "INFO" "$servis: Disabled durumda, izlenmiyor."
return 0
fi
if servis_aktif_mi "$servis"; then
log_yaz "OK" "$servis: Çalışıyor."
sayaci_sifirla "$servis"
else
log_yaz "HATA" "$servis: ÇALIŞMIYOR!"
servisi_yeniden_basla "$servis"
fi
}
ana_kontrol() {
log_yaz "INFO" "========== Servis sağlık kontrolü başlıyor =========="
log_yaz "INFO" "Sunucu: $(hostname) | Kernel: $(uname -r)"
for servis in $SERVISLER; do
servis_kontrol_et "$servis"
done
# Port kontrolleri
if [ -n "${PORT_KONTROLLERI:-}" ]; then
for kontrol in $PORT_KONTROLLERI; do
local servis_adi
local port
servis_adi=$(echo "$kontrol" | cut -d: -f1)
port=$(echo "$kontrol" | cut -d: -f2)
if ! port_dinliyor_mu "127.0.0.1" "$port"; then
log_yaz "UYARI" "$servis_adi: Port $port dinlenmiyor!"
else
log_yaz "OK" "$servis_adi: Port $port aktif."
fi
done
fi
log_yaz "INFO" "========== Kontrol tamamlandı =========="
}
ana_kontrol
Log Rotasyonu
Logların kontrolsüz büyümemesi için log rotation ayarı yapalım:
# /etc/logrotate.d/servis_kontrol
/var/log/servis_kontrol/*.log {
daily
missingok
rotate 14
compress
delaycompress
notifempty
create 0640 root adm
sharedscripts
postrotate
# Gerekirse buraya log rotation sonrası çalıştırılacak komut eklenebilir
/bin/true
endscript
}
Cron Job Kurulumu
Scripti her 5 dakikada bir çalıştırmak için:
# crontab -e ile ekleyin
*/5 * * * * /usr/local/bin/servis_saglik_kontrol.sh >> /var/log/servis_kontrol/cron.log 2>&1
# Veya systemd timer kullanmak isterseniz
# /etc/systemd/system/servis-kontrol.timer
Systemd timer ile daha sağlam bir çözüm:
# /etc/systemd/system/servis-kontrol.service
[Unit]
Description=Servis Saglik Kontrolu
After=network.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/servis_saglik_kontrol.sh
User=root
StandardOutput=journal
StandardError=journal
# /etc/systemd/system/servis-kontrol.timer
[Unit]
Description=Her 5 dakikada servis saglik kontrolu
Requires=servis-kontrol.service
[Timer]
OnBootSec=2min
OnUnitActiveSec=5min
Unit=servis-kontrol.service
AccuracySec=30s
[Install]
WantedBy=timers.target
Timer’ı aktif etmek için:
systemctl daemon-reload
systemctl enable servis-kontrol.timer
systemctl start servis-kontrol.timer
# Durumu kontrol et
systemctl status servis-kontrol.timer
systemctl list-timers servis-kontrol.timer
Gerçek Dünya Senaryosu: Web Sunucusu Stack İzleme
Tipik bir LEMP stack (Linux, Nginx, MySQL, PHP) için özelleştirilmiş kontrol scripti:
#!/bin/bash
# lemp_stack_kontrol.sh
# LEMP stack icin ozel saglik kontrolu
LOG="/var/log/lemp_kontrol.log"
TARIH=$(date '+%Y-%m-%d %H:%M:%S')
log() { echo "[$TARIH] $1" >> "$LOG"; }
# Nginx kontrolu - sadece process degil, gercekten yanit veriyor mu?
nginx_kontrol() {
if ! systemctl is-active --quiet nginx; then
log "HATA: Nginx calismıyor, yeniden baslatiliyor..."
systemctl restart nginx
return
fi
# HTTP yanit kontrolu
local http_kodu
http_kodu=$(curl -s -o /dev/null -w "%{http_code}"
--connect-timeout 5 http://localhost/ 2>/dev/null)
if [ "$http_kodu" = "000" ]; then
log "HATA: Nginx process var ama HTTP yaniti yok! Reload deneniyor..."
systemctl reload nginx || systemctl restart nginx
else
log "OK: Nginx calisiyor (HTTP $http_kodu)"
fi
}
# MySQL/MariaDB kontrolu
mysql_kontrol() {
if ! systemctl is-active --quiet mysql; then
log "HATA: MySQL calismıyor!"
systemctl restart mysql
sleep 5
if ! systemctl is-active --quiet mysql; then
log "KRITIK: MySQL yeniden baslatılamadı!"
# InnoDB recovery modunu kontrol et
grep -i "innodb" /var/log/mysql/error.log | tail -5 >> "$LOG"
fi
return
fi
# Gercek baglanti testi
if ! mysqladmin ping --silent 2>/dev/null; then
log "UYARI: MySQL process var ama ping yaniti yok."
else
local sorgu_sayisi
sorgu_sayisi=$(mysqladmin status 2>/dev/null | grep -o 'Queries per second avg: [0-9.]*' | awk '{print $NF}')
log "OK: MySQL calisiyor (Sorgu/sn ort: ${sorgu_sayisi:-bilinmiyor})"
fi
}
# PHP-FPM kontrolu
phpfpm_kontrol() {
local phpfpm_servis="php8.2-fpm"
if ! systemctl is-active --quiet "$phpfpm_servis"; then
log "HATA: PHP-FPM calismıyor, yeniden baslatiliyor..."
systemctl restart "$phpfpm_servis"
return
fi
# PHP-FPM socket kontrolu
local socket="/run/php/php8.2-fpm.sock"
if [ ! -S "$socket" ]; then
log "UYARI: PHP-FPM socket bulunamadı: $socket"
systemctl restart "$phpfpm_servis"
else
log "OK: PHP-FPM calisiyor (socket mevcut)"
fi
}
# Redis kontrolu (cache icin)
redis_kontrol() {
if ! systemctl is-active --quiet redis-server; then
log "UYARI: Redis calismıyor, yeniden baslatiliyor..."
systemctl restart redis-server
return
fi
# Redis PING testi
if redis-cli ping 2>/dev/null | grep -q "PONG"; then
local bellek_kullanim
bellek_kullanim=$(redis-cli info memory 2>/dev/null | grep "used_memory_human" | cut -d: -f2 | tr -d '[:space:]')
log "OK: Redis calisiyor (Bellek: ${bellek_kullanim:-?})"
else
log "UYARI: Redis process var ama PING yaniti yok."
fi
}
log "--- LEMP Stack Kontrol Basliyor ---"
nginx_kontrol
mysql_kontrol
phpfpm_kontrol
redis_kontrol
log "--- Kontrol Tamamlandi ---"
Disk Alanı ve Sistem Kaynaklarını da İzleyelim
Servis çökmelerinin önemli bir nedeni de disk dolması ve memory tükenmesidir. Script’e bu kontrolleri de ekleyelim:
#!/bin/bash
# sistem_kaynak_kontrol.sh
# Disk, bellek ve CPU kontrolu
LOG="/var/log/servis_kontrol/sistem_kaynak.log"
TARIH=$(date '+%Y-%m-%d %H:%M:%S')
ADMIN_EMAIL="[email protected]"
log() { echo "[$TARIH] $1" | tee -a "$LOG"; }
uyari_gonder() {
local konu=$1
local mesaj=$2
log "UYARI GONDERILIYOR: $konu"
echo "$mesaj" | mail -s "[UYARI] $konu - $(hostname)" "$ADMIN_EMAIL" 2>/dev/null
}
# Disk kullanimi kontrolu
disk_kontrol() {
local esik=${1:-85}
while IFS= read -r satir; do
local kullanim
local mount
kullanim=$(echo "$satir" | awk '{print $5}' | tr -d '%')
mount=$(echo "$satir" | awk '{print $6}')
if [ "$kullanim" -gt "$esik" ]; then
log "KRITIK: Disk dolulugu %$kullanim - $mount"
uyari_gonder "Disk dolulugu kritik: $mount (%$kullanim)"
"$(hostname) sunucusunda $mount dizini %$kullanim dolu!nnEn fazla yer kaplayan dizinler:n$(du -sh $mount/* 2>/dev/null | sort -rh | head -10)"
elif [ "$kullanim" -gt $((esik - 10)) ]; then
log "UYARI: Disk dolulugu %$kullanim - $mount"
else
log "OK: Disk dolulugu %$kullanim - $mount"
fi
done < <(df -h | grep '^/dev/' | awk '{print $0}')
}
# Bellek kullanimi kontrolu
bellek_kontrol() {
local esik=${1:-90}
local toplam
local kullanilan
local yuzde
toplam=$(free -m | awk '/^Mem:/{print $2}')
kullanilan=$(free -m | awk '/^Mem:/{print $3}')
yuzde=$((kullanilan * 100 / toplam))
if [ "$yuzde" -gt "$esik" ]; then
log "KRITIK: Bellek kullanimi %$yuzde ($kullanilan MB / $toplam MB)"
# En fazla bellek kullanan processler
local top_process
top_process=$(ps aux --sort=-%mem | head -6 | awk '{printf "%-20s %s%%n", $11, $4}')
uyari_gonder "Bellek kullanimi kritik (%$yuzde)"
"En fazla bellek kullanan processler:n$top_process"
else
log "OK: Bellek kullanimi %$yuzde ($kullanilan MB / $toplam MB)"
fi
}
# CPU yuku kontrolu (1 dakikalik ortalama)
cpu_kontrol() {
local esik=${1:-80}
local cpu_sayisi
local load_avg
local yuzde
cpu_sayisi=$(nproc)
load_avg=$(uptime | awk -F'load average:' '{print $2}' | awk -F',' '{print $1}' | tr -d ' ')
yuzde=$(echo "$load_avg $cpu_sayisi" | awk '{printf "%.0f", ($1/$2)*100}')
if [ "$yuzde" -gt "$esik" ]; then
log "UYARI: CPU yuku yuksek: $load_avg (${yuzde}% - $cpu_sayisi CPU)"
else
log "OK: CPU yuku: $load_avg (${yuzde}% - $cpu_sayisi CPU)"
fi
}
log "=== Sistem Kaynak Kontrolu ==="
disk_kontrol 85
bellek_kontrol 90
cpu_kontrol 80
log "=== Kontrol Tamamlandi ==="
Script’i Production’a Almadan Önce Test Edin
Her scripti canlıya almadan önce test modunda çalıştırmanızı öneririm:
# Test modu eklemek icin scriptin basina ekleyin
TEST_MODU=${TEST_MODU:-false}
servis_kontrol_et() {
local servis=$1
if servis_aktif_mi "$servis"; then
log_yaz "OK" "$servis: Çalışıyor."
else
log_yaz "HATA" "$servis: ÇALIŞMIYOR!"
if [ "$TEST_MODU" = "true" ]; then
log_yaz "TEST" "Test modunda: $servis yeniden başlatılmayacak."
else
servisi_yeniden_basla "$servis"
fi
fi
}
# Test modunda calistirmak icin:
# TEST_MODU=true /usr/local/bin/servis_saglik_kontrol.sh
Scriptin Güvenliği ve Sağlamlaştırılması
Production scriptlerinde dikkat edilmesi gereken güvenlik noktaları:
- set -euo pipefail: Hatalı komutlarda script’in durmasını sağlar
- Dosya izinleri: Script sadece root tarafından okunabilmeli:
chmod 700 /usr/local/bin/servis_saglik_kontrol.sh - Log dosyası izinleri:
chmod 640 /var/log/servis_kontrol/kontrol.log - Hassas bilgiler: Email şifreleri, webhook URL’leri gibi değerleri script içine değil, ayrı bir konfigürasyon dosyasına yazın ve bu dosyayı
chmod 600ile koruyun - Input validation: Dışarıdan gelen tüm değişkenleri validate edin
- Race condition: Aynı anda birden fazla script çalışmasını önlemek için lock dosyası kullanın
# Lock mekanizması
LOCK_DOSYASI="/var/run/servis_kontrol.lock"
if [ -f "$LOCK_DOSYASI" ]; then
LOCK_PID=$(cat "$LOCK_DOSYASI")
if kill -0 "$LOCK_PID" 2>/dev/null; then
echo "Script zaten çalışıyor (PID: $LOCK_PID). Çıkılıyor."
exit 0
fi
fi
echo $$ > "$LOCK_DOSYASI"
trap 'rm -f "$LOCK_DOSYASI"' EXIT
Sonuç
Bash ile servis sağlık kontrolü ve otomatik yeniden başlatma sistemi kurmak; basit bir scriptten başlayıp, katmanlı bir yapıya doğru büyüyebilir. Burada anlattığımız yaklaşım ile birkaç saatlik çalışma sonucunda production-ready bir izleme sisteminiz olabilir.
Önemli noktaları özetlemek gerekirse: Sadece process varlığını değil gerçek servise bağlantıyı kontrol edin. Sonsuz döngüye girmemek için yeniden başlatma sayacı tutun. Her olayı detaylıca loglayın. Kritik durumlarda mutlaka bildirim gönderin. Ve sisteminizi test modunda çalıştırarak canlıya almadan önce davranışını doğrulayın.
Bu scriptleri kendi ortamınıza göre uyarlamak için muhtemelen servis isimlerini ve port numaralarını değiştirmeniz yeterli olacaktır. Nagios veya Prometheus’a geçiş yapana kadar bu yaklaşım binlerce sunucuyu başarıyla yönetmiş ve gecenin ortasında sizi uyanık tutmaktan kurtarmıştır. Benim için kurtardı, umarım sizin için de kurtarır.