Sistem yöneticiliğinde işlerin büyük çoğunluğu tekrar eden görevlerden oluşur: disk kullanımını kontrol et, servisleri yeniden başlat, log dosyalarını temizle, backup al. Bunları her seferinde elle yapmak hem zaman kaybı hem de hata riski demek. Python’un os ve subprocess modülleri, bu tekrar eden işleri otomatize etmek için elimizdeki en güçlü araçlardan ikisi. Bu yazıda her iki modülü de derinlemesine inceleyeceğiz, aralarındaki farkları konuşacağız ve gerçek dünya senaryolarında nasıl kullanacağımızı öğreneceğiz.
os Modülü: İşletim Sistemiyle Temel Etkileşim
os modülü, Python’un standart kütüphanesinin bir parçası ve işletim sistemiyle etkileşime geçmenin en temel yolu. Dosya sistemi işlemleri, çevre değişkenleri, process yönetimi gibi konularda bize platform bağımsız bir arayüz sunuyor. Windows’ta da Linux’ta da aynı Python kodunu çalıştırabiliyorsunuz, ki bu büyük bir avantaj.
Dosya Sistemi İşlemleri
Günlük sysadmin işlerinin büyük kısmı dosya sistemiyle ilgili. Dizin oluşturmak, dosya taşımak, izinleri kontrol etmek bunların hepsi os modülüyle hallediliyor.
import os
# Mevcut dizini öğren
current_dir = os.getcwd()
print(f"Bulunduğun dizin: {current_dir}")
# Dizin oluştur (mkdir ile)
os.mkdir("/tmp/test_dizin")
# İç içe dizin yapısı oluştur (makedirs ile)
os.makedirs("/tmp/proje/logs/2024", exist_ok=True)
# Dizin içeriğini listele
for item in os.listdir("/var/log"):
tam_yol = os.path.join("/var/log", item)
if os.path.isfile(tam_yol):
boyut = os.path.getsize(tam_yol)
print(f"Dosya: {item} - Boyut: {boyut} bytes")
elif os.path.isdir(tam_yol):
print(f"Dizin: {item}/")
os.makedirs() kullanırken exist_ok=True parametresini vermeyi alışkanlık haline getirin. Dizin zaten varsa hata fırlatmak yerine sessizce devam eder, bu da scriptlerinizi daha sağlam hale getirir.
Çevre Değişkenleri ile Çalışmak
Production ortamlarında hassas bilgileri (veritabanı şifreleri, API anahtarları) script içine gömmek büyük bir güvenlik açığı. Bunun yerine environment variable kullanmak standart pratik haline geldi.
import os
# Çevre değişkeni oku
db_host = os.environ.get("DB_HOST", "localhost")
db_port = os.environ.get("DB_PORT", "5432")
db_password = os.environ.get("DB_PASSWORD")
if not db_password:
print("HATA: DB_PASSWORD environment variable tanımlanmamış!")
exit(1)
print(f"Veritabanına bağlanılıyor: {db_host}:{db_port}")
# Tüm environment variable'ları listele (debug amaçlı)
for key, value in os.environ.items():
if "PASSWORD" not in key.upper() and "SECRET" not in key.upper():
print(f"{key}={value}")
# Yeni bir environment variable set et (sadece mevcut process için geçerli)
os.environ["YENI_DEGISKEN"] = "deger"
os.environ.get() kullanmak os.environ[] kullanmaktan çok daha güvenli. İkincisi key bulunamazsa KeyError fırlatır, birincisi ise belirlediğiniz varsayılan değeri döner.
os.walk() ile Dizin Ağacı Gezme
Log temizleme scriptleri yazarken en çok işe yarayan fonksiyonlardan biri os.walk(). Bir dizin altındaki tüm dosya ve alt dizinleri recursive olarak gezer.
import os
import time
def eski_log_dosyalarini_bul(dizin, gun_siniri=30):
"""Belirtilen günden eski log dosyalarını bulur"""
simdi = time.time()
sinir = gun_siniri * 24 * 60 * 60 # Güne çevir
eski_dosyalar = []
for root, dirs, files in os.walk(dizin):
# Gizli dizinleri atla
dirs[:] = [d for d in dirs if not d.startswith('.')]
for dosya in files:
if dosya.endswith('.log') or dosya.endswith('.log.gz'):
tam_yol = os.path.join(root, dosya)
try:
son_degisiklik = os.path.getmtime(tam_yol)
yas = simdi - son_degisiklik
if yas > sinir:
boyut_mb = os.path.getsize(tam_yol) / (1024 * 1024)
eski_dosyalar.append({
'yol': tam_yol,
'gun': int(yas / 86400),
'boyut_mb': round(boyut_mb, 2)
})
except OSError as e:
print(f"Dosya okunamadı: {tam_yol} - {e}")
return eski_dosyalar
# Kullanım
eski = eski_log_dosyalarini_bul("/var/log", gun_siniri=30)
toplam_boyut = sum(f['boyut_mb'] for f in eski)
print(f"Temizlenebilecek {len(eski)} dosya bulundu ({toplam_boyut:.1f} MB)")
for dosya in sorted(eski, key=lambda x: x['boyut_mb'], reverse=True)[:10]:
print(f" {dosya['yol']} - {dosya['gun']} gün - {dosya['boyut_mb']} MB")
subprocess Modülü: Sistem Komutlarını Çalıştırmak
os.system() eski Python kodlarında sıkça görülür ama artık önerilen yaklaşım değil. subprocess modülü, sistem komutlarını çalıştırmak için çok daha güçlü ve güvenli bir arayüz sunuyor. Komutun çıktısını yakalamak, hata kodunu kontrol etmek, stdin/stdout yönetmek gibi konularda subprocess rakipsiz.
subprocess.run(): Modern Yaklaşım
Python 3.5’ten itibaren subprocess.run() tercih edilen yöntem haline geldi. Önceki subprocess.call(), subprocess.check_output() gibi fonksiyonların yerini aldı.
import subprocess
# Basit komut çalıştırma
sonuc = subprocess.run(
["df", "-h"],
capture_output=True,
text=True,
timeout=30
)
if sonuc.returncode == 0:
print("Disk kullanımı:")
print(sonuc.stdout)
else:
print(f"Hata oluştu: {sonuc.stderr}")
# Servisi kontrol et
def servis_durumu(servis_adi):
sonuc = subprocess.run(
["systemctl", "is-active", servis_adi],
capture_output=True,
text=True
)
return sonuc.stdout.strip()
servisler = ["nginx", "postgresql", "redis", "ssh"]
for servis in servisler:
durum = servis_durumu(servis)
emoji = "✓" if durum == "active" else "✗"
print(f"{emoji} {servis}: {durum}")
capture_output=True: stdout ve stderr’i yakala, ekrana yazdırma. text=True: Çıktıyı bytes yerine string olarak al. timeout=30: Komut 30 saniyede tamamlanmazsa TimeoutExpired hatası fırlat.
Güvenli Komut Çalıştırma: Shell Injection’dan Kaçınmak
Sysadmin scriptlerinde en sık yapılan hatalardan biri shell=True kullanırken string birleştirmesiyle komut oluşturmak. Bu, shell injection saldırılarına kapı açar.
import subprocess
# YANLIŞ - Güvensiz kullanım
kullanici_girdisi = "user; rm -rf /" # Kötü niyetli girdi
# subprocess.run(f"id {kullanici_girdisi}", shell=True) # YAPMAYIN!
# DOĞRU - Liste kullanımı ile güvenli kullanım
def kullanici_bilgisi_al(kullanici_adi):
# Basit doğrulama
if not kullanici_adi.replace('-', '').replace('_', '').isalnum():
raise ValueError(f"Geçersiz kullanıcı adı: {kullanici_adi}")
sonuc = subprocess.run(
["id", kullanici_adi], # Shell değil, direkt komut
capture_output=True,
text=True,
timeout=10
)
if sonuc.returncode == 0:
return sonuc.stdout.strip()
else:
return f"Kullanıcı bulunamadı: {kullanici_adi}"
# Shell=True'nun gerekli olduğu durumlar (pipeline gibi)
sonuc = subprocess.run(
"ps aux | grep nginx | grep -v grep",
shell=True,
capture_output=True,
text=True
)
print(sonuc.stdout)
shell=True kullanmak zorunda kaldığınız durumlar var, özellikle pipe (|) veya yönlendirme (>) içeren komutlar için. Bu durumda girdileri asla doğrudan komuta eklemeyin.
subprocess ile Gerçek Dünya: Backup Script
İşte production’da kullanabileceğiniz bir yedekleme scripti. PostgreSQL veritabanını yedekleyip sıkıştırıyor ve eski yedekleri temizliyor.
import subprocess
import os
import datetime
import sys
YEDEK_DIZIN = "/backup/postgresql"
DB_ADI = os.environ.get("PG_DB", "production_db")
PG_KULLANICI = os.environ.get("PG_USER", "postgres")
SAKLAMA_GUNU = 7
def komut_calistir(komut, hata_mesaji, shell=False):
"""Komut çalıştır, hata varsa logla ve çık"""
try:
sonuc = subprocess.run(
komut,
capture_output=True,
text=True,
shell=shell,
timeout=3600 # 1 saat timeout
)
if sonuc.returncode != 0:
print(f"HATA: {hata_mesaji}")
print(f"Stderr: {sonuc.stderr}")
return False, sonuc.stderr
return True, sonuc.stdout
except subprocess.TimeoutExpired:
print(f"ZAMAN AŞIMI: {hata_mesaji}")
return False, "Timeout"
except FileNotFoundError as e:
print(f"KOMUT BULUNAMADI: {e}")
return False, str(e)
def yedek_al():
tarih = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
yedek_dosya = os.path.join(YEDEK_DIZIN, f"{DB_ADI}_{tarih}.sql.gz")
# Dizin yoksa oluştur
os.makedirs(YEDEK_DIZIN, exist_ok=True)
print(f"[{tarih}] Yedekleme başlıyor: {DB_ADI}")
# pg_dump çalıştır ve gzip ile sıkıştır
dump_komutu = f"pg_dump -U {PG_KULLANICI} {DB_ADI} | gzip > {yedek_dosya}"
basarili, cikti = komut_calistir(dump_komutu, "pg_dump başarısız", shell=True)
if not basarili:
sys.exit(1)
# Dosya boyutunu kontrol et
boyut = os.path.getsize(yedek_dosya)
if boyut < 1000: # 1KB'dan küçükse şüphelenelim
print(f"UYARI: Yedek dosyası çok küçük ({boyut} bytes), kontrol edin!")
print(f"Yedek tamamlandı: {yedek_dosya} ({boyut / 1024 / 1024:.1f} MB)")
# Eski yedekleri temizle
temizle_eski_yedekler()
def temizle_eski_yedekler():
print(f"{SAKLAMA_GUNU} günden eski yedekler temizleniyor...")
komut = [
"find", YEDEK_DIZIN,
"-name", "*.sql.gz",
"-mtime", f"+{SAKLAMA_GUNU}",
"-delete"
]
basarili, cikti = komut_calistir(komut, "Eski yedek temizleme başarısız")
if basarili:
print("Temizleme tamamlandı")
if __name__ == "__main__":
yedek_al()
subprocess.Popen ile Gelişmiş Süreç Yönetimi
Uzun süren komutların çıktısını gerçek zamanlı olarak okumak istediğinizde subprocess.Popen devreye giriyor. run() komutu bitene kadar beklerken, Popen size süreç üzerinde daha fazla kontrol veriyor.
import subprocess
import sys
def uzun_komut_izle(komut):
"""Uzun süren komutun çıktısını anlık göster"""
process = subprocess.Popen(
komut,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
bufsize=1 # Satır tamponlaması
)
try:
for satir in iter(process.stdout.readline, ''):
print(satir, end='', flush=True)
sys.stdout.flush()
except KeyboardInterrupt:
print("nKullanıcı tarafından durduruldu")
process.terminate()
finally:
process.stdout.close()
return_code = process.wait()
if return_code != 0:
print(f"Komut hata kodu ile çıktı: {return_code}")
return return_code
# Büyük bir rsync işlemini izle
uzun_komut_izle([
"rsync", "-avh", "--progress",
"/data/uygulama/",
"backup-server:/backup/uygulama/"
])
os ve subprocess’i Birlikte Kullanmak: Pratik Senaryolar
Gerçek scriptlerde bu iki modülü birlikte kullanmak kaçınılmaz. İşte bir sistem sağlık kontrolü scripti:
import os
import subprocess
import datetime
def sistem_saglik_raporu():
"""Sistem sağlığını kontrol edip rapor üretir"""
rapor = []
uyarilar = []
tarih = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
rapor.append(f"=== Sistem Sağlık Raporu - {tarih} ===n")
# 1. CPU yükü kontrolü
try:
with open("/proc/loadavg") as f:
loadavg = f.read().split()
load_1 = float(loadavg[0])
cpu_sayisi = os.cpu_count()
rapor.append(f"CPU Yükü (1dk): {load_1} / {cpu_sayisi} çekirdek")
if load_1 > cpu_sayisi * 0.8:
uyarilar.append(f"YÜKSEK CPU YÜKÜ: {load_1}")
except Exception as e:
rapor.append(f"CPU yükü okunamadı: {e}")
# 2. Disk kullanımı
sonuc = subprocess.run(
["df", "-h", "--output=target,pcent,avail"],
capture_output=True, text=True
)
if sonuc.returncode == 0:
rapor.append("nDisk Kullanımı:")
for satir in sonuc.stdout.strip().split('n')[1:]:
parcalar = satir.split()
if len(parcalar) >= 2:
yuzde = parcalar[1].replace('%', '')
try:
if int(yuzde) > 85:
uyarilar.append(f"DISK DOLU: {parcalar[0]} - %{yuzde}")
rapor.append(f" !! {satir.strip()}")
else:
rapor.append(f" OK {satir.strip()}")
except ValueError:
pass
# 3. Kritik servisleri kontrol et
kritik_servisler = ["sshd", "nginx", "postgresql"]
rapor.append("nServis Durumları:")
for servis in kritik_servisler:
sonuc = subprocess.run(
["systemctl", "is-active", servis],
capture_output=True, text=True
)
durum = sonuc.stdout.strip()
if durum != "active":
uyarilar.append(f"SERVİS ÇALIŞMIYOR: {servis}")
rapor.append(f" !! {servis}: {durum}")
else:
rapor.append(f" OK {servis}: {durum}")
# 4. Bellek kullanımı
sonuc = subprocess.run(
["free", "-h"],
capture_output=True, text=True
)
if sonuc.returncode == 0:
rapor.append(f"nBellek Kullanımı:n{sonuc.stdout}")
# Raporu yaz
rapor_metni = 'n'.join(rapor)
if uyarilar:
rapor_metni += "nn=== UYARILAR ===n"
for uyari in uyarilar:
rapor_metni += f" - {uyari}n"
# Log dosyasına kaydet
log_dizin = "/var/log/saglik_raporlari"
os.makedirs(log_dizin, exist_ok=True)
log_dosya = os.path.join(log_dizin, f"rapor_{datetime.date.today()}.txt")
with open(log_dosya, 'a') as f:
f.write(rapor_metni + "nn")
print(rapor_metni)
return len(uyarilar) == 0
if __name__ == "__main__":
temiz = sistem_saglik_raporu()
exit(0 if temiz else 1)
Hata Yönetimi ve En İyi Pratikler
İyi bir otomasyon scripti, hataları sessizce yutan değil, onları yakalayan ve anlamlı mesajlar üreten scriptlerdir.
import subprocess
import os
import logging
# Logging yapılandırması
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('/var/log/otomasyon.log'),
logging.StreamHandler()
]
)
log = logging.getLogger(__name__)
def guvenli_komut(komut, calisma_dizini=None, env=None):
"""
Güvenli komut çalıştırıcı - hataları yakalar ve loglar
"""
try:
log.info(f"Komut çalıştırılıyor: {' '.join(komut) if isinstance(komut, list) else komut}")
env_tam = os.environ.copy()
if env:
env_tam.update(env)
sonuc = subprocess.run(
komut,
capture_output=True,
text=True,
timeout=300,
cwd=calisma_dizini,
env=env_tam
)
if sonuc.returncode != 0:
log.error(f"Komut başarısız (kod: {sonuc.returncode})")
log.error(f"Stderr: {sonuc.stderr[:500]}") # İlk 500 karakter
return None
log.info("Komut başarıyla tamamlandı")
return sonuc.stdout
except subprocess.TimeoutExpired:
log.error(f"Zaman aşımı: {komut}")
return None
except FileNotFoundError:
log.error(f"Komut bulunamadı: {komut[0] if isinstance(komut, list) else komut}")
return None
except PermissionError:
log.error(f"Yetki hatası: {komut}")
return None
# Kullanım örneği
cikti = guvenli_komut(["nginx", "-t"])
if cikti is not None:
log.info("Nginx konfigürasyonu geçerli")
guvenli_komut(["systemctl", "reload", "nginx"])
else:
log.error("Nginx config hatası var, reload yapılmıyor!")
Dikkat Edilmesi Gereken Noktalar
- shell=True riski: Kullanıcı girdisi içeren komutlarda asla
shell=Truekullanmayın. Mümkün olduğunca liste formatında komut verin. - Timeout zorunluluğu: Production scriptlerinde her zaman
timeoutparametresi kullanın. Askıda kalan bir komut tüm scripti bloke edebilir. - Return code kontrolü: Komutun başarılı olup olmadığını her zaman
returncodeile doğrulayın.0başarı, sıfır dışı değerler hata anlamına gelir. - stderr’i ihmal etmeyin: Hata ayıklamada en değerli bilgi
stderr‘de olur. Her zaman yakalayın ve loglayın. - Büyük çıktılar: Çok büyük çıktı üreten komutlar için
capture_output=Trueyerine dosyaya yönlendirme düşünün, aksi halde bellek sorunları yaşayabilirsiniz.
Sonuç
os ve subprocess modülleri, Python ile sistem otomasyonunun temel taşları. os modülü dosya sistemi, çevre değişkenleri ve process bilgilerine erişim sağlarken, subprocess modülü sistem komutlarını kontrollü ve güvenli bir şekilde çalıştırmanızı sağlıyor.
Bir kaç kilit noktayı kafaya kazımak faydalı olur: Güvenlik açısından shell=True‘yu gereksiz yere kullanmayın ve liste formatında komut vermeyi alışkanlık haline getirin. Sağlamlık açısından her zaman hata kontrolü yapın, timeout değeri belirleyin ve anlamlı log mesajları yazın. Bakım kolaylığı açısından komutları tek bir yerde tanımlayın ve tekrar kullanılabilir fonksiyonlar yazın.
Bu scriptleri cron’a ekleyip otomatikleştirdiğinizde, sabah işe geldiğinizde sistemlerin durumunu gösteren bir e-posta veya Slack mesajı sizi karşılıyor olabilir. Ya da daha güzeli, gece saat 3’te uyandırılmak yerine script sorunu zaten halletmiş oluyor. Sysadmin’in en iyi dostu, onun adına çalışan iyi yazılmış bir script.