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/lockveya/var/runaltında tutun - Non-blocking mod (
-n) mu yoksa timeout (-w) mu kullanacağınıza senaryonuza göre karar verin execile dosya tanımlayıcı açmak, subshell kullanmaktan daha güvenlidir- Production’da kilit çakışmalarını mutlaka loglayın ve izleyin
lsofve/proc/locksile 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.
