Üretim Ortamlarında Systemd Unit Dosyaları ile Güçlü Servis Yönetimi
Linux sistemlerinde servis yönetimi, bir sistem yöneticisinin veya DevOps mühendisinin temel sorumluluklarından biridir. Modern Linux dağıtımlarında bu görevi üstlenen birincil init sistemi systemd'dir. Systemd, sistem kaynaklarının verimli kullanımı, hızlı önyükleme ve güvenilir servis yönetimi için kapsamlı bir çerçeve sunar. Servislerinizi systemd ile yönetmek, uygulamalarınızın üretim ortamlarında tutarlı, izlenebilir ve otomatik bir şekilde çalışmasını sağlar.
Systemd Unit Dosyasının Temel Yapısı
Systemd, servisleri, mount noktalarını, soketleri ve diğer sistem kaynaklarını yönetmek için "unit" adı verilen yapıları kullanır. Bir servis için yazılan unit dosyaları genellikle .service uzantısıyla /etc/systemd/system/ dizininde veya ilgili paketlerin /usr/lib/systemd/system/ dizininde bulunur. Bir .service unit dosyası üç ana bölümden oluşur:
[Unit]: Servisin genel tanımını, bağımlılıklarını ve meta verilerini içerir.[Service]: Servisin nasıl başlatılacağını, durdurulacağını, yeniden başlatılacağını ve çalışma zamanı davranışlarını tanımlar.[Install]: Servisin sistem açılışında nasıl etkinleştirileceğini (enable) belirler.
Temel bir iskelet şu şekildedir:
[Unit]Description=My Custom Application ServiceAfter=network.target # Bu servis network.target yüklendikten sonra başlasın[Service]ExecStart=/usr/local/bin/myapp --config /etc/myapp.conf # Servisin başlatma komutuExecStop=/usr/local/bin/myapp --stop # Servisin durdurma komutu (opsiyonel)Restart=on-failure # Servis hata durumunda otomatik yeniden başlasınUser=myappuser # Servisin hangi kullanıcı altında çalışacağıGroup=myappgroup # Servisin hangi grup altında çalışacağı[Install]WantedBy=multi-user.target # Servis, çok kullanıcılı moda geçildiğinde etkinleştirilsin[Unit] Bölümü: Bağımlılıklar ve Meta Veriler
[Unit] bölümü, bir servisin diğer sistem bileşenleriyle olan ilişkisini ve genel davranışını tanımlar. Kritik direktifler şunlardır:
Description: Servisin kısa ve açıklayıcı bir tanımı.Documentation: Servisle ilgili belgelere URL veya dosya yolu.After/Before: Servisin hangi unit'lerden sonra veya önce başlatılması gerektiğini belirtir. Örneğin, bir web uygulaması için veritabanı servisiAfter=postgresql.serviceile tanımlanabilir.Requires: Belirtilen unit'in bu servisten önce başarıyla başlatılması gerektiğini zorunlu kılar. Eğer bağımlı unit başarısız olursa, bu servis de başlatılmaz.Wants:Requires'a benzer ancak daha yumuşaktır. Bağımlı unit'in başarısız olması bu servisin başlatılmasını engellemez.Conflicts: Belirtilen unit ile bu servisin aynı anda çalışamayacağını belirtir.
Örnek [Unit] kullanımı:
[Unit]Description=High-Performance Cache ServiceDocumentation=https://docs.mycache.orgAfter=network.target redis.service # Ağ ve Redis servisi yüklendikten sonra başlasınRequires=redis.service # Redis'in kesinlikle çalışır olması gerekiyorConflicts=old-cache.service # Eski önbellek servisi ile çakışır[Service] Bölümü: Çalışma Zamanı Konfigürasyonu
Bu bölüm, servisin kendisinin nasıl çalıştırılacağı, durdurulacağı ve izleneceğiyle ilgili talimatları içerir. En önemli direktifler şunlardır:
Type: Servisin başlatma davranışını belirler.simple(varsayılan):ExecStartkomutunun ana proses olduğunu varsayar ve hemen başlatılır.forking:ExecStartkomutunun bir child proses oluşturup ana prosesin çıkacağını varsayar. Systemd, child prosesi izler. Genellikle eski daemon'lar için kullanılır.oneshot: Servisin tek bir işlem çalıştırmak için kullanıldığını ve tamamlandığında çıktığını varsayar (örneğin, bir veritabanı yedeği alma).notify: Servisin başlatıldığını ve hazır olduğunu systemd'ye bildirmek içinsd_notify()çağrısını kullanacağını belirtir.
ExecStart: Servisi başlatmak için çalıştırılacak tam komut.ExecStop: Servisi durdurmak için çalıştırılacak komut (opsiyonel ama önerilir).ExecReload: Servisi yeniden yüklemek için çalıştırılacak komut (opsiyonel).Restart: Servisin beklenmedik bir şekilde sona ermesi durumunda systemd'nin nasıl davranacağını belirler (örneğin,always,on-failure,no).User/Group: Servisin hangi kullanıcı ve grup kimliği altında çalışacağını belirleyerek güvenlik sağlar.WorkingDirectory: Servis komutunun hangi dizinde çalışacağını belirler.Environment/EnvironmentFile: Servis için ortam değişkenleri tanımlar.LimitNOFILE/LimitNPROC: Açık dosya tanımlayıcıları ve proses limitleri gibi sistem kaynaklarını sınırlar.
Gerçek bir senaryodan alınmış [Service] örneği:
[Service]Type=simple # Uygulama main proses olarak çalışırExecStart=/usr/bin/node /opt/my-api/src/index.js --port 8080 # Node.js API'sini başlatExecStop=/bin/kill -TERM $MAINPID # PID ile durdurUser=apiuser # 'apiuser' kullanıcısı altında çalıştırGroup=apigroup # 'apigroup' grubu altında çalıştırWorkingDirectory=/opt/my-api # API'nin kök diziniRestart=on-failure # Hata durumunda yeniden başlatRestartSec=5s # Yeniden başlatmadan önce 5 saniye bekleEnvironment="NODE_ENV=production" "DATABASE_URL=postgres://user:pass@db:5432/mydb" # Ortam değişkenleriProtectSystem=full # Sistem dizinlerine yazma izni vermeProtectHome=true # Kullanıcı ev dizinlerine erişimi kısıtlaNoNewPrivileges=true # Ayrıcalık yükseltmelerini engelleLimitNOFILE=65536 # Açık dosya sayısını artır[Install] Bölümü: Servis Etkinleştirme
Bu bölüm, servisin sistem açılışında otomatik olarak başlaması için nasıl etkinleştirileceğini (enable) kontrol eder. Temel direktif WantedBy'dır:
WantedBy: Servisin hangi systemd "target"ına bağlandığını belirtir. En yaygın olanımulti-user.target'tır, yani çok kullanıcılı sistem modu (genellikle GUI olmayan sunucular için).graphical.targetise GUI içeren sistemler içindir. Bu direktif, servisisystemctl enablekomutuyla etkinleştirdiğinizde sembolik bir bağlantı oluşturulmasını sağlar.
Örnek [Install] kullanımı:
[Install]WantedBy=multi-user.target # Sistem çok kullanıcılı moda geçtiğinde bu servisi etkinleştirGerçek Dünya Senaryosu: Golang Web Uygulamasını Systemd ile Yönetmek
Bir Golang REST API uygulamasını ele alalım. Uygulama, /opt/go-api/ dizininde bulunur ve go-api adında bir çalıştırılabilir dosya üretir. Bu uygulamanın apiuser kullanıcısı altında, 8080 portunda sürekli çalışmasını istiyoruz.
Öncelikle, basit bir Go uygulaması oluşturalım (/opt/go-api/main.go):
package mainimport ("fmt""log""net/http")func handler(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Merhaba, Go API çalışıyor! Path: %s\n", r.URL.Path)}func main() { http.HandleFunc("/", handler) log.Println("Go API 8080 portunda dinliyor...") log.Fatal(http.ListenAndServe(":8080", nil))}Bu kodu derleyelim:
cd /opt/go-apigo build -o go-api main.goŞimdi /etc/systemd/system/go-api.service adında bir unit dosyası oluşturalım:
[Unit]Description=Golang REST API ServiceAfter=network.target # Ağ servisi yüklendikten sonra başlasın[Service]Type=simple # Uygulama ana proses olarak çalışırExecStart=/opt/go-api/go-api # Çalıştırılabilir dosyanın yoluUser=apiuser # Servisin 'apiuser' kullanıcısı altında çalışmasını sağlaGroup=apiuser # Servisin 'apiuser' grubu altında çalışmasını sağlaWorkingDirectory=/opt/go-api # Çalışma diziniRestart=on-failure # Hata durumunda otomatik yeniden başlatRestartSec=10s # Yeniden başlatmadan önce 10 saniye bekleStandardOutput=journal # Çıktıları journal'a yönlendirStandardError=journal # Hataları journal'a yönlendir[Install]WantedBy=multi-user.target # Çok kullanıcılı modda etkinleştirBu dosyayı kaydeddikten sonra, systemd'ye yeni bir unit dosyasının eklendiğini bildirmemiz gerekir:
sudo systemctl daemon-reloadŞimdi servisi başlatabilir ve sistem açılışında otomatik başlaması için etkinleştirebiliriz:
sudo systemctl start go-api.servicesudo systemctl enable go-api.serviceServisin durumunu kontrol etmek için:
sudo systemctl status go-api.serviceÇıktı, servisin aktif olup olmadığını, PID'sini ve son log mesajlarını gösterecektir. Uygulamanızın loglarını incelemek için journalctl kullanırız:
sudo journalctl -u go-api.service -f # Canlı logları izleBu adımlar, basit bir Go uygulamasını üretim ortamında güvenilir bir şekilde systemd ile yönetmek için gereken temel süreci özetlemektedir.
Servis Optimizasyonu ve En İyi Uygulamalar
Systemd unit dosyalarını sadece temel komutlarla sınırlamak yerine, güvenlik ve performans için ek direktifler kullanmak önemlidir:
- Kaynak Sınırlamaları:
LimitCPU,LimitMEMLOCKgibi direktiflerle bir servisin kullanabileceği kaynakları kısıtlayarak sistem kararlılığını artırın. Örneğin, bellek sızıntısı olan bir uygulama tüm RAM'i tüketmesin. - Güvenlik Sertleştirmesi:
ProtectSystem=full,ProtectHome=true,PrivateTmp=true,NoNewPrivileges=truegibi direktiflerle servisin dosya sistemi erişimini ve yetkilerini sınırlayın. Bu, bir uygulamanın ele geçirilmesi durumunda sistem üzerindeki etkiyi minimize eder. - Restart Politikaları:
Restart=on-failureveyaalwaysgibi politikaları kullanarak servisin beklenmedik durumlarda otomatik olarak kurtarılmasını sağlayın.RestartSecile yeniden başlatmalar arasında bekleme süresi tanımlayın. - Ortam Değişkenleri: Hassas bilgileri (veritabanı şifreleri, API anahtarları) doğrudan unit dosyasına yazmak yerine
EnvironmentFilekullanarak ayrı bir dosyadan yükleyin ve bu dosyanın izinlerini kısıtlayın. - Log Yönetimi:
StandardOutput=journalveStandardError=journalkullanarak tüm çıktıları systemd journal'ına yönlendirin. Bu,journalctlile merkezi ve zaman damgalı log erişimi sağlar. - Performans Analizi:
systemd-analyze blamevesystemd-analyze critical-chainkomutlarıyla sistemin önyükleme süresini ve servislerin başlatılma sırasını analiz ederek darboğazları tespit edin ve optimizasyon yapın.
Systemd unit dosyaları, Linux servis yönetiminde esneklik, kontrol ve otomasyon sağlar. Doğru yapılandırılmış unit dosyaları, uygulamalarınızın üretim ortamlarında güvenle, verimli ve öngörülebilir bir şekilde çalışmasını garanti eder. Bu derinlemesine yaklaşımla, karmaşık sistem gereksinimlerini karşılayabilir ve sistemlerinizin operasyonel direncini artırabilirsiniz.