Tüm yazılar
Strateji 6 dk okuma

Otomasyon devir teslim paketi: runbook, sağlık kontrolü ve geri alma planı

Çalışan bir otomasyonu devrederken kod yetmez: runbook, sırların yeri, çıkış kodu veren sağlık kontrolü ve tek komutluk geri alma betiğiyle devir paketi.

#process #documentation #automation

Bir otomasyonu teslim ederken en sık duyduğum cümle "kod repoda, README de var" oluyor. İki ay sonra aynı otomasyon sessizce durduğunda ise kimse ne zaman çalışması gerektiğini, hangi sırrı kullandığını ve son çalışan sürüme nasıl dönüleceğini bilmiyor. Bu yüzden ben her devirde kodun yanına bir otomasyon devir teslim paketi koyuyorum: kısa bir runbook, tek komutla çalışan bir sağlık kontrolü ve yine tek komutla çalışan bir geri alma yolu. Kod bu paketin yalnızca bir parçası.

Paketin amacı, otomasyonu yazan kişi ortada yokken bile üç sorunun cevaplanabilmesi: şu an sağlıklı mı, bozulduysa ilk nereye bakılır, düzelmiyorsa nasıl geri alınır. Bu üç soruya cevap veremeyen bir teslim aslında tamamlanmamış bir teslimdir; sorun yalnızca bir sonraki arızaya kadar ertelenmiştir.

Bu yazıda paketin bileşenlerini tek tek tanımlıyor, ardından kendi projelerimde kullandığım sağlık kontrolü ve sürüm geri alma betiklerini paylaşıyorum. İkisi de küçük ve bir Linux sunucusunda olduğu gibi çalışır.

Devir paketinde neler olmalı

Paketi beş başlık altında topluyorum. Her biri bir sayfayı geçmez; geçiyorsa otomasyonun kendisi fazla karmaşıktır ve önce o sadeleştirilmelidir.

  • Zamanlama: İş ne zaman, hangi tetikleyiciyle (cron, zamanlanmış görev, webhook) ve hangi kullanıcı hesabıyla çalışır. Saat dilimi açıkça yazılır; "her sabah" ifadesi yeterli değildir.
  • Haberdar olanlar: Başarılı koşumda kim bilgilendirilir, arızada kim aranır, hafta sonu ve tatilde yedek kimdir. İsim değil rol yazılır; kişiler değişir.
  • Üç tipik arıza: Geliştirme sırasında en çok karşılaşılan üç hata, belirtileri ve çözümleri. Üçten fazlası okunmaz, üçten azı eksiktir.
  • Sırlar: Hangi parola, token ve anahtarın kullanıldığı, nerede saklandığı ve nasıl döndürüleceği. Değerler burada yazmaz; yalnızca adları ve konumu.
  • Sağlık kontrolü ve geri alma: İkisi de tek komuttur ve ikisi de devir görüşmesinde alıcının önünde çalıştırılır.

Bu beş başlığın hiçbiri yeni değil; fark, devir sırasında hepsinin birlikte ve çalışır hâlde teslim edilmesinde. İzleme tarafını daha önce PowerShell ile sistem izleme betiği yazısında ele almıştım; buradaki sağlık kontrolü onun otomasyona odaklanmış, daha dar kapsamlı hâli.

Runbook: bir sayfa, üç arıza

Runbook'u bir arıza gecesinde, otomasyonu hiç görmemiş birinin okuyacağını varsayarak yazıyorum. Bu yüzden düzyazı değil, komut ve beklenen çıktı odaklı olmalı. Her arıza maddesi aynı kalıbı izler:

  • Belirti: Kullanıcının ya da izleme aracının gördüğü şey. Örneğin "sabah raporu e-postası gelmedi".
  • İlk komut: Teşhis için çalıştırılacak tek komut ve sağlıklı durumda vermesi gereken çıktı. Çoğu zaman bu, aşağıdaki sağlık kontrolü betiğidir.
  • Olası sebep ve çözüm: Token süresinin dolması, kaynak dosyanın gelmemesi, diskin dolması gibi somut sebepler ve her biri için adım adım çözüm.
  • Vazgeçme noktası: Çözüm kaç dakika içinde işe yaramazsa geri alma yoluna geçilir. Bu süreyi önceden yazmak, gece yarısı "yarım saat daha deneyeyim" cazibesini keser.

Kendi projelerimde bu üç madde neredeyse hep aynı çıkıyor: kimlik bilgisinin süresinin dolması, beklenen girdinin gelmemesi ya da biçim değiştirmesi ve diskin ya da bir servis kotasının dolması. Otomasyon ne yaparsa yapsın bu üçü listeye girmeyi hak ediyor.

