Bash ile Dosya Kilitleme ve Eşzamanlı Script Çalıştırmayı Önleme: flock Kullanımı

Cron job’larınız birbirinin üstüne mi biniyor? Backup scriptiniz henüz tamamlanmadan bir sonraki çalışması başlıyor mu? Bu sorunları yaşıyorsanız yalnız değilsiniz. Eşzamanlı script çalıştırma, production ortamlarında ciddi veri tutarsızlıklarına ve kaynak çakışmalarına yol açabilen klasik bir sorun. flock komutu ise bu sorunu Linux üzerinde zarif ve güvenilir bir şekilde çözen, çoğu sysadmin’in hakkını tam vermediği bir araç.

flock Nedir ve Nasıl Çalışır?

flock, Linux çekirdeğinin dosya kilitleme mekanizmasını (fcntl ve flock sistem çağrıları) kullanarak scriptlerinizi koordine etmenizi sağlayan bir kullanıcı alanı aracıdır. util-linux paketinin bir parçası olan bu komut, neredeyse tüm modern Linux dağıtımlarında hazır gelir.

Temel mantık şu: Bir script çalışmaya başladığında belirli bir dosyayı kilitler. İkinci bir script instance’ı aynı dosyayı kilitlemek istediğinde, ya bekler ya da hata vererek çıkar. Bu sayede aynı anda sadece bir instance aktif olur.

Kernel seviyesinde çalıştığı için process beklenmedik şekilde ölse bile kilit otomatik olarak serbest bırakılır. Bu, PID dosyası tabanlı yöntemlerle karşılaştırıldığında büyük bir avantajdır. PID dosyası yönteminde process çöktüğünde dosya ortada kalır ve bir sonraki çalışmayı bloke edebilir.

Temel Kullanım

flock iki farklı modda kullanılabilir: komut satırı modu ve dosya tanımlayıcı modu.

Komut Satırı Modu

En basit kullanım şekli şudur:

flock /var/lock/benim-scriptim.lock -c "echo 'Script çalışıyor'"

Daha gerçekçi bir örnek:

#!/bin/bash
flock -n /var/lock/yedekleme.lock -c "/opt/scripts/yedekleme.sh"
if [ $? -ne 0 ]; then
    echo "Yedekleme scripti zaten çalışıyor, atlanıyor." | logger -t yedekleme
fi

Buradaki -n parametresi non-blocking modunu aktif eder. Kilit alınamazsa beklemek yerine hemen çıkar.

Dosya Tanımlayıcı Modu (Script İçi Kullanım)

Scriptinizin içinde kullanmak için en yaygın ve önerilen yöntem şudur:

#!/bin/bash

LOCKFILE="/var/lock/$(basename $0).lock"
LOCKFD=99

# Kilit dosyasını aç
exec 99>"$LOCKFILE"

# Kilidi almaya çalış
flock -n $LOCKFD || {
    echo "Hata: Script zaten çalışıyor. PID: $(cat $LOCKFILE)"
    exit 1
}

# PID'i dosyaya yaz (opsiyonel, debug için yararlı)
echo $$ >&$LOCKFD

echo "Script başladı. PID: $$"
# Buraya gerçek iş kodunuz gelir
sleep 30
echo "Script tamamlandı."

Bu yöntemin güzelliği, script herhangi bir şekilde sonlandığında (hata, SIGKILL dahil) kernel kilit dosyasını otomatik olarak serbest bırakır.

Önemli Parametreler

flock komutunun bilmeniz gereken parametreleri:

  • -s: Shared (paylaşımlı) kilit alır. Birden fazla okuyucu aynı anda çalışabilir.
  • -x: Exclusive (özel) kilit alır. Varsayılan moddur, sadece bir writer çalışabilir.
  • -n: Non-blocking mod. Kilit alınamazsa beklemeden hata döner.
  • -w [saniye]: Belirtilen süre kadar kilidi bekler, süre dolunca çıkar.
  • -u: Kilidi manuel olarak serbest bırakır.
  • -o: Komut çalıştırılmadan önce lock dosyasını kapatır.
  • -c: Çalıştırılacak komutu string olarak alır.
  • -e / –verbose: Hata mesajlarını daha ayrıntılı gösterir.

