flock Komutu ile Kabuk Betiklerinde Dosya Kilitleme ve Eşzamanlı Çalışmayı Önleme
Cron job’larınız birbirinin üzerine mi basıyor? Bir betik çalışırken aynısı tekrar tetikleniyor ve veritabanı tutarsızlıkları, yarım kalan işlemler mi yaşıyorsunuz? Bu sorun, özellikle yoğun üretim ortamlarında can sıkıcı bir hal alıyor. flock komutu bu tür problemleri temelden çözen, hafif ama son derece güçlü bir araç. Bugün bu aracı her yönüyle ele alacağız.
flock Nedir ve Neden Var?
flock, Linux’ta dosya kilitleme mekanizmasını kabuk betiklerinden kullanmanızı sağlayan bir komut satırı aracıdır. Temelinde flock(2) sistem çağrısını kullanır. Çekirdek seviyesinde bir kilit oluşturduğu için, aynı anda birden fazla betik örneğinin çalışmasını engellemek için ideal bir çözümdür.
Klasik örnek: Her gece çalışan bir yedekleme betiğiniz var. Normalde 20 dakikada bitiyor, ama bir gece dosya sistemi yavaşlıyor ve betik 35 dakika sürüyor. Tam bu sırada cron yeni bir örneği tetikliyor. İki betik aynı anda çalışıyor, aynı dosyalara yazıyor, yedek bozuluyor. flock bu senaryoyu tamamen ortadan kaldırır.
flock genellikle util-linux paketinin bir parçası olarak gelir ve neredeyse tüm modern Linux dağıtımlarında hazır bulunur. Kontrol etmek için:
which flock
flock --version
Temel Kullanım Mantığı
flock iki farklı modda kullanılabilir: doğrudan bir komut çalıştırma modu ve kabuk betiği içinde kullanım modu.
Doğrudan komut çalıştırma:
flock /tmp/benim-kilit.lock -c "echo 'Bu komut sadece bir kez çalışır'"
Burada /tmp/benim-kilit.lock dosyası kilit dosyası olarak kullanılır. Eğer bu dosya üzerinde başka bir flock kilidi varsa, komut bekler ya da hata döner.
Betik içinde kullanım (fd ile):
#!/bin/bash
exec 200>/var/lock/yedekleme.lock
flock -n 200 || { echo "Betik zaten çalışıyor, çıkıyorum."; exit 1; }
echo "İşlem başladı: $(date)"
# Gerçek işlemler buraya gelir
sleep 30
echo "İşlem bitti: $(date)"
Bu ikinci yöntem daha idiomatik ve esnektir. exec 200> ile dosya tanımlayıcı 200’ü kilit dosyasına bağlıyoruz, ardından flock -n 200 ile kilit almaya çalışıyoruz.
Önemli Parametreler
-s / --shared: Paylaşımlı (okuma) kilidi alır. Birden fazla süreç aynı anda paylaşımlı kilit tutabilir.
-x / --exclusive: Özel (yazma) kilidi alır. Varsayılan davranış budur. Yalnızca bir süreç bu kilidi tutabilir.
-n / --nonblock: Kilit alınamazsa beklemek yerine hemen hata döner (çıkış kodu 1).
-w / --timeout : Belirtilen süre kadar kilit almayı bekler, süre aşılırsa çıkış kodu 1 döner.
-u / --unlock: Kilidi açar.
-o / --close: Çocuk süreç başlatılmadan önce kilit dosyasını kapatır.
-c / --command: Çalıştırılacak komutu belirtir, sh -c üzerinden çalıştırır.
-E / --conflict-exit-code : Kilit alınamadığında döndürülecek çıkış kodunu belirler.
Gerçek Dünya Senaryoları
Senaryo 1: Cron Job Çakışmalarını Önleme
En yaygın kullanım alanı budur. Aşağıdaki betik, birden fazla kez tetiklense bile yalnızca bir örneği çalışacak şekilde tasarlanmıştır:
#!/bin/bash
# /usr/local/bin/gunluk-rapor.sh
KILIT_DOSYASI="/var/lock/gunluk-rapor.lock"
LOG="/var/log/gunluk-rapor.log"
exec 9>"$KILIT_DOSYASI"
if ! flock -n 9; then
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Betik zaten çalışıyor, bu örnek sonlandırılıyor." >> "$LOG"
exit 0
fi
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Rapor oluşturma başladı." >> "$LOG"
# Rapor oluşturma işlemleri
/usr/local/bin/veri-cek.sh | /usr/local/bin/rapor-olustur.sh > /tmp/bugunun-raporu.html
# E-posta gönderimi
mail -s "Günlük Rapor $(date '+%d/%m/%Y')" [email protected] < /tmp/bugunun-raporu.html
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Rapor gönderildi." >> "$LOG"
/etc/cron.d/gunluk-rapor:
*/15 * * * * root /usr/local/bin/gunluk-rapor.sh
Bu yapıda betik her 15 dakikada bir tetiklenebilir, ama eğer önceki çalışma hâlâ devam ediyorsa yeni örnek sessizce çıkar.
Senaryo 2: Zaman Aşımlı Bekleme ile Veritabanı Yedekleme
Bazen betiğin çakışma durumunda beklemesini isteyebilirsiniz. Örneğin birden fazla yedekleme işi varsa ve sıralı çalışmalarını istiyorsanız:
#!/bin/bash
# /usr/local/bin/pg-yedekle.sh
DB_ADI="${1:-production}"
YEDEK_DIZIN="/mnt/yedekler/postgresql"
KILIT="/var/lock/pg-yedek.lock"
BEKLEME_SURESI=300 # 5 dakika bekle
exec 8>"$KILIT"
if ! flock -w "$BEKLEME_SURESI" 8; then
echo "HATA: $BEKLEME_SURESI saniye içinde kilit alınamadı. Başka bir yedekleme sürüyor olabilir."
exit 2
fi
echo "Yedekleme başlıyor: $DB_ADI - $(date)"
pg_dump -Fc -d "$DB_ADI" -f "${YEDEK_DIZIN}/${DB_ADI}_$(date '+%Y%m%d_%H%M%S').dump"
if [ $? -eq 0 ]; then
echo "Yedekleme başarıyla tamamlandı: $(date)"
# Eski yedekleri temizle (30 günden eskiler)
find "$YEDEK_DIZIN" -name "${DB_ADI}_*.dump" -mtime +30 -delete
else
echo "HATA: Yedekleme başarısız oldu!"
exit 1
fi
Senaryo 3: Paylaşımlı ve Özel Kilit Kombinasyonu
Bu senaryo biraz daha ileri düzey. Birden fazla okuma işleminin eş zamanlı yürüyebileceği, ama yazma işleminin tek başına çalışması gereken durumlar için:
#!/bin/bash
# okuyucu.sh - Birden fazla örnek aynı anda çalışabilir
KILIT="/var/lock/veri-islem.lock"
exec 7>"$KILIT"
flock -s 7 # Paylaşımlı kilit
echo "Okuma yapılıyor: PID $$ - $(date)"
# Okuma işlemleri
cat /var/data/paylasilan-veri.json | jq '.items[]'
echo "Okuma tamamlandı: PID $$"
#!/bin/bash
# yazici.sh - Sadece bir örnek çalışabilir, okuyucular bekler
KILIT="/var/lock/veri-islem.lock"
exec 7>"$KILIT"
if ! flock -x -w 60 7; then
echo "Yazma kilidi alınamadı, okuyucular devam ediyor olabilir."
exit 1
fi
echo "Yazma yapılıyor: PID $$ - $(date)"
# Yazma işlemleri - tüm okuyucular bu süre boyunca bekleyecek
echo '{"items": ["yeni", "veri"]}' > /var/data/paylasilan-veri.json
echo "Yazma tamamlandı: PID $$"
Gelişmiş Kullanım Kalıpları
PID Dosyası ile Birleşik Kullanım
Sadece kilit almak yetmez, bazen hangi sürecin kilidi tuttuğunu da bilmek istersiniz. PID dosyası ile birleştirme:
#!/bin/bash
KILIT="/var/lock/uzun-islem.lock"
PID_DOSYASI="/var/run/uzun-islem.pid"
exec 6>"$KILIT"
if ! flock -n 6; then
CALISNA_PID=$(cat "$PID_DOSYASI" 2>/dev/null)
if [ -n "$CALISAN_PID" ]; then
echo "Betik zaten çalışıyor (PID: $CALISAN_PID)"
else
echo "Betik zaten çalışıyor (PID bilinmiyor)"
fi
exit 1
fi
# Kilidi aldık, PID'imizi kaydet
echo $$ > "$PID_DOSYASI"
# Çıkışta PID dosyasını temizle
trap 'rm -f "$PID_DOSYASI"' EXIT
echo "İşlem başlıyor (PID: $$)"
# Uzun süren işlemler
sleep 60
echo "İşlem tamamlandı"
flock ile Paralel İş Kuyruğu
Birden fazla iş eş zamanlı çalışabileceği ama toplam sayının sınırlı tutulması gereken durumlar için:
#!/bin/bash
# is-kuyrugu.sh
MAX_PARALEL=3
IS_LISTESI=("is1" "is2" "is3" "is4" "is5" "is6")
bir_is_yap() {
local is_adi="$1"
local slot="$2"
local kilit="/var/lock/is-slot-${slot}.lock"
exec {fd}>"$kilit"
flock -x "$fd"
echo "[Slot $slot] Başlıyor: $is_adi"
sleep $((RANDOM % 10 + 5))
echo "[Slot $slot] Bitti: $is_adi"
eval "exec ${fd}>&-"
}
for is in "${IS_LISTESI[@]}"; do
# Boş slot bul ve işi oraya yerleştir
for slot in $(seq 1 $MAX_PARALEL); do
kilit="/var/lock/is-slot-${slot}.lock"
exec {fd}>"$kilit"
if flock -n "$fd"; then
eval "exec ${fd}>&-"
bir_is_yap "$is" "$slot" &
break
fi
eval "exec ${fd}>&-"
done
done
wait
echo "Tüm işler tamamlandı"
systemd Timer ile Entegrasyon
Cron yerine systemd timer kullanıyorsanız, flock yine işe yarar çünkü systemd’nin ConditionPathExists direktifi bazı durumlar için yeterli olmayabilir:
#!/bin/bash
# /usr/local/bin/systemd-gibi-betik.sh
# systemd service olarak çalışır ama örtüşmeyi engeller
set -euo pipefail
SERVIS_ADI="veri-senkron"
KILIT="/run/lock/${SERVIS_ADI}.lock"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | systemd-cat -t "$SERVIS_ADI" -p info
}
exec 5>"$KILIT"
if ! flock -n 5; then
log "Önceki çalışma henüz tamamlanmadı, bu örnek atlanıyor."
exit 0
fi
log "Senkronizasyon başlıyor"
# rsync ile senkronizasyon
rsync -avz --delete
--exclude='.git'
/kaynak/dizin/
yedek-sunucu:/hedef/dizin/
log "Senkronizasyon tamamlandı"
Kilit Dosyası Nereye Konulmalı?
Bu soru pratikte önemlidir. Deneyimden gelen birkaç kural:
/var/lock/: Sistem genelindeki betikler için, ancak önyüklemede temizlenmez./run/lock/veya/var/run/: Önyüklemede temizlenir, geçici işlemler için idealdir. Modern sistemlerde tercih edilmesi gereken yer burasıdır./tmp/: Kullanılabilir ama güvenlik açısından dikkatli olunmalı (symlink saldırıları). Root olmayan betikler için bile/tmpyerinemktempkullanımı tercih edilebilir.- Betiğin kendi dizini: Bağıl yollarla kilit dosyası oluşturmaktan kaçının, mutlak yol kullanın.
Ayrıca kilit dosyasının bulunduğu dizin, betiği çalıştıran kullanıcı tarafından yazılabilir olmalıdır.
Dikkat Edilmesi Gereken Durumlar
NFS üzerinde flock çalışmaz: flock(2) NFS üzerinde güvenilir değildir. Paylaşımlı NFS dizinlerinde kilit mekanizması olarak flock kullanmayın, lockd protokolü tutarsız davranabilir. NFS’te lockfile komutu ya da özel çözümler tercih edilmelidir.
Kilit dosyasını silmek kilidi açmaz: Bir süreç kilit dosyasını silse bile, diğer süreçlerde açık olan dosya tanımlayıcı kilit bilgisini korur. Kilit, dosya tanımlayıcısı kapatıldığında ya da süreç sonlandığında serbest bırakılır. Bu aslında bir özellik; betik çöktüğünde kilit otomatik serbest kalır.
# Test: Kilit tutulurken dosya silinirse ne olur?
flock /tmp/test.lock sleep 30 &
rm /tmp/test.lock # Silindikten sonra bile kilit geçerli
flock -n /tmp/test.lock echo "Bu çalışmaz" # Yeni dosya oluşur, kilit farklı inode'da
Çıkış kodlarını kontrol edin: flock -n ile nonblock modda çalışırken, özellikle set -e kullanıyorsanız kilit alınamama durumu betiği beklenmedik şekilde sonlandırabilir. Bu yüzden if ! flock -n fd gibi açık kontrol kullanmak daha güvenlidir.
Hızlı Test ve Debug
Kilit mekanizmanızın doğru çalışıp çalışmadığını test etmek için:
# Terminal 1: Kilit tut
flock -x /tmp/test.lock bash -c 'echo "Kilit alındı, 30 sn bekliyorum"; sleep 30'
# Terminal 2: Kilidi almaya çalış (nonblock)
flock -n /tmp/test.lock echo "Ben de çalıştım"
# Çıktı: Hiçbir şey (çıkış kodu 1)
# Hangi süreç kilidi tutuyor?
fuser /tmp/test.lock
# lslocks ile sistem genelindeki kilitleri gör
lslocks | grep test
lslocks komutu, sistemdeki tüm aktif kilitleri listeler ve debug sürecinde çok işe yarar.
Sonuç
flock komutu, Linux betik yazımında kritik bir güvenlik ağıdır. Özellikle üretim ortamlarında cron job’ların birbirinin üzerine basmasından, paralel çalışmaların yarattığı veri tutarsızlıklarından ve uzun süren işlemlerin çift tetiklenmesinden kaynaklanan sorunların büyük çoğunluğunu çözer.
Hafif, güvenilir ve çekirdek destekli bir mekanizma kullandığı için üçüncü taraf araçlara ya da karmaşık çözümlere gerek kalmaz. Betiklerinize flock entegre etmek için birkaç satır eklemek yeterli, karşılığında elde ettiğiniz güvenlik ve öngörülebilirlik ise paha biçilemez.
Küçük bir öneri ile bitirelim: Mevcut kritik betiklerinizi gözden geçirin. Eğer bir betiğin cron ile her X dakikada çalıştığını ve süresinin bazen X’i aştığını biliyorsanız, o betik flock için birinci adaydır. Bu tür betiklere flock eklemek için “bir şey bozulmayı beklemek” zorunda değilsiniz.