Runbook'ta durum cümlesi bulunmamasına dikkat ediyorum. "Token şu an geçerli" gibi bir satır bir gün mutlaka yanlış olur; onun yerine "token'ın geçerliliğini şu komutla kontrol et" yazılır. Belge nasıl yapılacağını anlatır, durumu komut söyler.

Sağlık kontrolü betiği

Sağlık kontrolü üç şeyi denetler: bağımlılıklar yerinde mi, sırlar ortamda mı, son koşum yakın zamanda ve başarıyla bitmiş mi. Çıkış kodu makine tarafından okunabilir olmalı; böylece hem devir görüşmesinde elle, hem de izleme aracı tarafından otomatik çalıştırılabilir.

#!/usr/bin/env python3
"""saglik.py - devir paketinin sağlık kontrolü.
Çıkış kodu: 0 sağlıklı · 1 son koşum sorunlu · 2 bağımlılık/sır eksik"""
import importlib
import os
import shutil
import sys
import time
from pathlib import Path

DURUM_DOSYASI = Path("/opt/rapor-botu/durum/son-kosum.txt")
AZAMI_YAS_SAAT = 26                      # günlük iş: 24 saat + 2 saat pay
GEREKLI_MODULLER = ["requests", "openpyxl"]
GEREKLI_KOMUTLAR = ["git", "sqlite3"]
GEREKLI_ORTAM = ["RAPOR_API_TOKEN", "SMTP_PAROLA"]

eksikler, kosum_sorunlari = [], []

for modul in GEREKLI_MODULLER:
    try:
        importlib.import_module(modul)
    except ImportError:
        eksikler.append(f"Python modülü yok: {modul}")

for komut in GEREKLI_KOMUTLAR:
    if shutil.which(komut) is None:
        eksikler.append(f"Komut PATH'te yok: {komut}")

for anahtar in GEREKLI_ORTAM:
    if not os.environ.get(anahtar):
        eksikler.append(f"Sır ortamda yok: {anahtar}")

if "--sadece-bagimliliklar" not in sys.argv:
    if not DURUM_DOSYASI.exists():
        kosum_sorunlari.append("Son koşum kaydı yok; iş hiç çalışmamış olabilir")
    else:
        yas_saat = (time.time() - DURUM_DOSYASI.stat().st_mtime) / 3600
        if yas_saat > AZAMI_YAS_SAAT:
            kosum_sorunlari.append(f"Son koşum {yas_saat:.1f} saat önce; sınır {AZAMI_YAS_SAAT}")
        if DURUM_DOSYASI.read_text(encoding="utf-8").strip() != "OK":
            kosum_sorunlari.append("Son koşum OK ile bitmemiş; günlüğe bak")

for baslik, liste in (("EKSIK", eksikler), ("KOSUM", kosum_sorunlari)):
    for satir in liste:
        print(f"[{baslik}] {satir}")

if eksikler:
    sys.exit(2)
if kosum_sorunlari:
    sys.exit(1)
print("OK: bağımlılıklar, sırlar ve son koşum yerinde")
sys.exit(0)

Betiğin dürüst olması, otomasyonun her başarılı koşumun sonunda son-kosum.txt dosyasına OK yazmasına bağlı. Bu satır otomasyona eklenmemişse sağlık kontrolü hep "kayıt yok" der; eklenmiş ama hata yolunda da yazılıyorsa hep "OK" der. Bu yüzden devirde betiği ilk kez çalıştırırken kasıtlı olarak bir bağımlılığı kaldırıp arızayı gördükten sonra geri koyuyorum; sıfır bulgu veren bir aracın çalıştığı önce kanıtlanmalı. Koşum geçmişini dosya yerine küçük bir SQLite tablosunda tutuyorsanız aynı mantık son satırın zaman damgasına uygulanır.

Betiği üç yerden çağırıyorum: devir görüşmesinde elle, zamanlayıcıda asıl işten hemen önce ve izleme aracından günde bir kez. Çıkış kodu sıfırdan farklıysa izleme aracı alarm üretir; böylece "iş çalışmadı ama kimse fark etmedi" durumu ortadan kalkar.

Sırlar nerede durur, nasıl döndürülür

Devir paketinde en çok atlanan bölüm bu. Kod GitHub'da, sunucu erişimi verildi, ama API token'ı hâlâ eski geliştiricinin kişisel hesabına bağlı. Bu durumda devir aslında gerçekleşmemiştir.

  • Her sırrın adı, kullanıldığı yer ve kasadaki konumu runbook'ta listelenir; değeri hiçbir belgede yer almaz.
  • Sırlar kişisel hesaplarla değil, otomasyona ait ayrı bir hesap ya da servis kimliğiyle üretilir. Kişi ayrıldığında hesabı kapatılabilmelidir.
  • Rotasyon adımları yazılı ve denenmiştir: yeni değer üretilir, kasaya yazılır, sağlık kontrolü çalıştırılır, eski değer ancak bundan sonra iptal edilir.
  • Devir günü en az bir sır fiilen döndürülür. Bu hem prosedürün işlediğini kanıtlar hem de eski geliştiricinin erişimini fiilen sonlandırır.