Gerçek Dünya Senaryosu 1: Cron Job Çakışması Önleme

En yaygın kullanım senaryosu budur. Diyelim ki her 5 dakikada çalışan bir log işleme scriptiniz var ama bazen 7-8 dakika sürüyor:

#!/bin/bash
# /opt/scripts/log-isle.sh

LOCKFILE="/var/lock/log-isle.lock"
LOGFILE="/var/log/log-isle.log"

# Tarih fonksiyonu
log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "$LOGFILE"
}

# Kilit dosyası tanımlayıcısını aç
exec 200>"$LOCKFILE"

# Non-blocking kilit dene
if ! flock -n 200; then
    log "UYARI: Başka bir instance çalışıyor. Bu çalışma atlanıyor."
    exit 0
fi

log "Başlatıldı. PID: $$"

# İşlem mantığı buraya
find /var/log/uygulama/ -name "*.log" -newer /tmp/son-isleme | while read dosya; do
    log "İşleniyor: $dosya"
    # Log işleme komutları
    gzip -k "$dosya"
    mv "${dosya}.gz" /archive/logs/
done

log "Tamamlandı."

Crontab’a şöyle eklersiniz:

*/5 * * * * /opt/scripts/log-isle.sh

Gerçek Dünya Senaryosu 2: Veritabanı Yedekleme

Veritabanı yedeklemelerinde çakışma çok daha ciddi sonuçlar doğurabilir. Tutarsız yedekalabilir, disk dolabilir veya veritabanı bağlantıları tükenebilir:

#!/bin/bash
# /opt/scripts/db-yedek.sh

set -euo pipefail

LOCKFILE="/var/lock/db-yedekleme.lock"
YEDEK_DIZIN="/backup/mysql"
TARIH=$(date +%Y%m%d_%H%M%S)
LOG="/var/log/db-yedek.log"

# Temizleme fonksiyonu
temizle() {
    local cikis_kodu=$?
    if [ $cikis_kodu -ne 0 ]; then
        echo "[$(date)] HATA: Yedekleme başarısız. Çıkış kodu: $cikis_kodu" >> "$LOG"
        # Tamamlanmamış yedeği sil
        rm -f "$YEDEK_DIZIN/gecici_yedek_$TARIH.sql"
    fi
}

trap temizle EXIT

# Kilit al
exec 201>"$LOCKFILE"

if ! flock -w 10 201; then
    echo "[$(date)] Kilit 10 saniye içinde alınamadı, yedekleme atlandı." >> "$LOG"
    exit 1
fi

echo "[$(date)] Yedekleme başladı. PID: $$" >> "$LOG"

# Önce geçici dosyaya yaz, başarılıysa taşı
mysqldump 
    --single-transaction 
    --routines 
    --triggers 
    --all-databases > "$YEDEK_DIZIN/gecici_yedek_$TARIH.sql"

gzip "$YEDEK_DIZIN/gecici_yedek_$TARIH.sql"
mv "$YEDEK_DIZIN/gecici_yedek_$TARIH.sql.gz" "$YEDEK_DIZIN/yedek_$TARIH.sql.gz"

# Eski yedekleri temizle (30 günden eski)
find "$YEDEK_DIZIN" -name "yedek_*.sql.gz" -mtime +30 -delete

echo "[$(date)] Yedekleme tamamlandı: yedek_$TARIH.sql.gz" >> "$LOG"

Gerçek Dünya Senaryosu 3: Paylaşımlı Kilit ile Okuma/Yazma Koordinasyonu

