Systemd ile Ortam Değişkeni ve Kaynak Limiti Tanımlama

Systemd servislerini yönetirken en çok göz ardı edilen konulardan biri, servislerin çalışma ortamını doğru şekilde yapılandırmaktır. Bir uygulama geliştirme ortamında mükemmel çalışırken production’da beklenmedik davranışlar sergiliyorsa, büyük ihtimalle ortam değişkenleri veya kaynak limitleri konusunda bir sorun vardır. Bu yazıda systemd unit dosyalarında ortam değişkeni tanımlamayı, harici environment dosyalarını kullanmayı ve kaynak limitlerini kontrol etmeyi gerçek dünya senaryolarıyla ele alacağız.

Ortam Değişkenleri Neden Önemli?

Geleneksel init sistemlerinde bir servis başlatıldığında, o servis genellikle root kullanıcısının shell ortamını miras alırdı. Systemd bu yaklaşımı tamamen değiştirdi. Systemd ile başlatılan her servis, izole bir ortamda çalışır ve yalnızca sizin açıkça tanımladığınız değişkenlere sahip olur.

Bu izolasyon aslında bir güvenlik avantajıdır. Bir servisin yanlışlıkla PATH, HOME veya hassas kimlik bilgilerini içeren ortam değişkenlerine erişmesini engellersiniz. Ancak bu durum, uygulamanızın ihtiyaç duyduğu değişkenleri bilinçli olarak tanımlamanızı zorunlu kılar.

Tipik bir sorun şöyle görünür: Node.js uygulamanız terminal üzerinden çalıştırdığınızda NODE_ENV=production değişkenini okurken, systemd servisi olarak başlatıldığında bu değişkeni bulamaz ve development modunda açılır. Ya da bir Java uygulaması JAVA_OPTS değişkenini bulamadığı için heap size ayarları olmadan başlar ve zamanla bellek sorunları yaşar.

Environment Direktifi ile Satır İçi Değişken Tanımlama

En basit yöntem, unit dosyasının [Service] bölümüne doğrudan Environment direktifi eklemektir.

# /etc/systemd/system/myapp.service

[Unit]
Description=My Node.js Application
After=network.target

[Service]
Type=simple
User=appuser
WorkingDirectory=/opt/myapp
Environment="NODE_ENV=production"
Environment="PORT=3000"
Environment="LOG_LEVEL=info"
ExecStart=/usr/bin/node /opt/myapp/server.js
Restart=always

[Install]
WantedBy=multi-user.target

Birden fazla değişkeni tek satırda da tanımlayabilirsiniz:

Environment="NODE_ENV=production" "PORT=3000" "LOG_LEVEL=info"

Ancak okunabilirlik açısından her değişkeni ayrı satırda tanımlamayı tercih etmenizi öneririm. Özellikle onlarca değişken olduğunda tek satır yöntemi bakımı zorlaştırır.

Önemli bir nokta: Değişken değerinde boşluk varsa çift tırnak içine almak zorundasınız. Değer içinde çift tırnak kullanmanız gerekirse ters eğik çizgi ile escape edin:

Environment="JAVA_OPTS=-Xms512m -Xmx2g -XX:+UseG1GC"
Environment="APP_DESC=My "Special" Application"

EnvironmentFile Direktifi ile Harici Dosya Kullanımı

Gerçek dünyada onlarca ortam değişkeni olabilir ve bunları unit dosyasına gömmek hem yönetimi zorlaştırır hem de güvenlik açısından sorunludur. Özellikle veritabanı şifreleri veya API anahtarları söz konusu olduğunda, bu değerlerin unit dosyasında görünmesi istemezsiniz çünkü systemctl cat komutuyla kolayca görüntülenebilirler.

EnvironmentFile direktifi bu sorunu çözer:

# /etc/systemd/system/webapp.service

[Unit]
Description=Web Application
After=network.target postgresql.service

