Toplu Sunucu Yönetimi: SSH ile Uzak Komut Çalıştırma

Onlarca, belki yüzlerce sunucuyu tek tek yönetmek zorunda kalmak, her sysadmin’in en büyük kabuslarından biridir. Bir güvenlik yaması uygulamak için 50 sunucuya sırayla bağlanmak, her birinde aynı komutu çalıştırmak ve sonuçları not defterine yazmak… Bu hem zaman kaybıdır hem de insan hatasına açık bir süreçtir. İşte tam bu noktada SSH ile toplu komut çalıştırma devreye girer ve hayatınızı kurtarır.

Bu yazıda sıfırdan başlayarak, gerçek dünya senaryolarında kullanabileceğiniz toplu SSH yönetim sistemlerini inceleyeceğiz. Basit tek satır komutlardan başlayıp, paralel çalışan, loglama yapan, hata yöneten production-ready scriptlere kadar gideceğiz.

SSH ile Uzak Komut Çalıştırmanın Temelleri

SSH sadece interaktif oturum açmak için değil, doğrudan komut çalıştırmak için de kullanılabilir. Temel kullanım şu şekildedir:

ssh kullanici@sunucu "komut"

Bu kadar basit. Ama bu basitlik üzerine çok güçlü şeyler inşa edebilirsiniz. Önce birkaç önemli SSH seçeneğini anlayalım:

  • -o StrictHostKeyChecking=no: Bilinmeyen host uyarısını atlar (dikkatli kullanın)
  • -o ConnectTimeout=10: Bağlantı zaman aşımı süresi
  • -o BatchMode=yes: Parola sorulmadan bağlanır, SSH key zorunlu olur
  • -i /yol/anahtar: Belirli bir private key kullan
  • -p 2222: Özel port belirt
  • -q: Sessiz mod, gereksiz çıktıları bastırır
  • -t: Pseudo-terminal tahsis eder, sudo gibi komutlar için gerekli

Şimdi asıl konuya geçelim.

SSH Key Tabanlı Kimlik Doğrulama Kurulumu

Toplu yönetimin ön koşulu, parola sormadan bağlanabilmektir. Bunun için SSH key pair oluşturup tüm sunuculara dağıtmanız gerekir.

#!/bin/bash
# ssh_key_dagit.sh - SSH anahtarını sunucu listesine dağıt

SUNUCU_LISTESI="sunucular.txt"
KULLANICI="admin"
SSH_KEY="$HOME/.ssh/id_rsa.pub"

if [ ! -f "$SSH_KEY" ]; then
    echo "SSH key bulunamadi, olusturuluyor..."
    ssh-keygen -t rsa -b 4096 -f "$HOME/.ssh/id_rsa" -N ""
fi