Bazen birden fazla script aynı anda bir kaynaktan okuyabilir, ama yazma sırasında okuma da yapılmamalıdır. Bu klasik reader-writer problemi için shared kilit kullanılır:

#!/bin/bash
# okuyucu.sh - Birden fazla instance aynı anda çalışabilir

LOCKFILE="/var/lock/veri-erisim.lock"
exec 202>"$LOCKFILE"

# Shared kilit al (diğer okuyucular engellenmez)
flock -s 202

echo "Okuyucu PID $$ veriyi okuyor..."
cat /opt/data/ortak-veri.json | python3 /opt/scripts/isle.py
echo "Okuyucu PID $$ tamamlandı."
#!/bin/bash
# yazici.sh - Çalışırken hiçbir okuyucu veya yazıcı çalışmamalı

LOCKFILE="/var/lock/veri-erisim.lock"
exec 203>"$LOCKFILE"

# Exclusive kilit al (tüm okuyucuların bitmesini bekler)
flock -x 203

echo "Yazıcı PID $$ veri güncelliyor..."
python3 /opt/scripts/veri-guncelle.py > /opt/data/ortak-veri.json.tmp
mv /opt/data/ortak-veri.json.tmp /opt/data/ortak-veri.json
echo "Yazıcı PID $$ tamamlandı."

Kilit Durumunu Kontrol Etme

Bir scriptin çalışıp çalışmadığını kontrol etmek için:

#!/bin/bash
# kilit-kontrol.sh

LOCKFILE="/var/lock/hedef-script.lock"

if flock -n "$LOCKFILE" true 2>/dev/null; then
    echo "Script şu anda çalışmıyor."
else
    echo "Script aktif olarak çalışıyor."
    # Hangi process tutuyor görmek için
    lsof "$LOCKFILE" 2>/dev/null | awk 'NR>1 {print "PID:", $2, "Komut:", $1}'
fi

Sistem genelinde tüm aktif kilitlerini görmek için:

# Tüm flock kilitlerini listele
lslocks | grep -E "FLOCK|flock"

# Ya da daha detaylı
cat /proc/locks

Gelişmiş Kullanım: Timeout ile Akıllı Bekleme

Bazen scriptin bir süre beklemesini, ama sonunda vazgeçmesini isteyebilirsiniz:

#!/bin/bash
# akilli-bekle.sh

LOCKFILE="/var/lock/kritik-islem.lock"
BEKLEME_SURESI=60  # 60 saniye bekle

exec 204>"$LOCKFILE"

echo "Kilit bekleniyor (maksimum $BEKLEME_SURESI saniye)..."

if ! flock -w "$BEKLEME_SURESI" 204; then
    echo "HATA: $BEKLEME_SURESI saniye içinde kilit alınamadı!"
    echo "Sistemi kontrol edin: lsof $LOCKFILE"
    exit 1
fi

echo "Kilit alındı, işlem başlıyor..."

# Uzun süren kritik işlem
rsync -avz /kaynak/ kullanici@sunucu:/hedef/

echo "İşlem tamamlandı."

Yaygın Hatalar ve Çözümleri

Hata 1: Alt Shell Sorunu

Pek çok kişi flock’u subshell içinde kullanır ve kilidi kaybeder:

#!/bin/bash
# YANLIS KULLANIM - Subshell kilit dosyasını kapatır
LOCKFILE="/var/lock/test.lock"

(
    flock -n 9 || exit 1
    echo "Bu çalışır ama kilit subshell bitince serbest kalır"
) 9>"$LOCKFILE"

# ANA SHELL'DE KILIT YOK!
# Uzun süren işlemleri burada yapmak hatalı

Doğrusu şudur:

#!/bin/bash
# DOGRU KULLANIM
LOCKFILE="/var/lock/test.lock"
exec 9>"$LOCKFILE"
flock -n 9 || { echo "Zaten çalışıyor"; exit 1; }

# Kilit tüm script boyunca tutulur
uzun_sure_calis