Kasa olarak neyi seçtiğiniz ikincil; önemli olan kasanın devirden sonra alıcının kontrolünde olması ve kasayı açan anahtarın da devir paketinde ayrıca ele alınması. Küçük ekiplerde dosya tabanlı şifreli bir kasa yeterli oluyor; büyük ekiplerde merkezi bir sır yöneticisi devreye girer. Her iki durumda da prensip aynı: kod sırrı bilmez, kasa sırrı bilir, betik aradaki köprüdür.

Betiklerde sırlar ortam değişkeninden okunur; dosyaya ya da koda gömülmez. Sağlık kontrolündeki GEREKLI_ORTAM listesi de tam olarak bu yüzden var: bir sır kasadan ortama düşmemişse iş başlamadan önce yakalanır.

Tek komutla geri alma

Geri alma, sürümlerin yan yana durduğu ve canlı olanın bir sembolik bağla gösterildiği basit bir düzene dayanıyor. Zamanlayıcı her zaman current bağını çalıştırır; geri almak, bağı bir önceki klasöre çevirmekten ibarettir. Eski sürüm silinmez, geçiş kaydı düşülür.

#!/usr/bin/env bash
# geri-al.sh - canlı bağı bir önceki sürüme çevirir.
# Kullanım: ./geri-al.sh [sürüm-adı]   (boşsa bir önceki sürüme döner)
set -euo pipefail

UYGULAMA=/opt/rapor-botu
SURUMLER="$UYGULAMA/surumler"     # her sürüm ayrı klasör: 2026-09-01_a1b2c3d
CANLI="$UYGULAMA/current"         # zamanlayıcı hep bu bağı çalıştırır

simdiki=$(basename "$(readlink -f "$CANLI")")
if [[ $# -ge 1 ]]; then
    hedef_ad="$1"
else
    # sıralı listede canlıdan bir önceki klasör
    hedef_ad=$(ls -1 "$SURUMLER" | grep -Fx -B1 "$simdiki" | head -n1 || true)
fi

if [[ -z "$hedef_ad" || "$hedef_ad" == "$simdiki" || ! -d "$SURUMLER/$hedef_ad" ]]; then
    echo "Geri dönülecek sürüm yok (şimdiki: $simdiki)"; exit 2
fi
hedef="$SURUMLER/$hedef_ad"

echo "Şimdiki : $simdiki"
echo "Hedef   : $hedef_ad"

# 1) hedef sürüm kendi bağımlılıklarıyla ayakta mı?
(cd "$hedef" && python3 saglik.py --sadece-bagimliliklar) || {
    echo "Hedef sürüm sağlık kontrolünü geçemedi"; exit 3; }

# 2) atomik geçiş: yeni bağı hazırla, tek hamlede yerine koy
ln -sfn "$hedef" "$CANLI.yeni"
mv -T "$CANLI.yeni" "$CANLI"

# 3) kayıt: kim, ne zaman, nereden nereye
echo "$(date -Is) geri-alma $simdiki -> $hedef_ad ($USER)" >> "$UYGULAMA/durum/gecis.log"
echo "Tamam. Eski sürüm silinmedi; sürüm adı vererek ileri de alabilirsiniz."

Betik, hedef sürümü canlıya almadan önce o sürümün kendi sağlık kontrolünü bağımlılık modunda çalıştırıyor. Geri döndüğünüz sürümün de kırık olması nadir değildir; özellikle arada bir sistem paketi güncellenmişse. Bağın mv -T ile tek hamlede değiştirilmesi, geçiş anında zamanlayıcı tetiklenirse yarım bir durum görülmesini engeller.

Geri alma betiğinin devir görüşmesinde fiilen çalıştırılmasını şart koşuyorum: bir sürüm geri, sağlık kontrolü, bir sürüm ileri, tekrar sağlık kontrolü. Hiç denenmemiş bir geri alma yolu, geri alma yolu değil bir umuttur.

Devir günü şu sırayı izliyorum: runbook alıcıyla birlikte okunur, sağlık kontrolü alıcının terminalinde çalıştırılır, bir sır döndürülür, geri alma bir kez geri bir kez ileri denenir. Dört adım da geçtiyse teslim tamamlanmıştır; biri geçmediyse tarih ertelenir. Kodun temiz yazılmış olması bu dört adımın yerine geçmez.

Elinizde çalışan ama devri yapılmamış bir otomasyon varsa, ya da teslim aldığınız bir işin runbook'u yoksa, ana sayfadan çalışmalarıma göz atabilir ve paketi birlikte kurmak için iletişim bölümünden yazabilirsiniz.