Üretim ortamında bir servis kontrolden çıktığında ne olduğunu hepimiz biliriz. Birkaç yıl önce yönettiğim bir sunucuda, yanlış yazılmış bir Python scripti gece yarısı tüm CPU’yu yutmaya başladı ve yanında çalışan kritik veritabanı servisi yanıt veremez hale geldi. O gece öğrendiğim ders şu oldu: kaynakları önceden kısıtlamak, kriz anında panik yaşamaktan çok daha iyidir. systemd’nin sunduğu CPUQuota ve MemoryLimit gibi direktifler tam da bu iş için var ve doğru kullanıldığında bir sunucunun istikrarını dramatik biçimde artırıyor.
systemd Kaynak Kısıtlamasının Temelleri
systemd, Linux çekirdeğinin cgroups (control groups) mekanizmasının üzerine oturmuş bir kaynak yönetim katmanı sunar. Yani arka planda dönen iş esasen Linux kernel’inin cgroup v1 ya da cgroup v2 altyapısına dayanıyor. systemd bunun üzerine sezgisel bir konfigürasyon dili koyuyor.
Bir servis dosyasının [Service] bölümüne eklediğiniz direktifler, o servisin hangi cgroup’a dahil edileceğini ve o cgroup üzerinde hangi kısıtların geçerli olacağını belirliyor. Bu sayede servis başladığında kısıtlar otomatik olarak devreye giriyor, servis durduğunda kaldırılıyor.
Kaynak kısıtlamasını destekleyen başlıca direktifler şunlar:
- CPUQuota: Servisin kullanabileceği maksimum CPU yüzdesini belirler
- CPUWeight: CPU zamanını öncelik bazlı paylaştırır (cgroup v2)
- MemoryLimit: Maksimum RAM kullanımını kısıtlar (cgroup v1 uyumlu)
- MemoryMax: Maksimum RAM kullanımını kısıtlar (cgroup v2 önerilen)
- MemoryHigh: Yumuşak bellek sınırı; aşılırsa servis yavaşlatılır
- TasksMax: Maksimum thread/process sayısını sınırlar
- IOWeight: Disk I/O önceliğini düzenler
Bu yazıda ağırlıklı olarak CPU ve bellek kısıtlamalarına odaklanacağız çünkü bunlar günlük hayatta en sık karşılaştığımız sorunların kaynağı.
Sistem Hazırlığı: cgroup v2 Kontrolü
Direktifleri kullanmadan önce hangi cgroup versiyonunu kullandığınızı bilmeniz önemli. Modern sistemlerde (Ubuntu 22.04+, RHEL 9, Fedora 31+) genellikle cgroup v2 aktif.
# cgroup versiyonunu kontrol et
mount | grep cgroup
# Ya da daha temiz bir yol
cat /proc/filesystems | grep cgroup
# systemd'nin cgroup modunu öğren
systemd-cgls --no-pager | head -20
Eğer /sys/fs/cgroup/ altında cgroup2 görüyorsanız v2 üzerindesiniz demektir. cgroup v2’de bazı direktiflerin adı değişti. MemoryLimit eski v1 direktifi, MemoryMax ise v2’nin önerdiği yol. Modern sistemlerde ikisi de çalışır ama MemoryMax kullanmak daha doğru.
CPUQuota ile CPU Kısıtlama
CPUQuota direktifi, bir servisin toplam CPU kapasitesinin yüzde kaçını kullanabileceğini belirler. Buradaki mantığı doğru anlamak önemli: %100, tek bir CPU çekirdeğin tamamı demek. Eğer 4 çekirdekli bir sunucunuzda CPUQuota=50% yazarsanız, servis teorik olarak 2 çekirdeğin tamamını kullanabilir.
Basit bir örnek üzerinden gidelim. Diyelim ki sunucunuzda bir log analiz servisi çalışıyor ve bu servisin CPU’yu boğmamasını istiyorsunuz:
# /etc/systemd/system/log-analyzer.service
[Unit]
Description=Log Analyzer Service
After=network.target
[Service]
Type=simple
User=loguser
ExecStart=/usr/local/bin/log-analyzer --daemon
CPUQuota=25%
Restart=on-failure
[Install]
WantedBy=multi-user.target
Bu konfigürasyonla servis, tek bir çekirdeğin %25’inden fazlasını kullanamaz. 8 çekirdekli bir sunucuda bile bu sınır geçerlidir.
Daha agresif bir senaryo düşünelim: bir web scraper servisi tüm çekirdeklerin toplamının %150’sini kullanabilsin istiyorsunuz (yani 1.5 CPU çekirdeği):
[Service]
ExecStart=/usr/local/bin/scraper
CPUQuota=150%
MemoryMax=512M
Mevcut Servislere Dinamik Kısıtlama
Her değişiklik için servis dosyasını düzenlemenize gerek yok. systemctl set-property komutuyla çalışan bir servise anlık kısıtlama ekleyebilirsiniz:
# Çalışan nginx servisine CPU kısıtı ekle
sudo systemctl set-property nginx.service CPUQuota=30%
# Değişikliğin uygulandığını doğrula
systemctl show nginx.service | grep CPUQuota
# Bu komut aynı zamanda kalıcı olarak /etc/systemd/system.control/ altına yazar
# Sadece geçici yapmak için --runtime bayrağı kullan
sudo systemctl set-property --runtime nginx.service CPUQuota=30%
--runtime bayrağını kullandığınızda kısıtlama servis yeniden başladığında veya sistem reboot edildiğinde kaybolur. Production ortamında geçici müdahaleler için idealdir.
MemoryLimit ve MemoryMax ile Bellek Kısıtlama
Bellek kısıtlaması CPU’dan biraz daha kritik çünkü bir servis bellek sınırını aştığında kernel OOM (Out of Memory) killer devreye girer ve süreci öldürür. Bu istediğiniz bir durum olmayabilir, bu yüzden limitleri dikkatli belirlemek gerekiyor.
Temel Bellek Direktifleri
Aşağıdaki direktifler arasındaki farkı kavramak önemli:
- MemoryMin: Servisin garanti edilmiş minimum belleği. Sistem baskı altında bile bu miktar korunur.
- MemoryLow: Yumuşak minimum. Sistem baskı altında bu miktarın altına düşürülmekten kaçınılır.
- MemoryHigh: Yumuşak maksimum. Aşılırsa servis aktif olarak yavaşlatılır ve bellek geri alınmaya çalışılır.
- MemoryMax: Sert maksimum. Aşılırsa OOM killer devreye girer.
- MemorySwapMax: Swap kullanımının üst sınırı.
Gerçek dünya senaryosuna geçelim. Bir Java uygulamasını systemd servisi olarak çalıştırıyorsunuz ve bu uygulamanın bellek sızıntısı yaptığını biliyorsunuz (düzeltilmesini bekliyorsunuz ama bu birkaç sprint alacak):
# /etc/systemd/system/java-app.service
[Unit]
Description=Java Application Server
After=network.target postgresql.service
Requires=postgresql.service
[Service]
Type=forking
User=appuser
WorkingDirectory=/opt/java-app
ExecStart=/usr/bin/java -Xms256m -Xmx512m -jar /opt/java-app/app.jar
MemoryMin=256M
MemoryHigh=600M
MemoryMax=768M
MemorySwapMax=0
CPUQuota=80%
Restart=on-failure
RestartSec=10s
[Install]
WantedBy=multi-user.target
MemorySwapMax=0 ile swap kullanımını tamamen engelliyoruz. Java uygulamalarında swap, performansı ciddi düşürebileceğinden bu genellikle iyi bir pratik.
Bellek Limitlerini Doğru Boyutlandırma
Limitleri belirlerken gerçekçi olmak gerekiyor. Çok düşük set etmek sürekli OOM crash’lerine yol açar, çok yüksek set etmek ise kısıtlamanın amacını ortadan kaldırır.
Mevcut bir servisin ne kadar bellek kullandığını ölçmek için:
# Servisin güncel kaynak kullanımını gör
systemctl status java-app.service
# Daha detaylı cgroup bilgisi
systemd-cgtop
# Belirli bir servis için memory kullanımını izle
cat /sys/fs/cgroup/system.slice/java-app.service/memory.current
# Alternatif olarak
sudo systemctl show java-app.service | grep -E "Memory|CPU"
Bir süre izledikten sonra peak kullanımı bulun ve ona %20-30 buffer ekleyin. Bu sizin MemoryHigh değeriniz olabilir. MemoryMax ise biraz daha yüksek.
Gerçek Dünya Senaryosu: Çoklu Servis Orkestrasyonu
Tek bir sunucuda birden fazla kritik servis çalıştırdığınızda kaynak planlaması daha da önemli hale geliyor. Tipik bir web sunucusu senaryosunu ele alalım: Nginx, PHP-FPM, Redis ve bir background job worker aynı makinede çalışıyor.
Nginx (reverse proxy ve static file serving):
# /etc/systemd/system/nginx.service.d/limits.conf
# Bu dosya override olarak çalışır
[Service]
CPUQuota=40%
MemoryHigh=256M
MemoryMax=512M
PHP-FPM (dinamik içerik işleme):
# /etc/systemd/system/php8.2-fpm.service.d/limits.conf
[Service]
CPUQuota=100%
MemoryHigh=512M
MemoryMax=768M
TasksMax=50
Background Job Worker (önceliği düşük olmalı):
# /etc/systemd/system/job-worker.service
[Unit]
Description=Background Job Worker
After=redis.service
[Service]
Type=simple
User=www-data
ExecStart=/usr/bin/php /var/www/artisan queue:work --sleep=3 --tries=3
CPUQuota=20%
CPUWeight=50
MemoryHigh=128M
MemoryMax=256M
Nice=10
Restart=always
RestartSec=5s
[Install]
WantedBy=multi-user.target
CPUWeight=50 ile bu servisin CPU önceliğini düşürüyoruz (varsayılan 100). Nice=10 ile process önceliğini de düşürüyoruz. İkisi birlikte çalışınca bu worker sistem yoğunken arka plana çekilir.
Override Dosyaları ile Mevcut Servisleri Özelleştirme
Paket yöneticisinden kurduğunuz servislerin ana .service dosyalarını düzenlememek iyi bir pratik. Güncellemeler geldiğinde değişikliklerinizin üstüne yazılabilir. Bunun yerine override dosyası kullanın:
# Override dizini ve dosyası otomatik oluşturur, editörde açar
sudo systemctl edit nginx.service
# Açılan editöre şunları yazın:
# [Service]
# CPUQuota=50%
# MemoryMax=1G
Bu komut /etc/systemd/system/nginx.service.d/override.conf dosyasını oluşturur. Değişiklikten sonra:
# systemd konfigürasyonunu yeniden yükle
sudo systemctl daemon-reload
# Servisi yeniden başlat
sudo systemctl restart nginx.service
# Kısıtlamaların uygulandığını doğrula
sudo systemctl show nginx.service | grep -E "CPUQuota|MemoryMax|MemoryHigh"
Kaynak Kullanımını İzleme ve Sorun Giderme
Kısıtlamaları koyduktan sonra işi bırakmak olmaz. İzleme yapmak şart.
# Tüm servislerin kaynak kullanımına genel bakış
systemd-cgtop --depth=5
# Belirli bir servisin anlık durumu
systemctl status myapp.service
# OOM killer tarafından öldürülen processleri bul
sudo journalctl -k | grep -i "oom|killed process"
# Belirli bir servisin memory olaylarını izle
sudo journalctl -u myapp.service | grep -i "memory|oom|killed"
# cgroup v2 ile memory events izle
cat /sys/fs/cgroup/system.slice/myapp.service/memory.events
memory.events dosyasındaki çıktı özellikle değerlidir:
- low:
MemoryLowsınırı tetiklendi - high:
MemoryHighsınırı aşıldı - max:
MemoryMaxsınırı aşıldı ve OOM başladı - oom: OOM killer çalıştı
- oom_kill: OOM killer bir prosesi öldürdü
Eğer oom_kill sayısı artıyorsa limitiniz çok düşük ya da uygulamanızın gerçekten bir bellek sorunu var.
Servis Çöktükten Sonra Ne Olduğunu Anlamak
# Servisin son 100 log satırını gör
sudo journalctl -u myapp.service -n 100 --no-pager
# Kernel loglarında OOM mesajlarına bak
sudo dmesg | grep -E "oom|Out of memory" | tail -20
# systemd'nin servis hakkında tuttuğu istatistikleri gör
systemctl show myapp.service | grep -E "ExecMainCode|Result|NRestarts"
Gelişmiş: Slice ile Servis Gruplarını Yönetme
systemd’de servisler, slice adı verilen hiyerarşik gruplara atanabilir. Bu, bir grup servis için tek noktadan kaynak yönetimi yapmanızı sağlar.
Diyelim ki tüm web servislerinizin toplam CPU kullanımını %60 ile sınırlamak istiyorsunuz:
# /etc/systemd/system/web.slice
[Unit]
Description=Web Services Slice
Before=slices.target
[Slice]
CPUQuota=60%
MemoryMax=2G
Sonra bu slice’a servis atayın:
# /etc/systemd/system/nginx.service.d/slice.conf
[Service]
Slice=web.slice
# Slice'ı etkinleştir
sudo systemctl enable web.slice
sudo systemctl start web.slice
# Slice içindeki servisleri göster
systemd-cgls /web.slice
Bu yaklaşım özellikle mikroservis mimarilerinde işe yarıyor. Her ekibin servislerini ayrı slice’lara koyabilir ve toplam kaynak kullanımını garantileyebilirsiniz.
Systemd Transient Units ile Geçici Kısıtlamalar
Bazen kalıcı bir servis dosyası yazmak istemeden tek seferlik bir işi kısıtlı kaynaklarla çalıştırmak istersiniz. systemd-run tam bu iş için:
# CPU kısıtlı geçici servis olarak komut çalıştır
sudo systemd-run --slice=background.slice
--property=CPUQuota=10%
--property=MemoryMax=256M
/usr/bin/python3 /opt/scripts/heavy-script.py
# İnteraktif shell çalıştır, kaynakları kısıtlı
sudo systemd-run --pty --same-dir
--property=CPUQuota=50%
--property=MemoryMax=512M
/bin/bash
Bu özellikle bakım scriptleri, büyük veri işleme görevleri veya test amaçlı çalıştırmalar için son derece kullanışlı.
Yaygın Hatalar ve Çözümleri
Saha deneyimlerinden derlenmiş bazı kritik noktalar:
- CPUQuota çok düşük ayarlamak: Özellikle Java gibi JVM tabanlı uygulamalar başlangıçta yüksek CPU tüketir. Çok düşük bir quota, servisin başlamamasına ya da çok yavaş başlamasına yol açar. Startup için ayrı bir
ExecStartPrehook’u düşünülebilir ya da başlangıçta yüksek, sonra düşürülen dinamik bir yapı kurulabilir.
- MemoryMax’i çok agresif ayarlamak: Uygulama gece yarısı önemli bir işlem yaparken OOM kill yiyebilir.
MemoryHighkullanmak daha nazik bir yaklaşım çünkü uygulamaya “yavaşla” sinyali verir, hemen öldürmez.
- daemon-reload yapmayı unutmak: Servis dosyasını değiştirdikten sonra
systemctl daemon-reloadçalıştırmazsanız eski konfigürasyon geçerli olmaya devam eder. Bu klasik bir hata.
- cgroup v1/v2 karışıklığı:
MemoryLimitv1 direktifi,MemoryMaxise v2. Modern sistemlerde ikisi de kabul edilir ama tutarlı olmak için sisteminizin hangi versiyonu kullandığını bilin ve ona göre yazın.
- Konteyner içinde çalışan servislerde çifte kısıtlama: Docker ya da LXC konteyneri içindeyseniz hem konteynerin hem de systemd servisinin kısıtları geçerli. En düşük değer kazanır, bu durum kafa karıştırıcı olabilir.
Kısıtlamaların Etkisini Test Etme
Üretime koymadan önce kısıtların doğru çalıştığını doğrulamak istersiniz:
# stress-ng ile CPU baskısı oluştur (stress-ng kurulu olmalı)
sudo apt install stress-ng # Debian/Ubuntu
sudo dnf install stress-ng # Fedora/RHEL
# %25 CPUQuota olan bir servisi simüle et
sudo systemd-run --property=CPUQuota=25% stress-ng --cpu 4 --timeout 30s
# Başka bir terminalde izle
watch -n 1 systemd-cgtop
# Bellek limitini test et
sudo systemd-run --property=MemoryMax=100M
stress-ng --vm 1 --vm-bytes 200M --timeout 20s
İkinci komut OOM kill ile sonuçlanmalı çünkü 100M limit var ama 200M talep ediyoruz. Bu beklenen davranış.
Sonuç
systemd’nin kaynak kısıtlama direktifleri, Linux sistem yönetiminde proaktif kapasite planlamasının temel araçlarından biri. CPUQuota ile kontrolden çıkabilecek servislerin CPU’yu tam anlamıyla ele geçirmesini engelliyorsunuz, MemoryMax ve MemoryHigh kombinasyonuyla ise bellek yönetimini hem güvenli hem de esnek hale getiriyorsunuz.
Pratikte önerim şu: yeni bir servis deploy ettiğinizde önce monitöring araçlarıyla birkaç gün gerçek kullanım verisini toplayın. Peak kullanıma %30 buffer ekleyerek MemoryHigh ve MemoryMax değerlerinizi belirleyin. CPU tarafında ise servisin işlevine göre karar verin; kritik servisler için CPUWeight ile önceliklendirme, arka plan işleri için CPUQuota ile sert sınır daha uygun.
Slice mekanizmasını da göz ardı etmeyin. Özellikle tek sunucuda çok sayıda servis koşturduğunuzda, servisleri mantıksal gruplara ayırıp grup bazlı kaynak yönetimi yapmak operasyonel yükü önemli ölçüde azaltıyor.
Son olarak: kısıtlamaları koyduktan sonra journalctl, systemd-cgtop ve cgroup memory.events dosyalarını düzenli izleyin. Bir servis sürekli OOM kill yiyorsa sorun uygulamada, limit çok düşükse konfigürasyonda demektir. Her iki durumda da erken uyarı sistemi kurmuş olmanın verdiği güven, gece yarısı alarmlarla uğraşmak zorunda kalmamaktan çok daha değerli.