[Service]
Type=simple
User=webapp
WorkingDirectory=/opt/webapp
EnvironmentFile=/etc/webapp/environment
EnvironmentFile=-/etc/webapp/environment.local
ExecStart=/opt/webapp/bin/start.sh
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

Dikkat edin, ikinci EnvironmentFile satırında başına - işareti koyduk. Bu, dosya bulunamazsa systemd’nin hata vermeyeceği anlamına gelir. Bu özelliği local override dosyaları veya opsiyonel konfigürasyonlar için kullanabilirsiniz.

Environment dosyasının formatı basittir:

# /etc/webapp/environment
# Bu dosyaya 600 izin verin: chmod 600 /etc/webapp/environment
# Sahipliği servis kullanıcısına atayın: chown root:webapp /etc/webapp/environment

DATABASE_URL=postgresql://webapp:gizlisifre@localhost:5432/webappdb
REDIS_URL=redis://localhost:6379/0
SECRET_KEY=uzun-ve-rastgele-bir-anahtar-buraya
SMTP_HOST=mail.sirket.com
SMTP_PORT=587
[email protected]
SMTP_PASS=mailsifre123
MAX_WORKERS=4
CACHE_TTL=3600

Environment dosyasının güvenliğini sağlamak kritiktir:

# Dosyayı oluştur ve izinleri ayarla
sudo touch /etc/webapp/environment
sudo chmod 600 /etc/webapp/environment
sudo chown root:root /etc/webapp/environment

# İçeriği ekle
sudo nano /etc/webapp/environment

# Servis dosyasını yeniden yükle
sudo systemctl daemon-reload
sudo systemctl restart webapp

Systemd-Creds ile Güvenli Kimlik Bilgisi Yönetimi

Systemd 249 ve üzeri sürümlerde systemd-creds aracı ile kimlik bilgilerini şifreleyerek saklayabilirsiniz. Bu özellik özellikle Ubuntu 22.04+ ve RHEL 9+ sistemlerde kullanılabilir:

# Şifreli credential dosyası oluştur
echo -n "gizlisifre123" | sudo systemd-creds encrypt --name=db-password -

# Unit dosyasında kullanım
# [Service] bölümüne ekleyin:
LoadCredentialEncrypted=db-password:/etc/credstore.encrypted/db-password

# Uygulama içinde okuma:
# $CREDENTIALS_DIRECTORY/db-password dosyasından okunur

Bu yöntem, şifrelenmiş kimlik bilgilerini makineye özgü bir anahtarla korur ve dosya sistemi erişimi olan birinin bile düz metin olarak okuyamamasını sağlar.

Kaynak Limitleri: Servislerin Kontrolden Çıkmasını Önleme

Production ortamında tek bir servisin tüm sistem kaynaklarını tüketmesi felaket senaryosudur. Bir bellek sızıntısı olan uygulama, sınırsız büyüyerek diğer servislerin çökmesine neden olabilir. Systemd’nin kaynak limit direktifleri bu durumu önler.

CPU Limitleri

[Service]
# CPU ağırlığı (eski CPUShares'in yerini aldı, 1-10000 arası)
CPUWeight=100

# Maksimum CPU kullanımı (örnek: 2 çekirdek = 200%)
CPUQuota=50%

# CPU affinity - sadece belirli çekirdeklerde çalıştır
CPUAffinity=0 1

CPUQuota direktifi en pratik olanıdır. Bir servisin asla toplam CPU kapasitesinin %50’sinden fazlasını kullanmamasını garanti eder. 4 çekirdekli bir sistemde CPUQuota=200% diyerek maksimum 2 çekirdek kapasitesi kullanmasına izin verebilirsiniz.

Bellek Limitleri

[Service]
# Bellek kullanımını bu değerde sınırla (aşarsa OOM killer devreye girer)
MemoryMax=2G

# Soft limit - aşılabilir ama swap kullanımı artar
MemoryHigh=1500M

