Uzak sunucuları yönetmek, onlarca farklı makineye bağlanıp aynı komutları tek tek çalıştırmak… Bunu manuel yapıyorsan zaten bir şeyler yanlış gidiyor demektir. Python’un paramiko kütüphanesi tam da bu noktada devreye giriyor ve SSH üzerinden uzak sunucu yönetimini otomatize etmeni sağlıyor. Hem sysadminler hem de DevOps mühendisleri için vazgeçilmez bir araç haline gelmiş olan paramiko’yu bu yazıda en ince ayrıntısına kadar ele alacağız.
Paramiko Nedir ve Neden Kullanmalısın?
Paramiko, Python için geliştirilmiş bir SSH2 protokol kütüphanesidir. Saf Python ile yazılmıştır ve SSH bağlantısı kurma, dosya transferi (SFTP), tünel oluşturma gibi işlemleri programatik olarak yapmanı sağlar.
Alternatif olarak subprocess ile ssh komutu çağırabilirsin, ama bu yöntem taşınabilir değildir ve Windows’ta iş görmez. fabric kütüphanesi paramiko’nun üzerine kurulmuştur, yani temeli anlamak her iki araçta da sana avantaj sağlar. Paramiko’nun avantajları şöyle sıralanabilir:
- Platform bağımsızlığı: Windows, Linux ve macOS üzerinde aynı şekilde çalışır
- Şifre ve anahtar desteği: Hem password hem de SSH key authentication destekler
- SFTP dahil: Ayrı bir kütüphaneye gerek kalmadan dosya transferi yapabilirsin
- Gelişmiş kontrol: Timeout, banner, host key kontrolü gibi ince ayarlar mümkündür
- Aktif topluluk: Sürekli güncellenen ve yaygın kullanılan bir kütüphanedir
Kurulum ve Ortam Hazırlığı
Başlamadan önce sanal ortam oluşturmak her zaman iyi bir pratiktir:
# Sanal ortam oluştur
python3 -m venv ssh-otomasyon
source ssh-otomasyon/bin/activate # Linux/macOS
# ssh-otomasyonScriptsactivate # Windows
# Paramiko'yu kur
pip install paramiko
# Versiyon kontrolü
python -c "import paramiko; print(paramiko.__version__)"
Bazı sistemlerde cryptography paketi de gerekebilir, paramiko bunu bağımlılık olarak zaten çeker ama sorun yaşarsan:
pip install paramiko cryptography
İlk SSH Bağlantısı: Temel Kullanım
En basit haliyle bir SSH bağlantısı kurup komut çalıştıralım. Bu örnek, bir web sunucusunun uptime bilgisini çekiyor:
# baglanti_temel.py
import paramiko
import sys
def uzak_komut_calistir(host, port, kullanici, sifre, komut):
"""Uzak sunucuda komut çalıştırır ve sonucu döner."""
ssh = paramiko.SSHClient()
# Host key doğrulamasını ayarla
# Üretim ortamında AutoAddPolicy kullanmaktan kaçın!
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
try:
ssh.connect(
hostname=host,
port=port,
username=kullanici,
password=sifre,
timeout=10
)
stdin, stdout, stderr = ssh.exec_command(komut)
cikti = stdout.read().decode('utf-8').strip()
hata = stderr.read().decode('utf-8').strip()
if hata:
print(f"HATA: {hata}", file=sys.stderr)
return cikti
except paramiko.AuthenticationException:
print("Kimlik doğrulama başarısız!")
return None
except paramiko.SSHException as e:
print(f"SSH hatası: {e}")
return None
finally:
ssh.close()
# Kullanım örneği
sonuc = uzak_komut_calistir(
host="192.168.1.50",
port=22,
kullanici="admin",
sifre="gizli_sifre",
komut="uptime && df -h /"
)
if sonuc:
print(sonuc)
SSH Key ile Bağlantı: Güvenli Yöntem
Üretim ortamında şifre yerine SSH key kullanmak hem daha güvenli hem de otomasyon için çok daha pratiktir. Script’lere şifre gömmek ciddi bir güvenlik açığıdır:
# key_baglanti.py
import paramiko
import os
def key_ile_baglan(host, kullanici, key_dosyasi=None, key_sifresi=None):
"""
SSH private key ile bağlantı kurar.
key_dosyasi belirtilmezse ~/.ssh/id_rsa kullanılır.
"""
ssh = paramiko.SSHClient()
# Bilinen host'ları sistem dosyasından yükle
ssh.load_system_host_keys()
# Yeni host'lar için politika belirle
ssh.set_missing_host_key_policy(paramiko.RejectPolicy())
# Güvenli ortamlarda AutoAddPolicy yerine WarningPolicy de kullanılabilir
if key_dosyasi is None:
key_dosyasi = os.path.expanduser("~/.ssh/id_rsa")
try:
# Private key'i yükle
if key_sifresi:
pkey = paramiko.RSAKey.from_private_key_file(
key_dosyasi,
password=key_sifresi
)
else:
pkey = paramiko.RSAKey.from_private_key_file(key_dosyasi)
ssh.connect(
hostname=host,
username=kullanici,
pkey=pkey,
timeout=15
)
print(f"[OK] {host} bağlantısı başarılı")
return ssh
except FileNotFoundError:
print(f"Key dosyası bulunamadı: {key_dosyasi}")
return None
except paramiko.SSHException as e:
print(f"SSH key hatası: {e}")
return None
# Kullanım
ssh_client = key_ile_baglan("web01.sirket.com", "deploy")
if ssh_client:
stdin, stdout, stderr = ssh_client.exec_command("hostname -f")
print(stdout.read().decode())
ssh_client.close()
Gerçek Dünya Senaryosu 1: Çoklu Sunucu Yönetimi
Diyelim ki 20 web sunucusunun disk kullanımını kontrol etmen gerekiyor. Manuel yapmak yerine şu script’i kullan:
# disk_kontrol.py
# Tüm sunucularda disk kullanımını kontrol eder ve uyarı verir
import paramiko
import json
from datetime import datetime
SUNUCULAR = [
{"host": "web01.sirket.com", "port": 22, "kullanici": "admin"},
{"host": "web02.sirket.com", "port": 22, "kullanici": "admin"},
{"host": "db01.sirket.com", "port": 2222, "kullanici": "dbadmin"},
{"host": "cache01.sirket.com", "port": 22, "kullanici": "admin"},
]
ESIK_YUZDE = 80 # Bu değerin üzerindeyse uyarı ver
KEY_DOSYASI = "/home/monitor/.ssh/id_ed25519"
def disk_kullanimi_al(ssh):
"""df çıktısını parse eder, disk kullanım yüzdelerini döner."""
komut = "df -h --output=source,pcent,target | grep -v tmpfs | tail -n+2"
stdin, stdout, stderr = ssh.exec_command(komut)
diskler = []
for satir in stdout.read().decode().strip().split('n'):
parcalar = satir.split()
if len(parcalar) >= 3:
yuzde = int(parcalar[1].replace('%', ''))
diskler.append({
"cihaz": parcalar[0],
"kullanim": yuzde,
"mount": parcalar[2]
})
return diskler
def tum_sunuculari_kontrol_et():
rapor = {
"tarih": datetime.now().isoformat(),
"sunucular": [],
"uyarilar": []
}
for sunucu in SUNUCULAR:
ssh = paramiko.SSHClient()
ssh.load_system_host_keys()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
try:
pkey = paramiko.Ed25519Key.from_private_key_file(KEY_DOSYASI)
ssh.connect(
hostname=sunucu["host"],
port=sunucu["port"],
username=sunucu["kullanici"],
pkey=pkey,
timeout=10
)
diskler = disk_kullanimi_al(ssh)
sunucu_raporu = {
"host": sunucu["host"],
"durum": "OK",
"diskler": diskler
}
for disk in diskler:
if disk["kullanim"] >= ESIK_YUZDE:
uyari = f"UYARI: {sunucu['host']} - {disk['mount']} - %{disk['kullanim']} dolu!"
rapor["uyarilar"].append(uyari)
print(uyari)
sunucu_raporu["durum"] = "UYARI"
rapor["sunucular"].append(sunucu_raporu)
except Exception as e:
hata = {"host": sunucu["host"], "durum": "HATA", "mesaj": str(e)}
rapor["sunucular"].append(hata)
print(f"HATA: {sunucu['host']} - {e}")
finally:
ssh.close()
return rapor
if __name__ == "__main__":
rapor = tum_sunuculari_kontrol_et()
# Raporu JSON olarak kaydet
with open(f"disk_rapor_{datetime.now().strftime('%Y%m%d_%H%M')}.json", "w") as f:
json.dump(rapor, f, indent=2, ensure_ascii=False)
print(f"nToplam {len(rapor['uyarilar'])} uyarı var.")
SFTP ile Dosya Transferi
Paramiko’nun SFTP modülü, uzak sunuculara dosya yükleme ve indirme işlemlerini kolaylaştırır:
# sftp_transfer.py
import paramiko
import os
from pathlib import Path
class SFTPYonetici:
def __init__(self, host, kullanici, key_dosyasi, port=22):
self.host = host
self.kullanici = kullanici
self.key_dosyasi = key_dosyasi
self.port = port
self.ssh = None
self.sftp = None
def baglan(self):
self.ssh = paramiko.SSHClient()
self.ssh.load_system_host_keys()
self.ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
pkey = paramiko.RSAKey.from_private_key_file(self.key_dosyasi)
self.ssh.connect(
hostname=self.host,
port=self.port,
username=self.kullanici,
pkey=pkey
)
self.sftp = self.ssh.open_sftp()
return self
def yukle(self, yerel_dosya, uzak_yol):
"""Yerel dosyayı uzak sunucuya yükler."""
dosya_adi = Path(yerel_dosya).name
uzak_tam_yol = f"{uzak_yol}/{dosya_adi}"
print(f"Yükleniyor: {yerel_dosya} -> {self.host}:{uzak_tam_yol}")
self.sftp.put(yerel_dosya, uzak_tam_yol)
print(f"[OK] Yükleme tamamlandı")
return uzak_tam_yol
def indir(self, uzak_dosya, yerel_yol):
"""Uzak sunucudan dosya indirir."""
dosya_adi = os.path.basename(uzak_dosya)
yerel_tam_yol = os.path.join(yerel_yol, dosya_adi)
print(f"İndiriliyor: {self.host}:{uzak_dosya} -> {yerel_tam_yol}")
self.sftp.get(uzak_dosya, yerel_tam_yol)
print(f"[OK] İndirme tamamlandı")
return yerel_tam_yol
def dizin_listele(self, uzak_yol):
"""Uzak dizindeki dosyaları listeler."""
return self.sftp.listdir_attr(uzak_yol)
def kapat(self):
if self.sftp:
self.sftp.close()
if self.ssh:
self.ssh.close()
def __enter__(self):
return self.baglan()
def __exit__(self, exc_type, exc_val, exc_tb):
self.kapat()
# Context manager ile kullanım
with SFTPYonetici("web01.sirket.com", "deploy", "~/.ssh/id_rsa") as sftp:
# Config dosyası yükle
sftp.yukle("/etc/nginx/nginx.conf", "/tmp/yedek")
# Log dosyası indir
sftp.indir("/var/log/nginx/error.log", "/tmp/loglar")
# Dizin listele
dosyalar = sftp.dizin_listele("/var/www/html")
for dosya in dosyalar:
print(f"{dosya.filename} - {dosya.st_size} bytes")
Gerçek Dünya Senaryosu 2: Otomatik Deployment Script’i
Bir web uygulamasını birden fazla sunucuya deploy eden gerçekçi bir örnek:
# deployment.py
# Basit bir web app deployment otomasyonu
import paramiko
import time
import sys
class Deployer:
def __init__(self, sunucular, kullanici, key_dosyasi):
self.sunucular = sunucular
self.kullanici = kullanici
self.key_dosyasi = key_dosyasi
self.basarili = []
self.basarisiz = []
def _baglan(self, host, port=22):
ssh = paramiko.SSHClient()
ssh.load_system_host_keys()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
pkey = paramiko.RSAKey.from_private_key_file(self.key_dosyasi)
ssh.connect(hostname=host, port=port, username=self.kullanici, pkey=pkey, timeout=15)
return ssh
def _komut_calistir(self, ssh, komut, timeout=60):
"""Komutu çalıştırır, exit code'u kontrol eder."""
stdin, stdout, stderr = ssh.exec_command(komut)
# Exit code için kanalı bekle
exit_status = stdout.channel.recv_exit_status()
cikti = stdout.read().decode('utf-8').strip()
hata = stderr.read().decode('utf-8').strip()
return exit_status, cikti, hata
def sunucu_deploy(self, host, port, uygulama_yolu, git_branch="main"):
print(f"n{'='*50}")
print(f"Sunucu: {host}")
print(f"{'='*50}")
ssh = None
try:
ssh = self._baglan(host, port)
adimlar = [
("Dizine geç", f"cd {uygulama_yolu}"),
("Git pull", f"cd {uygulama_yolu} && git pull origin {git_branch}"),
("Bağımlılıkları yükle", f"cd {uygulama_yolu} && pip install -r requirements.txt -q"),
("Migration çalıştır", f"cd {uygulama_yolu} && python manage.py migrate --no-input"),
("Static dosyaları topla", f"cd {uygulama_yolu} && python manage.py collectstatic --no-input"),
("Servisi yeniden başlat", "sudo systemctl restart gunicorn"),
("Servis durumunu kontrol et", "systemctl is-active gunicorn"),
]
for adim_adi, komut in adimlar:
print(f" --> {adim_adi}...", end=" ", flush=True)
exit_code, cikti, hata = self._komut_calistir(ssh, komut)
if exit_code == 0:
print("OK")
else:
print(f"HATA (exit: {exit_code})")
if hata:
print(f" {hata[:200]}")
raise Exception(f"Adım başarısız: {adim_adi}")
self.basarili.append(host)
print(f"[BASARILI] {host} deploy tamamlandı")
except Exception as e:
self.basarisiz.append({"host": host, "hata": str(e)})
print(f"[BASARISIZ] {host}: {e}")
finally:
if ssh:
ssh.close()
def deploy_baslat(self, uygulama_yolu, git_branch="main"):
print(f"Deployment başlıyor - {len(self.sunucular)} sunucu")
baslangic = time.time()
for sunucu in self.sunucular:
self.sunucu_deploy(
sunucu["host"],
sunucu.get("port", 22),
uygulama_yolu,
git_branch
)
gecen_sure = time.time() - baslangic
print(f"n{'='*50}")
print(f"DEPLOYMENT ÖZETI")
print(f"Süre: {gecen_sure:.1f} saniye")
print(f"Başarılı: {len(self.basarili)} sunucu")
print(f"Başarısız: {len(self.basarisiz)} sunucu")
if self.basarisiz:
print("nHatalı sunucular:")
for item in self.basarisiz:
print(f" - {item['host']}: {item['hata']}")
sys.exit(1)
# Kullanım
sunucular = [
{"host": "app01.sirket.com", "port": 22},
{"host": "app02.sirket.com", "port": 22},
{"host": "app03.sirket.com", "port": 22},
]
deployer = Deployer(
sunucular=sunucular,
kullanici="deploy",
key_dosyasi="/home/ci/.ssh/id_ed25519"
)
deployer.deploy_baslat(
uygulama_yolu="/var/www/myapp",
git_branch="main"
)
İnteraktif Shell ve PTY Kullanımı
Bazı komutlar (özellikle sudo gerektiren veya interaktif çıktı veren) pseudo-terminal (PTY) gerektirir:
# pty_kullanim.py
import paramiko
import time
def sudo_komut_calistir(ssh, komut, sudo_sifre):
"""
Sudo gerektiren komutları çalıştırmak için PTY kullanır.
Mümkünse bunun yerine sudoers'a NOPASSWD eklemek daha temizdir.
"""
# PTY ile kanal aç
kanal = ssh.get_transport().open_session()
kanal.get_pty()
kanal.exec_command(f"sudo -S {komut}")
# Sudo şifre prompt'unu bekle
time.sleep(0.5)
# Şifreyi gönder
kanal.send(f"{sudo_sifre}n")
# Çıktıyı oku
cikti = b""
while True:
if kanal.recv_ready():
cikti += kanal.recv(1024)
if kanal.exit_status_ready():
break
time.sleep(0.1)
exit_code = kanal.recv_exit_status()
kanal.close()
return exit_code, cikti.decode('utf-8')
# Örnek: Servis yeniden başlatma
ssh = paramiko.SSHClient()
ssh.load_system_host_keys()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect("sunucu.com", username="admin", key_filename="~/.ssh/id_rsa")
exit_code, cikti = sudo_komut_calistir(ssh, "systemctl restart nginx", "sudo_sifrem")
print(f"Exit code: {exit_code}")
print(f"Çıktı: {cikti}")
ssh.close()
Önemli Güvenlik Notları
Paramiko kullanırken dikkat etmen gereken güvenlik konuları:
- AutoAddPolicy kullanımı:
AutoAddPolicy()Man-in-the-Middle saldırılarına karşı savunmasızdır. Üretim ortamında known_hosts dosyasını kullan veRejectPolicy()veyaWarningPolicy()tercih et - Şifreleri kaynak koduna gömme: Şifreleri asla direkt koda yazma.
os.environ.get(),python-dotenv, HashiCorp Vault veya benzeri araçlar kullan - SSH key izinleri: Private key dosyaları
chmod 600olmalı, yoksa paramiko bağlantıyı reddedebilir - Timeout değerleri: Her zaman timeout belirt, yoksa script sonsuza kadar asılı kalabilir
- Logging: Tüm bağlantı ve komut loglarını kaydet, hem güvenlik hem de debug için kritik
- Ed25519 key kullanımı: RSA yerine Ed25519 key’leri tercih et, daha modern ve güvenlidir
Bağlantı Havuzu ve Performans
Çok sayıda sunucuya bağlanırken sıralı işlem yerine paralel bağlantı kullanabilirsin:
# paralel_baglanti.py
import paramiko
from concurrent.futures import ThreadPoolExecutor, as_completed
import threading
# Thread-safe print için lock
print_lock = threading.Lock()
def guvenli_print(*args, **kwargs):
with print_lock:
print(*args, **kwargs)
def sunucuda_calistir(sunucu_bilgi):
host = sunucu_bilgi["host"]
komut = sunucu_bilgi["komut"]
key_dosyasi = sunucu_bilgi.get("key", "~/.ssh/id_rsa")
ssh = paramiko.SSHClient()
ssh.load_system_host_keys()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
try:
pkey = paramiko.RSAKey.from_private_key_file(key_dosyasi)
ssh.connect(hostname=host, username="admin", pkey=pkey, timeout=10)
stdin, stdout, stderr = ssh.exec_command(komut)
stdout.channel.recv_exit_status()
cikti = stdout.read().decode().strip()
guvenli_print(f"[{host}] {cikti}")
return {"host": host, "durum": "OK", "cikti": cikti}
except Exception as e:
guvenli_print(f"[{host}] HATA: {e}")
return {"host": host, "durum": "HATA", "mesaj": str(e)}
finally:
ssh.close()
# Paralel çalıştırma
sunucular = [
{"host": f"web{i:02d}.sirket.com", "komut": "uptime", "key": "~/.ssh/id_ed25519"}
for i in range(1, 11)
]
sonuclar = []
# max_workers değerini sunucu sayısına ve ağ kapasitesine göre ayarla
with ThreadPoolExecutor(max_workers=5) as executor:
futures = {executor.submit(sunucuda_calistir, s): s for s in sunucular}
for future in as_completed(futures):
sonuc = future.result()
sonuclar.append(sonuc)
print(f"nToplam: {len(sonuclar)} işlem tamamlandı")
basarili = sum(1 for s in sonuclar if s["durum"] == "OK")
print(f"Başarılı: {basarili}, Başarısız: {len(sonuclar) - basarili}")
Hata Yönetimi ve Yeniden Deneme Mantığı
Ağ ortamında geçici hatalar kaçınılmazdır. Basit bir retry mekanizması eklemek script’lerini çok daha sağlam hale getirir:
paramiko.AuthenticationException: Yanlış şifre veya key sorunuparamiko.NoValidConnectionsError: Sunucuya ulaşılamadıparamiko.SSHException: Genel SSH protokol hatasısocket.timeout: Bağlantı zaman aşımıEOFError: Bağlantı beklenmedik şekilde kapandı
Bu hataları yakalayıp uygun şekilde loglamak, production scriptlerinde gece yarısı sizi uyandırmayacak sağlıklı bir otomasyon altyapısının temelidir.
Sonuç
Paramiko, Python ile SSH otomasyonu için gerçekten güçlü ve esnek bir araç. Temel kullanımdan başlayıp SFTP, paralel bağlantı ve deployment pipeline’larına kadar uzanan geniş bir kullanım alanı var.
Birkaç önemli noktayı tekrar vurgulayalım: Üretim ortamında şifreler yerine SSH key kullan, AutoAddPolicy’yi dikkatli kullan, her bağlantıya timeout ekle ve tüm işlemleri logla. Paralel bağlantı kullanırken sunucu sayısına göre max_workers değerini makul tut, 50 sunucuya aynı anda bağlanmaya çalışmak ağı zorlayabilir.
Paramiko’yu öğrendikten sonra sıradaki adım fabric kütüphanesi olabilir. Fabric, paramiko’nun üzerine daha yüksek seviye bir arayüz sunar ve özellikle deployment senaryoları için işleri daha da kolaylaştırır. Ama temeli anlamak her zaman önce gelir, fabric altında paramiko var ve bir şeyler ters gittiğinde ne aradığını bilmek büyük avantaj sağlar.
Bu script’leri cron job’larla, CI/CD pipeline’larıyla veya monitoring sistemlerinizle entegre ederek tam bir otomasyon altyapısı kurabilirsin. Onlarca sunucuyu tek bir Python script’iyle yönetmek, işin tadını çıkarmanın en güzel yollarından biri.