Onlarca sunucuya tek tek bağlanıp aynı komutu çalıştırmaktan bıktıysanız, gecenin üçünde deployment sırasında SSH oturumu açık tutmaya çalışıyorsanız ya da “acaba bu komutu hangi sunucuda çalıştırdım?” diye kendinize soruyorsanız, Fabric tam da sizin için yapılmış bir araç. Python ekosisteminin en değerli sysadmin araçlarından biri olan Fabric, SSH üzerinden uzak sunucu yönetimini ve otomasyon görevlerini inanılmaz derecede kolaylaştırıyor.
Fabric Nedir ve Neden Kullanmalısınız?
Fabric, Python ile yazılmış, SSH protokolü üzerinden uzak sunucularda komut çalıştırmanıza, dosya transferi yapmanıza ve karmaşık deployment süreçlerini otomatikleştirmenize olanak tanıyan bir kütüphanedir. Arka planda Paramiko SSH kütüphanesini kullanır ve bunu sysadmin dostu bir API ile sarar.
Eski Fabric 1.x sürümünden bahsedildiğinde çoğu kişinin aklına fabfile.py ve fab komutu gelir. Fabric 2.x ise tamamen yeniden yazılmış, daha temiz bir API sunuyor ve Python 3 ile tam uyumlu çalışıyor. Bu yazıda Fabric 2.x kullanacağız.
Şimdi neden Fabric sorusuna dönelim. Alternatifler olan Ansible veya SaltStack gibi araçlar harika, ancak küçük ve orta ölçekli operasyonlar için bazen fazla karmaşık gelebiliyor. Fabric’in güzelliği şu: Saf Python yazıyorsunuz. Özel bir DSL öğrenmenize gerek yok. Python biliyorsanız, Fabric’i beş dakikada öğrenebilirsiniz.
Kurulum ve İlk Adımlar
Fabric kurulumu son derece basit:
pip install fabric
# Sanal ortam kullanıyorsanız (önerilir)
python3 -m venv fabric-env
source fabric-env/bin/activate
pip install fabric
# Kurulumu doğrulayın
fab --version
Kurulumun ardından basit bir bağlantı testi yapalım. Aşağıdaki örnek, uzak bir sunucuya bağlanıp uname -a komutunu çalıştırır:
python3 -c "
from fabric import Connection
c = Connection('192.168.1.10', user='admin', connect_kwargs={'password': 'sifreniz'})
result = c.run('uname -a')
print(result.stdout.strip())
"
Ancak production ortamında şifre yerine SSH anahtarı kullanmak çok daha güvenli ve pratik:
python3 -c "
from fabric import Connection
c = Connection(
host='192.168.1.10',
user='admin',
connect_kwargs={
'key_filename': '/home/user/.ssh/id_rsa'
}
)
result = c.run('uname -a', hide=True)
print(f'Kernel: {result.stdout.strip()}')
"
fabfile.py ile Görev Tanımlama
Fabric’in gerçek gücü fabfile.py dosyasında saklı. Bu dosyada görevler (tasks) tanımlarsınız ve fab komutu ile çağırırsınız.
# fabfile.py
from fabric import task, Connection
@task
def sistem_bilgisi(c):
"""Uzak sunucunun temel sistem bilgilerini göster"""
print("=== CPU Bilgisi ===")
c.run('cat /proc/cpuinfo | grep "model name" | head -1')
print("n=== RAM Kullanımı ===")
c.run('free -h')
print("n=== Disk Kullanımı ===")
c.run('df -h')
print("n=== Yüklü Servisler ===")
c.run('systemctl list-units --type=service --state=running')
Bu görevi çalıştırmak için:
# Tek sunucuya bağlan
fab -H [email protected] sistem-bilgisi
# Birden fazla sunucuya bağlan
fab -H [email protected],[email protected] sistem-bilgisi
Gerçek Dünya Senaryosu 1: Web Sunucusu Deployment
Diyelim ki bir Django uygulamanızı birden fazla web sunucusuna deploy etmeniz gerekiyor. Manuel süreç şöyle: Git’ten çek, bağımlılıkları güncelle, migration çalıştır, static dosyaları topla, Nginx’i yeniden başlat. Her seferinde bu adımları tek tek yapmak hem zaman alıyor hem de hata riskini artırıyor.
# fabfile.py
from fabric import task, Connection
from fabric.group import ThreadingGroup
import datetime
APP_DIR = '/var/www/myapp'
VENV_DIR = '/var/www/myapp/venv'
REPO_URL = '[email protected]:sirket/myapp.git'
@task
def deploy(c):
"""Django uygulamasını deploy et"""
timestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
with c.cd(APP_DIR):
print(f"[{c.host}] Git güncellemesi başlıyor...")
c.run('git fetch origin')
c.run('git pull origin main')
print(f"[{c.host}] Bağımlılıklar güncelleniyor...")
c.run(f'{VENV_DIR}/bin/pip install -r requirements.txt --quiet')
print(f"[{c.host}] Veritabanı migration çalıştırılıyor...")
c.run(f'{VENV_DIR}/bin/python manage.py migrate --noinput')
print(f"[{c.host}] Static dosyalar derleniyor...")
c.run(f'{VENV_DIR}/bin/python manage.py collectstatic --noinput')
print(f"[{c.host}] Nginx yeniden başlatılıyor...")
c.sudo('systemctl reload nginx')
print(f"[{c.host}] Gunicorn yeniden başlatılıyor...")
c.sudo('systemctl restart gunicorn')
print(f"[{c.host}] Deployment tamamlandı: {timestamp}")
@task
def rollback(c):
"""Son deployment'ı geri al"""
with c.cd(APP_DIR):
result = c.run('git log --oneline -5', hide=True)
print("Son 5 commit:")
print(result.stdout)
commit_hash = input("Geri dönmek istediğiniz commit hash'ini girin: ")
c.run(f'git checkout {commit_hash}')
c.sudo('systemctl restart gunicorn')
print(f"[{c.host}] {commit_hash} commit'ine geri dönüldü")
Tüm production sunucularına aynı anda deploy etmek için:
fab -H web1.sirket.com,web2.sirket.com,web3.sirket.com deploy
Gerçek Dünya Senaryosu 2: Log Analizi ve Monitoring
Sistem loglarını birden fazla sunucudan toplamak ve analiz etmek sysadmin’lerin sık yaptığı işlerden biri. Fabric ile bu süreç çok kolaylaşıyor:
# fabfile.py - log analiz modülü
from fabric import task
import re
@task
def nginx_hata_analizi(c, son_n_satir=1000):
"""Nginx error log'larını analiz et"""
result = c.run(
f'tail -{son_n_satir} /var/log/nginx/error.log',
hide=True
)
satirlar = result.stdout.strip().split('n')
hata_sayaci = {}
for satir in satirlar:
# 5xx hatalarını yakala
if 'upstream' in satir.lower() or 'connect() failed' in satir.lower():
hata_tipi = satir.split('[error]')[-1].strip()[:50]
hata_sayaci[hata_tipi] = hata_sayaci.get(hata_tipi, 0) + 1
print(f"n[{c.host}] Son {son_n_satir} satırdaki hatalar:")
for hata, sayi in sorted(hata_sayaci.items(), key=lambda x: x[1], reverse=True)[:10]:
print(f" {sayi:4d} kez: {hata}")
@task
def disk_uyarisi(c, esik=85):
"""Disk kullanımı eşiği aşan bölümleri raporla"""
result = c.run("df -h | awk 'NR>1 {print $5, $6}'", hide=True)
uyarilar = []
for satir in result.stdout.strip().split('n'):
parcalar = satir.split()
if len(parcalar) >= 2:
yuzde = int(parcalar[0].replace('%', ''))
mount = parcalar[1]
if yuzde >= int(esik):
uyarilar.append((yuzde, mount))
if uyarilar:
print(f"n[{c.host}] UYARI - Yüksek disk kullanımı:")
for yuzde, mount in uyarilar:
print(f" {mount}: %{yuzde}")
else:
print(f"[{c.host}] Disk kullanımı normal (%{esik} altında)")
@task
def aktif_baglanti_sayisi(c):
"""Aktif network bağlantı sayısını göster"""
result = c.run(
"ss -tn | awk 'NR>1 {print $1}' | sort | uniq -c",
hide=True
)
print(f"n[{c.host}] Bağlantı durumları:")
print(result.stdout)
Dosya Transferi ve Yapılandırma Yönetimi
Fabric, put ve get metodları ile dosya transferini de destekliyor. Bu özelliği yapılandırma dosyalarını dağıtmak için harika kullanabilirsiniz:
# fabfile.py - konfigürasyon yönetimi
from fabric import task
from pathlib import Path
import os
@task
def nginx_config_dagit(c, config_dosyasi='nginx.conf'):
"""Nginx konfigürasyonunu uzak sunucuya aktar ve doğrula"""
# Önce yerel dosyanın var olup olmadığını kontrol et
if not Path(config_dosyasi).exists():
print(f"HATA: {config_dosyasi} bulunamadı!")
return
# Yedek al
timestamp = c.run("date +%Y%m%d_%H%M%S", hide=True).stdout.strip()
c.sudo(f'cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak.{timestamp}')
# Yeni konfigürasyonu geçici konuma yükle
c.put(config_dosyasi, '/tmp/nginx.conf.new')
# Root yetkisiyle taşı
c.sudo('mv /tmp/nginx.conf.new /etc/nginx/nginx.conf')
c.sudo('chown root:root /etc/nginx/nginx.conf')
c.sudo('chmod 644 /etc/nginx/nginx.conf')
# Konfigürasyonu test et
test_result = c.sudo('nginx -t', warn=True)
if test_result.failed:
print(f"[{c.host}] HATA: Nginx konfigürasyon testi başarısız! Yedekten geri yükleniyor...")
c.sudo(f'cp /etc/nginx/nginx.conf.bak.{timestamp} /etc/nginx/nginx.conf')
else:
print(f"[{c.host}] Konfigürasyon başarıyla güncellendi")
c.sudo('systemctl reload nginx')
@task
def log_topla(c, hedef_dizin='./logs'):
"""Uzak sunucudan log dosyalarını topla"""
os.makedirs(hedef_dizin, exist_ok=True)
log_dosyalari = [
'/var/log/nginx/access.log',
'/var/log/nginx/error.log',
'/var/log/syslog'
]
for log_dosyasi in log_dosyalari:
dosya_adi = Path(log_dosyasi).name
hedef = f"{hedef_dizin}/{c.host}_{dosya_adi}"
try:
c.get(log_dosyasi, hedef)
print(f"[{c.host}] {dosya_adi} indirildi -> {hedef}")
except Exception as e:
print(f"[{c.host}] {dosya_adi} indirilemedi: {e}")
Paralel Bağlantı ve ThreadingGroup
Çok sayıda sunucuya aynı anda komut göndermek istiyorsanız ThreadingGroup kullanabilirsiniz. Bu özellik, sunucu sayısı arttığında ciddi zaman kazandırır:
# fabfile.py - paralel işlem
from fabric import task
from fabric.group import ThreadingGroup, SerialGroup
SUNUCULAR = [
'web1.sirket.com',
'web2.sirket.com',
'web3.sirket.com',
'db1.sirket.com'
]
SSH_PARAMS = {
'user': 'deploy',
'connect_kwargs': {'key_filename': '/home/deploy/.ssh/id_rsa'}
}
@task
def tum_sunuculara_patch(c):
"""Tüm sunuculara güvenlik yaması uygula (paralel)"""
grup = ThreadingGroup(*SUNUCULAR, **SSH_PARAMS)
# Önce güncelleme listesini al
print("Paket listeleri güncelleniyor...")
grup.sudo('apt-get update -qq')
# Güvenlik yamalarını uygula
print("Güvenlik yamaları uygulanıyor...")
grup.sudo('apt-get upgrade -y --only-upgrade')
# Reboot gerekip gerekmediğini kontrol et
for baglanti in grup:
result = baglanti.run(
'test -f /var/run/reboot-required && echo "REBOOT_GEREKLI" || echo "TAMAM"',
hide=True
)
durum = result.stdout.strip()
print(f"{baglanti.host}: {durum}")
@task
def servis_durumu_kontrol(c):
"""Kritik servislerin durumunu paralel olarak kontrol et"""
grup = ThreadingGroup(*SUNUCULAR, **SSH_PARAMS)
kritik_servisler = ['nginx', 'postgresql', 'redis-server']
for baglanti in grup:
print(f"n--- {baglanti.host} ---")
for servis in kritik_servisler:
result = baglanti.run(
f'systemctl is-active {servis}',
hide=True,
warn=True
)
durum = result.stdout.strip()
emoji = "OK" if durum == 'active' else "SORUN"
print(f" [{emoji}] {servis}: {durum}")
Hata Yönetimi ve Güvenli Operasyonlar
Production ortamında hata yönetimi kritik. Fabric’in warn parametresi ve exception handling ile sağlam betikler yazabilirsiniz:
# fabfile.py - hata yönetimi
from fabric import task
from invoke.exceptions import UnexpectedExit
import sys
@task
def guvenli_servis_yeniden_baslat(c, servis_adi):
"""Servisi güvenli şekilde yeniden başlat, başarısız olursa raporla"""
# Mevcut durumu kontrol et
durum_result = c.run(
f'systemctl is-active {servis_adi}',
hide=True,
warn=True
)
onceki_durum = durum_result.stdout.strip()
print(f"[{c.host}] {servis_adi} mevcut durum: {onceki_durum}")
# Yeniden başlatmayı dene
try:
c.sudo(f'systemctl restart {servis_adi}')
# Başarıyla başladığını doğrula
import time
time.sleep(2)
yeni_durum_result = c.run(
f'systemctl is-active {servis_adi}',
hide=True,
warn=True
)
yeni_durum = yeni_durum_result.stdout.strip()
if yeni_durum == 'active':
print(f"[{c.host}] {servis_adi} başarıyla yeniden başlatıldı")
else:
print(f"[{c.host}] UYARI: {servis_adi} yeniden başlatılamadı!")
# Journal log'larını al
c.sudo(
f'journalctl -u {servis_adi} --since "5 minutes ago" --no-pager',
warn=True
)
except Exception as e:
print(f"[{c.host}] HATA: {servis_adi} yeniden başlatılırken sorun oluştu: {e}")
sys.exit(1)
@task
def mysql_yedek_al(c, yedek_dizin='/backup/mysql'):
"""MySQL veritabanının yedeğini al"""
from datetime import datetime
tarih = datetime.now().strftime('%Y%m%d_%H%M%S')
yedek_dosyasi = f"{yedek_dizin}/full_backup_{tarih}.sql.gz"
# Yedek dizininin var olduğundan emin ol
c.sudo(f'mkdir -p {yedek_dizin}')
print(f"[{c.host}] MySQL yedeği başlıyor...")
try:
c.sudo(
f'mysqldump --all-databases --single-transaction '
f'| gzip > {yedek_dosyasi}',
warn=False
)
# Yedek boyutunu kontrol et
boyut_result = c.run(f'du -sh {yedek_dosyasi}', hide=True)
boyut = boyut_result.stdout.split()[0]
print(f"[{c.host}] Yedek tamamlandı: {yedek_dosyasi} ({boyut})")
# Eski yedekleri temizle (30 günden eski)
c.sudo(
f'find {yedek_dizin} -name "*.sql.gz" -mtime +30 -delete'
)
print(f"[{c.host}] 30 günden eski yedekler temizlendi")
except UnexpectedExit as e:
print(f"[{c.host}] HATA: Yedekleme başarısız! {e}")
SSH Tunnel ve Port Yönlendirme
Fabric, SSH tunnel oluşturmak için de kullanılabilir. Özellikle güvenlik duvarı arkasındaki veritabanlarına bağlanmak için çok kullanışlı:
# fabfile.py - SSH tünelleme
from fabric import task, Connection
@task
def db_testi_yapildakta(c):
"""Bastion sunucusu üzerinden iç ağdaki DB'ye bağlan"""
# Bastion (atlama sunucusu) üzerinden bağlan
bastion = Connection('bastion.sirket.com', user='ops')
# İç ağdaki DB sunucusuna bastion üzerinden tünel aç
with Connection(
'db-internal.sirket.local',
user='dbadmin',
gateway=bastion
) as db_conn:
result = db_conn.run('psql -U app -c "SELECT version();"', hide=True)
print(f"Veritabanı bağlantısı başarılı:")
print(result.stdout.strip())
Ortam Değişkenleri ve Konfigürasyon Yönetimi
Büyük projelerde sunucu listelerini ve konfigürasyonu ayrı dosyalarda tutmak iyi bir pratik:
# config.py
import os
from fabric import Connection
def sunucu_baglantilarini_olustur(ortam='production'):
"""Ortama göre sunucu bağlantılarını döndür"""
ortam_haritasi = {
'production': {
'web': ['web1.sirket.com', 'web2.sirket.com', 'web3.sirket.com'],
'db': ['db1.sirket.com', 'db2.sirket.com'],
'cache': ['cache1.sirket.com']
},
'staging': {
'web': ['staging-web.sirket.com'],
'db': ['staging-db.sirket.com'],
'cache': ['staging-cache.sirket.com']
}
}
ssh_params = {
'user': os.environ.get('DEPLOY_USER', 'deploy'),
'connect_kwargs': {
'key_filename': os.environ.get(
'DEPLOY_KEY',
'/home/deploy/.ssh/id_rsa'
)
}
}
sunucular = ortam_haritasi.get(ortam, {})
return {
rol: [Connection(host, **ssh_params) for host in hostlar]
for rol, hostlar in sunucular.items()
}
# fabfile.py - konfigürasyonu kullan
from fabric import task
from config import sunucu_baglantilarini_olustur
@task
def production_deploy(c):
"""Production ortamına deploy et"""
sunucular = sunucu_baglantilarini_olustur('production')
# Önce cache sunucularını güncelle
print("Cache sunucuları güncelleniyor...")
for baglanti in sunucular['cache']:
baglanti.sudo('systemctl flush redis')
# Sonra web sunucularını sırayla güncelle (rolling deployment)
print("Web sunucuları sırayla güncelleniyor...")
for i, baglanti in enumerate(sunucular['web']):
print(f"Web sunucu {i+1}/{len(sunucular['web'])}: {baglanti.host}")
# Load balancer'dan çıkar (örnek)
baglanti.sudo('touch /var/www/maintenance.html')
# Deploy et
with baglanti.cd('/var/www/myapp'):
baglanti.run('git pull origin main')
baglanti.run('venv/bin/pip install -r requirements.txt -q')
baglanti.sudo('systemctl restart gunicorn')
# Load balancer'a geri ekle
baglanti.sudo('rm -f /var/www/maintenance.html')
print(f" {baglanti.host} hazır")
Pratik İpuçları ve Yaygın Tuzaklar
Fabric kullanırken karşılaştığım ve sizi yakabilecek birkaç konuya değineyim.
sudo ve parola yönetimi konusunda dikkatli olun. Fabric’te sudo çalıştırırken sudo metodunu kullanın, run('sudo komut') değil. Eğer sunucularınızda NOPASSWD direktifi yoksa, sudo parolasını config ile verebilirsiniz:
from fabric import Config, Connection
config = Config(overrides={'sudo': {'password': 'sudo-sifreniz'}})
c = Connection('sunucu.com', config=config)
c.sudo('apt-get update')
hide parametresini çıktıyı kontrol etmek için kullanın:
- hide=True: Hem stdout hem stderr’i gizler
- hide=’stdout’: Sadece stdout’u gizler
- hide=’stderr’: Sadece stderr’i gizler
warn=True parametresi komut başarısız olsa bile exception fırlatmaz, sadece uyarı verir. Komutun başarılı olup olmadığını result.failed veya result.ok ile kontrol edebilirsiniz.
Bağlantı zaman aşımı için connect_kwargs içinde timeout değeri belirleyin. Yavaş ağlarda veya meşgul sunucularda varsayılan değer yetersiz kalabilir:
c = Connection(
'sunucu.com',
connect_kwargs={
'key_filename': '/path/to/key',
'timeout': 30,
'banner_timeout': 60
}
)
Sonuç
Fabric, günlük sysadmin rutinlerini otomatikleştirmek için mükemmel bir araç. Deployment süreçlerinden log toplayıp analize, güvenlik yaması uygulamaktan toplu servis yönetimine kadar geniş bir yelpazede kullanabilirsiniz.
Ansible ile karşılaştırıldığında Fabric daha az opinionated ve daha esnektir. Python’un tüm gücünü kullanabilirsiniz: koşullar, döngüler, kütüphaneler, veritabanı bağlantıları… Sınır yok. Öte yandan idempotency (aynı işlemi birden fazla çalıştırmanın güvenli olması) konusunda Ansible daha olgun. Büyük ölçekli, karmaşık altyapı yönetimi için Ansible veya Terraform daha uygun olabilir.
Fabric ile başlamak için şu yolu öneririm: Önce en sık tekrar ettiğiniz manuel görevi seçin, bunu fabfile.py içinde bir fonksiyona dönüştürün, test edin. Başarıyla çalıştıkça yeni görevler ekleyin. Zamanla elinizde güçlü bir otomasyon kütüphanesi birikecek.
Son olarak şunu söylemeliyim: Fabric gibi araçlarla production sunucularda çalışmadan önce mutlaka staging ortamında test edin. SSH üzerinden çalışan bir script, yanlış yazıldığında onlarca sunucuda aynı anda hasara yol açabilir. Her zaman önce warn=True ile neyin çalışacağını gözlemleyin, ardından production’a alın.