# Minimum garanti edilen bellek
MemoryMin=256M

# Swap kullanımını da sınırla (MemoryMax + MemorySwapMax)
MemorySwapMax=512M

MemoryMax ve MemoryHigh arasındaki fark önemlidir. MemoryHigh bir soft limit olarak çalışır: bu sınır aşıldığında systemd süreci yavaşlatmaya çalışır ancak öldürmez. MemoryMax ise sert bir sınırdır ve bu değer aşıldığında kernel OOM killer süreci sonlandırır.

Bir web scraping servisi için örnek:

# /etc/systemd/system/scraper.service

[Unit]
Description=Web Scraper Service
After=network.target

[Service]
Type=simple
User=scraper
WorkingDirectory=/opt/scraper
EnvironmentFile=/etc/scraper/environment

# Kaynak limitleri
CPUWeight=50
CPUQuota=25%
MemoryHigh=512M
MemoryMax=768M
MemorySwapMax=256M

# IO limitleri
IOWeight=50
IOReadBandwidthMax=/dev/sda 50M
IOWriteBandwidthMax=/dev/sda 20M

ExecStart=/usr/bin/python3 /opt/scraper/main.py
Restart=on-failure
RestartSec=30

[Install]
WantedBy=multi-user.target

Dosya Tanımlayıcı ve Process Limitleri

[Service]
# Açık dosya tanımlayıcı limiti (ulimit -n karşılığı)
LimitNOFILE=65536

# Maksimum process/thread sayısı
LimitNPROC=512

# Core dump boyutu (0 = core dump kapalı)
LimitCORE=0

# Maksimum stack boyutu
LimitSTACK=8M

Elasticsearch, Nginx veya yüksek trafikli Java uygulamaları için LimitNOFILE değerini artırmak neredeyse her zaman gereklidir. Varsayılan 1024 veya 4096 değerleri bu uygulamalar için yetersiz kalır:

# /etc/systemd/system/elasticsearch.service.d/limits.conf
# (override dosyası olarak ekleyin)

[Service]
LimitNOFILE=65535
LimitNPROC=4096
LimitMEMLOCK=infinity
Environment="ES_HEAP_SIZE=4g"
Environment="JAVA_OPTS=-Xms4g -Xmx4g"

Override Dosyaları ile Mevcut Servisleri Değiştirme

Paket yöneticisi tarafından yüklenen bir servisin unit dosyasını doğrudan düzenlemek kötü bir pratiktir. Paket güncellendiğinde değişiklikleriniz silinir. Bunun yerine override dosyaları kullanın:

# Bu komut /etc/systemd/system/nginx.service.d/override.conf dosyasını oluşturur
sudo systemctl edit nginx

Ya da dosyayı manuel oluşturabilirsiniz:

sudo mkdir -p /etc/systemd/system/nginx.service.d/
sudo nano /etc/systemd/system/nginx.service.d/resource-limits.conf
# /etc/systemd/system/nginx.service.d/resource-limits.conf

[Service]
# Mevcut değerleri sıfırla (bazı direktifler için gerekli)
LimitNOFILE=

# Yeni değeri tanımla
LimitNOFILE=100000

# Ortam değişkeni ekle
Environment="NGINX_WORKER_PROCESSES=auto"

# Bellek limiti
MemoryMax=4G
MemoryHigh=3G

Override sistemi şu şekilde çalışır: systemd önce orijinal unit dosyasını okur, ardından /etc/systemd/system/servisadi.service.d/ dizinindeki tüm .conf dosyalarını alfabetik sırayla uygular. Bu sayede orijinal dosyaya dokunmadan istediğiniz değeri ezebilirsiniz.

Mevcut efektif konfigürasyonu kontrol etmek için:

# Tüm override'lar dahil birleşik konfigürasyonu göster
sudo systemctl cat nginx

# Servisin anlık kaynak kullanımını göster
sudo systemctl status nginx

