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
600olmalı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.