while IFS= read -r sunucu; do
    [[ "$sunucu" =~ ^#.*$ ]] && continue
    [[ -z "$sunucu" ]] && continue
    
    echo "Key kopyalaniyor: $sunucu"
    ssh-copy-id -i "$SSH_KEY" -o StrictHostKeyChecking=no 
        "$KULLANICI@$sunucu" 2>/dev/null
    
    if [ $? -eq 0 ]; then
        echo "  [OK] $sunucu"
    else
        echo "  [HATA] $sunucu - Manuel kontrol gerekli"
    fi
done < "$SUNUCU_LISTESI"

sunucular.txt dosyası şu formatta olmalıdır:

# Web sunuculari
192.168.1.10
192.168.1.11
web-prod-01.sirket.com

# Veritabani sunuculari
db-01.sirket.com
db-02.sirket.com

Basit Döngü ile Toplu Komut Çalıştırma

En temel yaklaşım, bir for döngüsü içinde SSH komutunu çalıştırmaktır. Sıralı çalışır, yavaştır ama anlaşılması kolaydır ve az sayıda sunucu için yeterlidir.

#!/bin/bash
# toplu_komut.sh - Temel toplu komut çalıştırma

SUNUCULAR=("web-01" "web-02" "web-03" "db-01" "db-02")
KULLANICI="admin"
KOMUT="df -h / | tail -1"

echo "=== Disk Kullanimi Raporu ==="
echo "Tarih: $(date)"
echo ""

for sunucu in "${SUNUCULAR[@]}"; do
    echo "--- $sunucu ---"
    ssh -o ConnectTimeout=5 
        -o BatchMode=yes 
        -o StrictHostKeyChecking=no 
        "$KULLANICI@$sunucu" "$KOMUT" 2>/dev/null
    
    if [ $? -ne 0 ]; then
        echo "  UYARI: $sunucu baglanamadi veya komut basarisiz!"
    fi
    echo ""
done

Bu script’i çalıştırdığınızda her sunucunun disk kullanımını sırayla görürsünüz. Güzel ama yavaş. 20 sunucu için bile dakikalar alabilir.

Paralel SSH: İşleri Hızlandırmak

Gerçek dünyada sıralı çalışmak kabul edilemez. Paralel çalışma için birkaç yöntem var.

Background Process ile Paralel Çalışma

#!/bin/bash
# paralel_ssh.sh - Arka planda paralel SSH

SUNUCU_LISTESI="$1"
KOMUT="$2"
KULLANICI="${3:-admin}"
LOG_DIR="/tmp/ssh_sonuclar_$$"
MAKS_PARALEL=20

if [ -z "$SUNUCU_LISTESI" ] || [ -z "$KOMUT" ]; then
    echo "Kullanim: $0 sunucular.txt 'komut' [kullanici]"
    exit 1
fi

mkdir -p "$LOG_DIR"

calistir_ssh() {
    local sunucu="$1"
    local log_dosya="$LOG_DIR/$sunucu.log"
    
    ssh -o ConnectTimeout=10 
        -o BatchMode=yes 
        -o StrictHostKeyChecking=no 
        "$KULLANICI@$sunucu" "$KOMUT" 
        > "$log_dosya" 2>&1
    
    echo $? > "$LOG_DIR/$sunucu.exit"
}

aktif_is=0
pids=()
sunucular=()

while IFS= read -r sunucu; do
    [[ "$sunucu" =~ ^#.*$ ]] && continue
    [[ -z "$sunucu" ]] && continue
    
    calistir_ssh "$sunucu" &
    pids+=($!)
    sunucular+=("$sunucu")
    ((aktif_is++))
    
    # Maksimum paralel sayisina ulastiysa bekle
    if [ "$aktif_is" -ge "$MAKS_PARALEL" ]; then
        wait "${pids[0]}"
        pids=("${pids[@]:1}")
        ((aktif_is--))
    fi
done < "$SUNUCU_LISTESI"

# Kalan tum isler bitmesini bekle
wait

# Sonuclari raporla
echo ""
echo "========================================="
echo "KOMUT: $KOMUT"
echo "TARIH: $(date)"
echo "========================================="

basarili=0
basarisiz=0

for sunucu in "${sunucular[@]}"; do
    exit_kodu=$(cat "$LOG_DIR/$sunucu.exit" 2>/dev/null)
    cikti=$(cat "$LOG_DIR/$sunucu.log" 2>/dev/null)
    
    if [ "$exit_kodu" -eq 0 ]; then
        echo ""
        echo "=== $sunucu [OK] ==="
        echo "$cikti"
        ((basarili++))
    else
        echo ""
        echo "=== $sunucu [HATA - Exit: $exit_kodu] ==="
        echo "$cikti"
        ((basarisiz++))
    fi
done

echo ""
echo "========================================="
echo "OZET: Basarili=$basarili, Basarisiz=$basarisiz"
echo "========================================="

# Gecici dosyalari temizle
rm -rf "$LOG_DIR"

Bu script’i şöyle kullanırsınız:

chmod +x paralel_ssh.sh
./paralel_ssh.sh sunucular.txt "uptime" admin
./paralel_ssh.sh sunucular.txt "free -m | grep Mem" root

Gerçek Dünya Senaryosu: Güvenlik Yaması Uygulama

Diyelim ki kritik bir CVE çıktı ve tüm Ubuntu sunucularınıza patch uygulamanız gerekiyor. Gece 02:00’de tek başınasınız ve 45 sunucunuz var. İşte bu durumda kullanacağınız script:

#!/bin/bash
# guvenlik_yamasi.sh - Toplu güvenlik güncellemesi

SUNUCU_LISTESI="uretim_sunucular.txt"
KULLANICI="deploy"
LOG_DOSYA="/var/log/patch_$(date +%Y%m%d_%H%M%S).log"
HATA_DOSYA="/var/log/patch_hatalar_$(date +%Y%m%d_%H%M%S).log"

log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_DOSYA"
}

hata_log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] HATA: $1" | tee -a "$HATA_DOSYA"
}

yamala() {
    local sunucu="$1"
    
    log "Baslaniyor: $sunucu"
    
    # Once baglanti testi
    if ! ssh -o ConnectTimeout=5 -o BatchMode=yes 
        "$KULLANICI@$sunucu" "echo ok" &>/dev/null; then
        hata_log "$sunucu - SSH baglanamadi, atlaniyor"
        return 1
    fi
    
    # Sistemi güncelle
    ssh -o ConnectTimeout=30 -o BatchMode=yes 
        "$KULLANICI@$sunucu" 
        "sudo DEBIAN_FRONTEND=noninteractive apt-get update -qq && 
         sudo DEBIAN_FRONTEND=noninteractive apt-get upgrade -y -qq 
         -o Dpkg::Options::='--force-confdef' 
         -o Dpkg::Options::='--force-confold'" 
        >> "$LOG_DOSYA" 2>&1
    
    local cikis_kodu=$?
    
    if [ $cikis_kodu -eq 0 ]; then
        log "Tamamlandi: $sunucu"
        
        # Yeniden baslatma gerekip gerekmedigini kontrol et
        yeniden_baslatma=$(ssh -o BatchMode=yes "$KULLANICI@$sunucu" 
            "[ -f /var/run/reboot-required ] && echo 'GEREKLI' || echo 'GEREKMIYOR'")
        
        log "$sunucu yeniden baslatma: $yeniden_baslatma"
    else
        hata_log "$sunucu - Guncelleme basarisiz (exit: $cikis_kodu)"
        return 1
    fi
}

# Ana akis
log "=== Guncelleme Basliyor ==="
log "Sunucu listesi: $SUNUCU_LISTESI"

# Once kac sunucu var goster
toplam=$(grep -v "^#" "$SUNUCU_LISTESI" | grep -v "^$" | wc -l)
log "Toplam sunucu: $toplam"

# Kullaniciya onay sor
echo ""
echo "UYARI: $toplam sunucuya guncelleme uygulanacak!"
echo "Log: $LOG_DOSYA"
read -p "Devam etmek istiyor musunuz? (evet/hayir): " onay

if [ "$onay" != "evet" ]; then
    echo "Iptal edildi."
    exit 0
fi

while IFS= read -r sunucu; do
    [[ "$sunucu" =~ ^#.*$ ]] && continue
    [[ -z "$sunucu" ]] && continue
    yamala "$sunucu" &
done < "$SUNUCU_LISTESI"

wait

log "=== Tum Islemler Tamamlandi ==="
log "Hatalar icin: $HATA_DOSYA"

# Ozet
basarili=$(grep -c "Tamamlandi:" "$LOG_DOSYA")
yeniden_baslatma=$(grep -c "GEREKLI" "$LOG_DOSYA")
echo ""
echo "Basarili guncelleme: $basarili"
echo "Yeniden baslatma gerekli: $yeniden_baslatma"

Çok Satırlı ve Karmaşık Komutlar

Bazen tek satır yetmez. Uzak sunucuda çok satırlı bir script çalıştırmanız gerekir. Bunun için heredoc kullanabilirsiniz:

#!/bin/bash
# uzak_sistem_analiz.sh - Detayli sistem analizi

sunucu="$1"
kullanici="${2:-admin}"

if [ -z "$sunucu" ]; then
    echo "Kullanim: $0 sunucu [kullanici]"
    exit 1
fi

ssh -o BatchMode=yes -o ConnectTimeout=10 
    "$kullanici@$sunucu" bash << 'UZAK_SCRIPT'

echo "============================================"
echo "SUNUCU: $(hostname -f)"
echo "TARIH: $(date)"
echo "============================================"

echo ""
echo "--- SISTEM BILGISI ---"
uname -r
cat /etc/os-release | grep PRETTY_NAME

echo ""
echo "--- CPU KULLANIMI ---"
top -bn1 | grep "Cpu(s)" | awk '{print "Kullanim: " $2 + $4 "%"}'

echo ""
echo "--- BELLEK ---"
free -h | awk 'NR==2{printf "Toplam: %s, Kullanilan: %s, Bos: %sn",$2,$3,$4}'

echo ""
echo "--- DISK KULLANIMI ---"
df -h | awk 'NR==1{print} //$/{print}' | column -t

echo ""
echo "--- SON 5 HATA (syslog) ---"
grep -i "error|critical|alert" /var/log/syslog 2>/dev/null | tail -5 || echo "Log okunamadi"

echo ""
echo "--- ZOMBIE PROSESLER ---"
ps aux | awk '{if ($8=="Z") print $0}' | head -10

echo ""
echo "--- DINLEYEN PORTLAR ---"
ss -tlnp | grep LISTEN | head -15

UZAK_SCRIPT

echo "Analiz tamamlandi: $sunucu"

SSH ile Dosya Transfer ve Komut Kombinasyonu

Bazen uzak sunucuya script göndermek ve çalıştırmak daha mantıklıdır:

#!/bin/bash
# script_dag_calistir.sh - Script dagit ve calistir

YEREL_SCRIPT="$1"
SUNUCU_LISTESI="$2"
KULLANICI="${3:-admin}"
UZAK_HEDEF="/tmp/$(basename $YEREL_SCRIPT)_$$"

if [ ! -f "$YEREL_SCRIPT" ]; then
    echo "Script bulunamadi: $YEREL_SCRIPT"
    exit 1
fi

calistir() {
    local sunucu="$1"
    
    # Script'i kopyala
    scp -o BatchMode=yes 
        -o ConnectTimeout=10 
        -o StrictHostKeyChecking=no 
        -q 
        "$YEREL_SCRIPT" "$KULLANICI@$sunucu:$UZAK_HEDEF" 2>/dev/null
    
    if [ $? -ne 0 ]; then
        echo "[HATA] $sunucu: Dosya kopyalanamadi"
        return 1
    fi
    
    # Calistir ve temizle
    sonuc=$(ssh -o BatchMode=yes -o ConnectTimeout=30 
        "$KULLANICI@$sunucu" 
        "chmod +x $UZAK_HEDEF && $UZAK_HEDEF; EXIT=$?; rm -f $UZAK_HEDEF; exit $EXIT" 
        2>&1)
    
    local exit_kodu=$?
    
    if [ $exit_kodu -eq 0 ]; then
        echo "[OK] $sunucu:"
        echo "$sonuc" | sed 's/^/    /'
    else
        echo "[HATA] $sunucu (exit: $exit_kodu):"
        echo "$sonuc" | sed 's/^/    /'
    fi
}

while IFS= read -r sunucu; do
    [[ "$sunucu" =~ ^#.*$ ]] && continue
    [[ -z "$sunucu" ]] && continue
    calistir "$sunucu" &
done < "$SUNUCU_LISTESI"

wait
echo ""
echo "Tum islemler tamamlandi."

Gelişmiş Loglama ve Raporlama

Production ortamında neyin ne zaman yapıldığını kayıt altına almak zorunludur. İşte audit-friendly bir log yapısı:

#!/bin/bash
# audit_ssh.sh - Denetim loglu toplu SSH yonetimi

PROGRAM_ADI="$(basename $0)"
LOG_BASE="/var/log/sysadmin"
SESSION_ID="$(date +%Y%m%d_%H%M%S)_$$"
LOG_DOSYA="$LOG_BASE/session_$SESSION_ID.log"
OZET_DOSYA="$LOG_BASE/ozet_$SESSION_ID.txt"

mkdir -p "$LOG_BASE"

# Loglama fonksiyonu
log() {
    local seviye="$1"
    local mesaj="$2"
    local sunucu="${3:-SISTEM}"
    printf '[%s] [%s] [%s] [%s] %sn' 
        "$(date '+%Y-%m-%d %H:%M:%S')" 
        "$SESSION_ID" 
        "$seviye" 
        "$sunucu" 
        "$mesaj" >> "$LOG_DOSYA"
}

# Sonuc kaydet
sonuc_kaydet() {
    local sunucu="$1"
    local durum="$2"
    local sure="$3"
    local detay="$4"
    
    printf '%-30s %-10s %-8s %sn' 
        "$sunucu" "$durum" "${sure}s" "$detay" >> "$OZET_DOSYA"
}

# SSH komutu calistir ve olc
olcumlu_ssh() {
    local sunucu="$1"
    local komut="$2"
    local baslangic cikis_kodu bitis sure cikti
    
    baslangic=$(date +%s)
    
    log "INFO" "SSH baslaniyor: $komut" "$sunucu"
    
    cikti=$(ssh -o BatchMode=yes 
        -o ConnectTimeout=10 
        -o ServerAliveInterval=30 
        -o ServerAliveCountMax=3 
        "$KULLANICI@$sunucu" "$komut" 2>&1)
    cikis_kodu=$?
    
    bitis=$(date +%s)
    sure=$((bitis - baslangic))
    
    if [ $cikis_kodu -eq 0 ]; then
        log "SUCCESS" "Tamamlandi (${sure}s)" "$sunucu"
        sonuc_kaydet "$sunucu" "BASARILI" "$sure" "OK"
        echo "[OK $sunucu] $cikti"
    elif [ $cikis_kodu -eq 255 ]; then
        log "ERROR" "SSH baglanamadi (${sure}s)" "$sunucu"
        sonuc_kaydet "$sunucu" "SSH_HATA" "$sure" "Baglanamadi"
        echo "[BAGLANAMADI $sunucu]"
    else
        log "ERROR" "Komut basarisiz exit=$cikis_kodu (${sure}s)" "$sunucu"
        sonuc_kaydet "$sunucu" "HATA_$cikis_kodu" "$sure" "$cikti"
        echo "[HATA $sunucu] $cikti"
    fi
    
    return $cikis_kodu
}

# Kullanimi ayarla
KULLANICI="${SSH_KULLANICI:-admin}"
SUNUCU_LISTESI="${1:-sunucular.txt}"
KOMUT="${2:-uptime}"

# Baslik yaz ozet dosyasina
{
    echo "=============================================="
    echo "OTURUM: $SESSION_ID"
    echo "KULLANICI: $(whoami)@$(hostname)"
    echo "KOMUT: $KOMUT"
    echo "TARIH: $(date)"
    echo "=============================================="
    printf '%-30s %-10s %-8s %sn' "SUNUCU" "DURUM" "SURE" "DETAY"
    echo "----------------------------------------------"
} > "$OZET_DOSYA"

log "INFO" "Oturum basladi. Komut: $KOMUT" "SISTEM"

# Paralel calistir
while IFS= read -r sunucu; do
    [[ "$sunucu" =~ ^#.*$ ]] && continue
    [[ -z "$sunucu" ]] && continue
    olcumlu_ssh "$sunucu" "$KOMUT" &
done < "$SUNUCU_LISTESI"

wait

log "INFO" "Oturum tamamlandi" "SISTEM"

echo ""
echo "Log: $LOG_DOSYA"
echo "Ozet: $OZET_DOSYA"
echo ""
cat "$OZET_DOSYA"

pssh: Hazır Araçlarla Hız Kazanmak

Kendi script’inizi yazmak yerine pssh (Parallel SSH) kullanabilirsiniz. Ubuntu/Debian’da sudo apt-get install pssh, RHEL/CentOS’ta sudo yum install pssh ile kurulur.

# Tum sunucularda uptime calistir
pssh -h sunucular.txt -l admin -P "uptime"

# Ciktiyi dosyalara kaydet
pssh -h sunucular.txt -l admin 
    -o /tmp/pssh_cikti 
    -e /tmp/pssh_hatalar 
    "df -h"

# Timeout ayarla, paralel sayisini belirle
pssh -h sunucular.txt -l admin 
    -t 30 
    -p 50 
    "systemctl status nginx | grep Active"

# Sudo gereken komutlar
pssh -h sunucular.txt -l admin 
    -A 
    -i 
    "sudo systemctl restart nginx"

pssh parametreleri kısaca:

  • -h dosya: Host listesi dosyası
  • -l kullanici: SSH kullanicisi
  • -p sayi: Paralel is sayisi
  • -t saniye: Timeout süresi
  • -o dizin: Stdout ciktilari bu dizine kaydet
  • -e dizin: Stderr ciktilari bu dizine kaydet
  • -i: Ciktiyi ekrana yazdir
  • -P: Host prefixi ile yazdir
  • -A: Parola sor (key yoksa)

Güvenlik Notları

Toplu SSH yönetimi yaparken dikkat etmeniz gereken bazı kritik noktalar var:

  • SSH key’leri koruyun: Private key’lerin izinleri mutlaka 600 olmalıdır. chmod 600 ~/.ssh/id_rsa
  • Ayrı bir yönetim kullanıcısı kullanın: Root yerine sudo yetkili ayrı bir kullanıcı oluşturun
  • Log tutun: Her toplu işlemi kayıt altına alın, kim ne zaman ne yaptı görünür olsun
  • Test önce: Yeni bir script’i önce test sunucusunda deneyin, direkt production’a sürmeyin
  • Jump host kullanın: İnternetten direkt erişim yerine bastion/jump host üzerinden gidin
  • Komutları doğrulayın: Özellikle değişken içeren komutlarda injection açıkları olabilir
# Kötü - güvensiz
ssh admin@sunucu "rm -rf $KULLANICI_GIRDISI"

# İyi - değişkeni kontrol et
KULLANICI_GIRDISI=$(echo "$KULLANICI_GIRDISI" | tr -dc '[:alnum:]_-')
ssh admin@sunucu "rm -rf /tmp/$KULLANICI_GIRDISI"

Sonuç

SSH ile toplu sunucu yönetimi, her sysadmin’in araç çantasında olması gereken kritik bir beceridir. Başlangıç olarak basit döngülerle işe başlayabilirsiniz. Altyapınız büyüdükçe paralel çalışma, loglama ve hata yönetimi ekleyin.

Birkaç pratik öneri ile bitirelim: Scriptlerinizi versiyon kontrolünde tutun (Git). Her toplu işlemi loglamayı alışkanlık haline getirin. İş arkadaşlarınızın da anlayabileceği şekilde yorum satırları ekleyin. Ve en önemlisi, herhangi bir script’i production’da çalıştırmadan önce mutlaka test ortamında deneyin.

Altyapınız 5 sunucu da olsa 500 sunucu da olsa bu yaklaşımlar geçerlidir. Tek fark, 500 sunucuda bu scriptlerin değerinin ne kadar daha fazla olduğudur. Gece 03:00’te 500 sunucuya elle bağlanmak yerine bir script çalıştırıp kahvenizi yudumlamak… İşin güzel tarafı tam da bu.

Yorum yapın