# Cgroup kaynak kullanımını izle
sudo systemd-cgtop

Gerçek Dünya Senaryosu: PostgreSQL Servisini Optimize Etme

Bir müşterinin sunucusunda PostgreSQL’in yavaş çalıştığını ve zaman zaman bellek hatası verdiğini düşünelim. Önce mevcut durumu inceleyelim:

# Servisin mevcut limitlerini kontrol et
systemctl show postgresql -p LimitNOFILE,LimitNPROC,MemoryMax,CPUQuota

# Cgroup üzerinden anlık kullanımı izle
cat /sys/fs/cgroup/system.slice/postgresql.service/memory.current

Ardından optimize edilmiş bir override dosyası oluşturalım:

# /etc/systemd/system/postgresql.service.d/tuning.conf

[Service]
# PostgreSQL için kritik dosya tanımlayıcı limiti
LimitNOFILE=65536

# Shared memory için gerekli
LimitMEMLOCK=infinity

# PostgreSQL ortam değişkenleri
Environment="PGDATA=/var/lib/postgresql/14/main"
Environment="PGOPTIONS=-c max_connections=200"

# Bellek yönetimi
MemoryHigh=6G
MemoryMax=8G

# CPU - database servisi yüksek öncelikli olsun
CPUWeight=200
Nice=-5

# IO önceliği yüksek tut
IOWeight=200

Bu değerleri uyguladıktan sonra:

sudo systemctl daemon-reload
sudo systemctl restart postgresql

# Değişikliklerin uygulandığını doğrula
sudo systemctl show postgresql | grep -E "Memory|CPU|Limit"

Systemd-Analyze ile Ortam Değişkenlerini Debug Etme

Bir servisin hangi ortam değişkenlerine sahip olduğunu görmek için birkaç yöntem vardır:

# Servis ortamını göster (systemd 247+)
sudo systemctl show webapp --property=Environment

# Çalışan prosesin gerçek ortamını göster (PID üzerinden)
sudo cat /proc/$(systemctl show webapp -p MainPID --value)/environ | tr '' 'n'

# Servis loglarında ortam değişkenlerini görmek için
# Unit dosyasına geçici olarak ekleyin:
ExecStartPre=/usr/bin/env

Ortam değişkeni sorunlarını debug ederken en faydalı yöntem servisi foreground’da çalıştırmaktır:

# Servisi systemd olmadan, aynı kullanıcıyla test et
sudo -u webapp /bin/bash -c 'export $(cat /etc/webapp/environment | xargs) && /opt/webapp/bin/start.sh'

Tmpfiles ve RuntimeDirectory ile Geçici Dosya Yönetimi

Servisler çalışma zamanında geçici dosyalara veya dizinlere ihtiyaç duyabilir. Systemd bunun için özel direktifler sunar:

[Service]
# /run/altında servis için dizin oluştur (servis durduğunda silinir)
RuntimeDirectory=myapp
RuntimeDirectoryMode=0750

# /var/lib/ altında kalıcı dizin
StateDirectory=myapp
StateDirectoryMode=0750

# /var/cache/ altında cache dizini
CacheDirectory=myapp

# /var/log/ altında log dizini
LogsDirectory=myapp

# Bu dizinler otomatik olarak User/Group sahipliğine atanır
User=appuser
Group=appuser

Bu direktifler sayesinde ExecStartPre ile dizin oluşturmak veya izinleri ayarlamakla uğraşmazsınız. Systemd her şeyi sizin için halleder ve servis kullanıcısı bu dizinlere yazabilir.

Ortam değişkeni olarak bu dizin yollarını da kullanabilirsiniz:

[Service]
RuntimeDirectory=myapp
StateDirectory=myapp
Environment="RUNTIME_DIR=/run/myapp"
Environment="DATA_DIR=/var/lib/myapp"

Sık Yapılan Hatalar ve Çözümleri