Hata 2: /tmp Kullanımı

/tmp dizinini kilit dosyası için kullanmaktan kaçının. Bazı sistemlerde noexec veya tmpfs ile mount edilmiş olabilir ve sistem yeniden başladığında temizlenebilir. /var/lock veya /var/run kullanın.

Hata 3: Kilit Dizininin Varlığını Kontrol Etmemek

#!/bin/bash
# Güvenli başlangıç
LOCK_DIR="/var/lock/scriptlerim"
mkdir -p "$LOCK_DIR"
LOCKFILE="$LOCK_DIR/$(basename $0 .sh).lock"

exec 9>"$LOCKFILE"
flock -n 9 || { echo "Zaten çalışıyor"; exit 1; }

Monitoring ve Alerting Entegrasyonu

Production ortamlarında kilitlerin ne zaman devreye girdiğini takip etmek önemlidir:

#!/bin/bash
# izleme-entegre.sh

LOCKFILE="/var/lock/production-job.lock"
SCRIPT_ADI=$(basename $0)
SLACK_WEBHOOK="${SLACK_WEBHOOK_URL:-}"

bildir() {
    local mesaj="$1"
    logger -t "$SCRIPT_ADI" "$mesaj"
    
    if [ -n "$SLACK_WEBHOOK" ]; then
        curl -s -X POST "$SLACK_WEBHOOK" 
            -H 'Content-type: application/json' 
            -d "{"text": "[$SCRIPT_ADI] $mesaj"}" > /dev/null
    fi
}

exec 9>"$LOCKFILE"

if ! flock -w 30 9; then
    KILITLI_PROCESS=$(lsof -t "$LOCKFILE" 2>/dev/null)
    bildir "UYARI: 30 saniye beklendi ama kilit alınamadı. Kilitli PID: $KILITLI_PROCESS"
    exit 1
fi

BASLANGIC=$(date +%s)
bildir "Başlatıldı. PID: $$"

# İşlemler...
./asil-is.sh

BITIS=$(date +%s)
SURE=$((BITIS - BASLANGIC))
bildir "Tamamlandı. Süre: ${SURE} saniye"

flock vs. Diğer Yöntemler

Bazı alternatiflerin kısa karşılaştırması:

  • PID dosyası yöntemi: Process ölünce PID dosyası ortada kalır, bir sonraki çalışmayı yanlışlıkla engelleyebilir. Manuel temizlik gerektirir.
  • mkdir tabanlı kilitleme: Atomic operasyon olmadığı için race condition riski taşır.
  • lockfile komutu: Procmail paketinden gelir, her sistemde yüklü değildir.
  • flock: Kernel seviyesinde atomic, otomatik temizlenen, güvenilir. Production için en iyi seçenek.

Sonuç

flock sysadmin araç kutunuzda olması gereken, öğrenmesi kolay ama etkisi büyük bir komut. Özellikle cron job çakışmaları, yedekleme scriptleri ve paylaşımlı kaynaklara erişim senaryolarında hayat kurtarıcı oluyor.

Özetle dikkat etmeniz gereken noktalar:

  • Kilit dosyalarını her zaman /var/lock veya /var/run altında tutun
  • Non-blocking mod (-n) mu yoksa timeout (-w) mu kullanacağınıza senaryonuza göre karar verin
  • exec ile dosya tanımlayıcı açmak, subshell kullanmaktan daha güvenlidir
  • Production’da kilit çakışmalarını mutlaka loglayın ve izleyin
  • lsof ve /proc/locks ile kilit durumunu her zaman kontrol edebilirsiniz

Bir de şunu söyleyeyim: “Bende hiç çakışma olmaz” diye düşünüyorsanız, o güveni sağlayan şey ya flock kullanıyor olmanız ya da henüz başınıza gelmemiş olmasıdır. İkincisini beklemek yerine şimdi ekleyin.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir