Eğitimler

Uygulamanız için systemd servisi: unit'ten otomatik yeniden başlatmaya

Uygulamanız SSH kapattıktan sonra ölüyorsa veya root kullanıcısıyla çalışıyorsa, bu pratik kılavuz unit oluşturma, otomatik yeniden başlatma ve journalctl ile loglama tam size göre.

Eğitimler

Uygulamanızı nohup ./app & ile başlattınız, SSH'ı kapattınız ve yarım saat sonra site 502 veriyor. Ya da daha kötüsü: süreç hâlâ canlı ama kimse nerede olduğunu, kimin çalıştırdığını ve sunucu yeniden başlatıldıktan sonra neden geri gelmediğini bilmiyor. İşte tam bu noktada bir systemd unit dosyası yazmanız ve shell script'lerinden kurtulmanız gerekiyor.

Neden nohup ve screen işe yaramıyor

Her iki araç da süreci canlı tutar, ancak hiçbiri sorumluluk almaz. Süreç çökerse, kimse onu geri getirmez. Sunucu yeniden başlatılırsa, hiçbir şey ayağa kalkmaz. Log istiyorsanız, /tmp içinde dağınık dosyaları aramanız gerekir. systemd bu üç işi tek bir metin dosyasıyla yapar ve bunun yanı sıra kaynak kısıtlamalarını, önyükleme sırasını ve diğer servislere bağımlılığı da yönetir.

İlk unit dosyasını oluşturma

Dosyayı /etc/systemd/system/myapp.service konumunda oluşturun. Yol önemlidir: /etc/systemd/system içindeki unit'ler paket yöneticisinin yüklediği her şeyden önceliklidir ve paket güncellemesiyle silinmez.

[Unit]
Description=My Node API
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=appuser
Group=appuser
WorkingDirectory=/srv/myapp
ExecStart=/usr/bin/node /srv/myapp/server.js
Restart=on-failure
RestartSec=5
Environment=NODE_ENV=production
EnvironmentFile=-/etc/myapp/env
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target

Bu dosyadaki birkaç satır diğerlerinden daha önemlidir. After=network-online.target uygulamanın ağ hazır olmadan önce çalışmamasını garanti eder; bu olmadan, ağdaki bir veritabanına bağlanan servis önyüklemede bağlantı hatasıyla ölür ve siz kodun bozuk olduğunu düşünürsünüz. EnvironmentFile'ın başındaki tire işareti, dosya yoksa systemd'nin hata vermemesi anlamına gelir. Ve Restart=on-failure yalnızca süreç sıfırdan farklı çıkış koduyla veya sinyalle öldüğünde onu geri getirir; uygulamanız kendisi sıfır koduyla çıkıyorsa ve tekrar ayağa kalkmasını bekliyorsanız, Restart=always ayarlamalısınız.

Type=simple mı yoksa Type=exec mi?

Çoğu uygulama için Type=simple yeterlidir. systemd'nin çalıştırılabilir dosyanın gerçekten var olduğundan ve çalıştırıldığından emin olmasını istiyorsanız, Type=exec seçin; pratik farkı, "dosya bulunamadı" hatasını logda değil, servis durumunda o anda görürsünüz. Kendi kendini daemonize eden servisler için (PHP-FPM gibi) Type=forking kullanın, ancak uygulamanızı kendiniz yazıyorsanız, daemonize etmeyin; systemd bundan hoşlanmaz ve süreç takibini zorlaştırır.

Root olmayan kullanıcıyla çalıştırma

Burada hata yapıyorlar: unit'i User= olmadan yazıyorlar ve uygulama root ile ayağa kalkıyor. İşaretini sonradan görüyorlar, uygulamadaki basit bir güvenlik açığı sunucuya tam erişime dönüştüğünde. Kullanıcı oluşturun ve dizin sahipliğini verin:

sudo useradd --system --no-create-home --shell /usr/sbin/nologin appuser
sudo chown -R appuser:appuser /srv/myapp
sudo chmod 750 /srv/myapp

Uygulama 1024 altındaki bir portta dinliyorsa (80 veya 443 gibi), root olmayan kullanıcının bind izni yoktur. Doğru yol, uygulamanın önüne Nginx gibi bir reverse proxy koymak ve uygulamanın 3000 gibi yüksek bir portta dinlemesidir. İkinci yol, servise AmbientCapabilities=CAP_NET_BIND_SERVICE vermektir ki bu en az gerekli izni verir. Üçüncü yol, root ile çalıştırmaktır, ki bu yanlıştır.

Etkinleştirme ve otomatik yeniden başlatma

Unit dosyasındaki her değişiklikten sonra systemd'nin yeniden okuması gerekir:

sudo systemctl daemon-reload
sudo systemctl enable --now myapp.service
systemctl status myapp.service

enable --now satırı hem servisi sonraki önyükleme için etkinleştirir hem de şimdi çalıştırır. Yalnızca start yazarsanız, sunucu yeniden başlatıldıktan sonra servis ayağa kalkmaz ve bu "dün çalışıyordu"nun en yaygın nedenlerinden biridir.

Otomatik yeniden başlatmayı test etmek için süreci kasten öldürün ve systemd'nin birkaç saniye sonra onu geri getirip getirmediğine bakın:

sudo pkill -f server.js
sleep 6
systemctl status myapp.service | grep -E "Active|Main PID"

Servis activating (auto-restart) durumunda takılıysa, yeniden başlatma sayısı sınırı aşmış demektir. systemd varsayılan olarak bir servis kısa bir aralıkta arka arkaya birkaç kez ölürse denemekten vazgeçer. [Unit] bölümünde StartLimitIntervalSec=60 ve StartLimitBurst=5 ile bu davranışı kontrol edin. Bu sınırı kasten sonsuz yapmayın; döngüde çöken ve sürekli ayağa kalkan bir uygulama sunucu kaynaklarını sessizce tüketir.

journalctl ile loglar

Uygulama çıktısı otomatik olarak journal'a kaydedilir, yeter ki StandardOutput=journal ayarlanmış olsun. En çok kullanılan komutlar:

journalctl -u myapp.service -n 100 --no-pager
journalctl -u myapp.service -f
journalctl -u myapp.service --since "1 hour ago"
journalctl -u myapp.service -p err -b

-f anahtarı tail -f gibi çalışır, -b yalnızca mevcut önyüklemenin loglarını getirir ve -p err hata seviyesi ve üstünü filtreler. Çoğu kişiyi şaşırtan bir nokta: journal varsayılan olarak diske kaydedilmez ve yeniden başlatmayla silinir. Logların kalmasını istiyorsanız, /etc/systemd/journald.conf içinde Storage=persistent değerini ayarlayın ve systemctl restart systemd-journald çalıştırın. Hacmi kontrol etmek için SystemMaxUse=500M de ayarlayın ki journal tüm diski doldurmasın.

Uygulama logu kendi dosyasına yazdığında

Uygulamanız herhangi bir nedenle ayrı bir dosyaya log yazmak zorundaysa, StandardOutput=append:/var/log/myapp/app.log kullanın (systemd sürüm 240 ve üzeri). Bu, ExecStart'ta manuel yönlendirmeden daha iyidir, çünkü log rotasyonunu ve dosya sahipliğini kendiniz kontrol edersiniz ve systemd dosyayı servis kullanıcısıyla açar.

Kaynak kısıtlaması ve sıkılaştırma

Hiçbir shell script'in sağlamadığı avantajlardan biri de servisi kısıtlamaktır. Gerçekten değerli birkaç satır:

MemoryMax=512M
CPUQuota=80%
PrivateTmp=true
ProtectSystem=strict
ReadWritePaths=/srv/myapp/uploads
NoNewPrivileges=true

MemoryMax servisi sınırı aştığında öldürür ve logda oom-kill mesajıyla kaydedilir; bu mesajı journal'da görürseniz, sorunun kod olmadığını, bellek sınırı olduğunu bilin. ProtectSystem=strict tüm dosya sistemini salt okunur yapar ve yalnızca ReadWritePaths içinde belirttiğiniz yollar yazılabilir kalır. Bu ayar mükemmeldir, ancak bedeli vardır: uygulamanız bu yolların dışında bir yerde geçici dosya oluşturuyorsa, Read-only file system hatasıyla ölür ve bunu bulmak zaman alır. Önce ProtectSystem=full ile başlayın ve sonra daha da sıkılaştırın.

Production'a geçmeden önce

Üç şeyi kontrol edin. Birincisi, systemctl enable'ı unutmayın. İkincisi, sunucuyu bir kez yeniden başlatın ve servisin kendi kendine ayağa kalkıp kalkmadığına bakın; bunu test etmemek kritik saatlerde en çok zaman kaybettiren şeydir. Üçüncüsü, uygulamanın doğru kullanıcıyla çalıştığından emin olun: ps -o user= -p $(systemctl show -p MainPID --value myapp.service) root değil, servis kullanıcısının adını döndürmelidir.

Hâlâ özel bir sunucuda çalışıyorsanız ve aynı yapıyı yönetilen bir ortamda uygulamak istiyorsanız, Linux hosting iyi bir başlangıç noktasıdır çünkü systemd ve journalctl'e tam erişiminiz vardır. WordPress uygulamaları için de cron'unuz gerçek wp-cron yerine çalışmıyorsa, aynı unit dosyası mantığı işe yarar; örneğini WordPress wp-cron; neden çalışmıyor yazısında ele aldık. Servis ayağa kalkıyor ama kullanıcı HTTP hatası alıyorsa, kök nedene daha hızlı ulaşmak için HTTP durum kodu: yaygın hatalar için pratik kılavuz yazısına bakın. Ve sunucu Linux'unda yeniyseniz, ServerNet dokümantasyon ve bilgi tabanı kopyalayıp düzenleyebileceğiniz birkaç hazır unit örneği içerir.

Sık sorulan sorular

systemd servisim neden SSH kapattıktan sonra ölüyor?

systemd servisi asla oturumunuza bağlı değildir ve SSH'ı kapatmak onu öldürmemelidir. Ölüyorsa, muhtemelen uygulamayı systemctl start ile çalıştırmadınız ve eski nohup süreci hâlâ çalışıyor. systemctl status myapp ile Main PID'in ps aux'ta gördüğünüz süreçle aynı olup olmadığını kontrol edin.

systemctl restart ile reload arasındaki fark nedir?

restart süreci tamamen öldürür ve sıfırdan ayağa kaldırır, bu yüzden birkaç saniye kesinti olur. reload SIGHUP sinyali gönderir ve uygulamanız bunu destekliyorsa, kesinti olmadan ayarları yeniden okur. Uygulama SIGHUP'ı yönetmiyorsa, reload etkisizdir veya süreci öldürür; emin olmak için ExecReload=/bin/kill -HUP $MAINPID satırını açıkça yazın.

Servisin hangi kullanıcıyla çalıştığını nasıl anlarım?

systemctl show -p MainPID --value myapp.service komutu ana süreç numarasını verir ve ps -o user= -p PID kullanıcı adını verir. Eğer root dönerse, unit dosyasında User= satırı yok demektir veya onu değiştirdikten sonra daemon-reload yapmamışsınız demektir.

journalctl logları yeniden başlatmadan sonra siliniyor; ne yapmalıyım?

Journal bazı dağıtımlarda varsayılan olarak bellekte tutulur. /etc/systemd/journald.conf içinde Storage=persistent değerini etkinleştirin ve journald servisini yeniden başlatın. SystemMaxUse ile de hacim sınırı belirleyin ki loglar diski doldurmasın.

ServerNet Destek

ServerNet mühendislik ve yayın ekibi — altyapı, ağ ve web barındırma uzmanları.

WordPress Hosting
Paylaş:

Yorumlar 0

Henüz yorum yok — ilk siz olun!

Yorum bırakın

İlgili hizmet

WordPress Hosting

LiteSpeed Enterprise ve NVMe üzerinde WordPress'e özel altyapı — otomatik kurulum, güvenli güncelleme, staging ve sizi Google'da üstte tutan önbellek.