Hata 1: Değişken değerinde tırnak sorunu

Systemd environment dosyalarında bash’teki gibi tırnak işlemesi yapılmaz. Değerleri tırnak içine almadan yazın:

# YANLIS - bash'te olduğu gibi tırnak kullanmak
DATABASE_URL="postgresql://user:pass@host/db"

# DOGRU - tırnak olmadan
DATABASE_URL=postgresql://user:pass@host/db

Eğer değerde özel karakter varsa ve unit dosyasında Environment= direktifi kullanıyorsanız, değerin tamamını çift tırnak içine alın ancak değer içindeki tırnak işaretlerini escape edin.

Hata 2: systemctl daemon-reload Unutmak

Unit dosyasını her değiştirdiğinizde daemon-reload çalıştırmanız gerekir. Aksi halde systemd eski konfigürasyonu kullanmaya devam eder:

sudo systemctl daemon-reload && sudo systemctl restart webapp

Hata 3: MemoryMax ve cgroup v2 Uyumsuzluğu

Eski sistemlerde cgroup v1 kullanılıyorken yeni sistemlerde cgroup v2 varsayılandır. MemoryMax cgroup v2’ye özgüdür. Sisteminizin hangi cgroup versiyonunu kullandığını kontrol edin:

# cgroup versiyonunu kontrol et
stat -fc %T /sys/fs/cgroup/

# tmpfs çıktısı = cgroup v1
# cgroup2fs çıktısı = cgroup v2

Hata 4: LimitNOFILE için Çift Tanım

Bazı servislerin /etc/security/limits.conf dosyasında da limit tanımları olabilir. Systemd servisleri bu dosyayı okumaz (PAM ile başlatılmadığı sürece), bu yüzden sistemin genel limitlerini değiştirseniz bile systemd servisi için ayrıca tanımlamanız gerekir.

Kaynakları İzleme ve Doğrulama

Tanımladığınız limitlerin gerçekten uygulandığını doğrulamak önemlidir:

# Servisin cgroup limitlerini göster
sudo cat /sys/fs/cgroup/system.slice/webapp.service/memory.max
sudo cat /sys/fs/cgroup/system.slice/webapp.service/cpu.max

# Tüm servislerin kaynak kullanımını anlık izle
sudo systemd-cgtop -d 2

# Servis için detaylı kaynak raporu
sudo systemctl status webapp --no-pager -l

# Journald üzerinden kaynak ihlali loglarını izle
sudo journalctl -u webapp -f --grep="memory|killed|oom"

OOM Killer tarafından öldürülen servisleri tespit etmek için:

sudo journalctl -k --grep="Out of memory|oom_kill" -n 50

Sonuç

Systemd’nin ortam değişkeni ve kaynak limit yönetimi, production sistemlerin kararlılığı için kritik öneme sahiptir. Birkaç temel noktayı özetleyecek olursak:

  • Hassas bilgileri EnvironmentFile ile yönetin ve bu dosyaların izinlerini (chmod 600) doğru ayarlayın.
  • Paket yöneticisi tarafından yüklenen servisleri override dosyaları ile özelleştirin, orijinal unit dosyasını doğrudan değiştirmeyin.
  • MemoryMax, CPUQuota ve LimitNOFILE direktiflerini production’da her zaman tanımlayın, varsayılanlara güvenmeyin.
  • RuntimeDirectory ve StateDirectory direktiflerini kullanarak manuel dizin yönetiminden kaçının.
  • Değişiklik yaptıktan sonra systemd-cgtop ve /proc/PID/environ ile konfigürasyonun doğru uygulandığını doğrulayın.

Bu araçları doğru kullanan bir sistem yöneticisi, hem güvenlik açısından izole hem de kaynak açısından kontrollü bir servis ortamı oluşturabilir. Bir servisin kontrolden çıkıp tüm sistemi etkilemesi artık geçmişte kalır.

Yorum yapın