Birçok üretim ortamında, bir systemd hizmetindeki yavaş bir bellek sızıntısı, OOM killer tarafından son olarak durdurulana kadar fark edilmez. O zaman kullanıcılar zaten etkilenmiş olur, loglar doldurulur ve hizmeti yeniden başlatmak için acil durumda kalırsınız. Ortamımda, iki basit ama güçlü systemd yönergesine güveniyorum: MemoryUsage= ve WatchdogSec=. Birlikte, artan bellek kullanımına erken fark varmanızı sağlar ve çekirdeğin müdahale etmesinden önce müdahale etmenizi mümkün kılar.
Temel fikir basittir: MemoryUsage= ile bir eşik tanımlarsınız ve bu eşik aşılırsa bir uyarı tetiklenir; bunu WatchdogSec= ile birleştirerek, hizmet belirli bir aralık içinde “check-in” yapmazsa otomatik olarak yeniden başlatılır. Bu, izleme araçlarını değiştirmekle ilgili değil — birim dosyası içinde hafif, çekirdek tarafından zorunlu kılınan bir güvenlik ağı eklemektir.
MemoryUsage= Nasıl Çalışır ve Erken Tespit İçin Nasıl Kullanılır
MemoryUsage= sadece belleği sınırlamaz — aynı zamanda bir hizmet tanımlı bir eşiği aşarsa günlük kaydı oluşturur. Bu, sızıntıları erken tespit etmek için mükemmeldir. Örneğin, bir hizmet normalde 200MB kullanıyorsa ama 500MB’ye doğru yavaşça tırmanıyorsa, 400MB’de bir uyarı ayarlayabilirsiniz.
Genellikle bir hizmet birimi dosyasına şunu eklerim:
[Service]
MemoryUsage=400M
Hizmet 400MB’yi aşarsa, systemd şöyle bir mesaj günlüğe kaydeder:
systemd[1]: myservice.service: Memory usage exceeded limit: 450.3M/400M
Bu mesaj journalctl -u myservice içinde görünür ve günlük toplayıcınız tarafından yakalanabilir. Ben bu mesajları Grafana veya Loki’ye özel bir uyarı kuralına yönlendiririm, böylece performans bozulmadan önce haberdar olurum. Bu bir sert sınır değil — hizmet çalışmaya devam eder — ancak size bir uyarı verir.
WatchdogSec= ile Otomatik Kurtarma için Eşleştirme
Şimdi, bunu WatchdogSec= ile birleştirin. Bu yönerge, hizmetin systemd'ye canlı olduğunu periyodik olarak bildirmesini bekler. Belirtilen süre içinde bildirim yapmazsa, systemd hizmetin donduğunu varsayar ve yeniden başlatır.
İşte burada hile var: Ben WatchdogSec= sadece donmuş hizmetler için kullanmıyorum. Bellek çok arttığında bir sıfırlama mekanizması olarak kullanıyorum. Hizmet hâlâ yanıt veriyorsa bile, bir sızıntı olduğu için sağlıksız olur. Bu yüzden sık bir watchdog aralığı ayarlarım ve hizmetin, çöp toplama veya temizlik döngüleri sonrasında sadece kendi watchdog bayrağını sıfırlamasını beklerim.
Örneğin:
[Service]
WatchdogSec=30
Hizmet, en az her 30 saniyede bir sd_notify("WATCHDOG=1") çağrısı yapmalıdır. Bellek artarsa ve hizmet yavaşlamaya başlar veya kontrolleri atlarsa, watchdog bir yeniden başlatma tetikler.
Pratik Örnek: Yavaş Sızıntıya Sahip Bir Java Servisi
Bir Java tabanlı API servisi, çözülmemiş thread locals nedeniyle saat başına yaklaşık 5MB sızıntı yapıyordu. Normalde 300MB'de duruyordu, dört gün içinde 1.2GB'ye kadar yükseliyordu — OOM eşiğine çok yakın.
Aşağıdaki yapılandırmayı yaptım:
[Service]
MemoryUsage=800M
WatchdogSec=60
800MB'de, erken bir journal uyarısı aldım. Daha önemli olanı, sızıntı kötüleştiğinde JVM'nin çöp toplama işlemi daha uzun sürmeye başladı ve bu da watchdog check-in'lerini kaçırmasına yol açtı. İki kez ardışık check-in kaçırıldıktan sonra, systemd servisi otomatik olarak yeniden başlattı — sızıntı temizlendi ve normal işleme döndü.
Bu davranışı aşağıdaki komutlarla doğruladım:
# Manuel uyarı testi tetikle (destekleniyorsa)
systemctl kill -s SIGWARNING myservice
# Bellek olaylarını journal'de kontrol et
journalctl -u myservice -b | grep -i "memory usage exceeded"
# Watchdog'un aktif olduğunu doğrula
systemctl show myservice -p WatchdogTimestamp
Güvenli Dağıtım İçin Tavsiyeler
İzleme-only modu ile başlayın. MemoryUsage= değerini yüksek bir seviyeye ayarlayın (örneğin, normal bazının %150) ve birkaç gün içinde günlüklerde tetiklemeleri izleyin. Güvendiğinizde, değeri gerçek erken uyarı eşiğine kadar düşürün.
WatchdogSec= için, hizmetiniz gerçekten watchdog bildirimlerini uyguladığından emin olun. Çoğu modern dil bu için kütüphane sunar: C'de sd_notify, Go'da systemd-daemon, Java için üçüncü parti jar'lar. Hizmetiniz bu özelliği desteklemiyorsa, bir sidecar adaptörü düşünün — ama sahte yapmayın.
Ayrıca, MemoryUsage= değerini çok düşük ayarlamaktan kaçının. Hizmeti normal ani artışlarda yeniden başlatmak yerine, sızıntıları yakalamak istiyorsunuz. Genellikle şu komutla baz tespiti yaparım:
# Son 24 saatteki maksimum bellek kullanımını al
journalctl -u myservice | grep -i "memory\|rss\|vmrss" | awk '{print $NF}' | sort -n | tail -5
Bu Kombinasyonu Ne Zaman Kullanmamalısınız
Bu yaklaşım, bellek artışının yavaş ve sürekli olduğu uzun süre çalışan hizmetler için en iyi sonuçları verir. Kısa ömürlü veya toplu işler için daha az faydalıdır. Ayrıca, doğru profiling yapmak veya kök nedeni düzeltmek yerine kullanılması gereken bir şey değildir — bir güvenlik ağıdır.
systemd-oomd ve OOMScoreAdjust ayarlarını, OOM puanlama konulu yazımda anlattığım gibi kritik hizmetler için hâlâ kullanıyorum. Ancak, yeniden başlatma kabul edilebilir ve sızıntılar yavaş olan hizmetler için, MemoryUsage= ve WatchdogSec= parametreleri, harici izleyicilerden elde edemeyeceğiniz görünürlük ve otonomi sağlar.
Sonuç
Hafıza sızıntılarını erken yakalamak için karmaşık araçlara ihtiyacınız yok. Sadece birim dosyasına iki satır ekleyerek, systemd'nin yerleşik özelliklerinden yararlanarak, günlük tabanlı uyarılar ve otomatik kurtarma elde edersiniz. Bu düşük yükü, güvenilir ve init sistemine derinlemesine entegre bir yaklaşımdır.
Linux üzerinde hizmet çalıştırıyorsanız ve beklenmeyen OOM kill'lerini azaltmak istiyorsanız, buradan başlayın. Erken tespit için MemoryUsage= ekleyin, bunu WatchdogSec= ile birleştirerek otomatik temizlemeyi sağlayın ve systemd'ye ağır işleri yapmasını bırakın — OOM killer devreye girmeden önce.
Kapak görseli: USDAgov · PDM (Openverse / kamu malı) · https://www.flickr.com/photos/41284017@N08/7644752188
