Hizmetler Hosting & Sunucu Araçlar Blog Ara Kurumsal EnglishEN
Teklif Alın

Bir Node.js uygulamasını sunucuya taşıyıp node app.js ile çalıştırmak, geliştirme aşamasında işe yarar ama production için yeterli değildir. Terminal penceresini kapattığınız ya da SSH bağlantınız koptuğu an, çoğu durumda işlem de sizinle birlikte sonlanır; kernel, oturuma bağlı işlemlere SIGHUP sinyali gönderir ve nohup, screen ya da tmux gibi bir önlem alınmadıysa uygulama durur. Bu yazıda bu sorunun neden var olduğunu, systemd'nin bunu nasıl çözdüğünü ve bir .service unit dosyasının nasıl yapılandırıldığını ele alıyoruz.

"node app.js" komutunun production'da yetersiz kalması

Terminalde doğrudan başlatılan bir Node.js işleminin üç temel zayıflığı vardır. Birincisi, oturuma bağımlıdır: SSH bağlantısı kapanınca işlem de kapanır. İkincisi, kendi kendini izleyemez: uygulama beklenmeyen bir hata (uncaught exception, bellek taşması vb.) nedeniyle çökerse, birisi fark edip sunucuya tekrar bağlanana ve komutu elle yeniden çalıştırana kadar site veya API çevrimdışı kalır. Üçüncüsü, sunucu yeniden başlatıldığında (planlı bakım, kernel güncellemesi, güç kesintisi) hiçbir otomatik başlatma mekanizması olmadığı için uygulama kendiliğinden ayağa kalkmaz.

Bu üç sorun da aslında aynı köke iniyor: işletim sistemi, o Node.js işlemini bir "servis" olarak tanımıyor. Linux'ta bu görevi üstlenen bileşen systemd'dir.

systemd servis yönetimi ne değiştirir

systemd, Debian, Ubuntu, CentOS/RHEL ve Fedora dahil çoğu modern Linux dağıtımında PID 1 olarak çalışan, yani sistemin ilk açılan ve tüm diğer süreçlerin yönetiminden sorumlu olan init sistemidir. Bir uygulamayı systemd'ye bir unit dosyasıyla tanıttığınızda, uygulamanın yaşam döngüsü artık işletim sisteminin sorumluluğuna geçer: sistem açılışında otomatik başlatma, beklenmeyen çökme durumunda yeniden başlatma, standart çıktı/hata akışlarının merkezi bir günlük sisteminde (journald) toplanması ve systemctl ile tek tip durum kontrolü.

Unit dosyasının üç bölümü

Bir .service unit dosyası üç bölümden oluşur ve genellikle /etc/systemd/system/ altına yazılır.

BölümGörevi
[Unit]Servisin açıklaması ve bağımlılıkları. After= ile hangi hedeften veya servisten sonra başlatılacağı belirtilir (örneğin network.target).
[Service]Servisin nasıl çalıştırılacağı: ExecStart ana komut, WorkingDirectory çalışma dizini, User hangi kullanıcı altında çalışacağı, Restart ve RestartSec yeniden başlatma politikası, Environment ortam değişkenleri.
[Install]Servisin systemctl enable ile hangi hedefe (genellikle multi-user.target) bağlanacağını, yani açılışta ne zaman devreye gireceğini tanımlar.

Type=simple, ExecStart ile başlatılan işlemin servisin ana işlemi olduğu anlamına gelir; Node.js gibi ön planda çalışan, kendini arka plana atmayan (fork/daemonize etmeyen) programlar için doğru seçimdir. Klasik Unix daemon'ları gibi kendini fork edip arkada bir alt işlem bırakan programlarda ise Type=forking kullanılır ve genellikle bir PIDFile belirtmek gerekir — ama bir Node.js uygulaması için bu neredeyse hiç gerekmez.

ExecStart, WorkingDirectory, User ve Restart directive'leri

ExecStart mutlak yollarla yazılmalıdır: /usr/bin/node /var/www/myapp/server.js gibi. Göreli yol veya sadece node server.js yazmak, systemd'nin komutu hangi dizinden çalıştıracağını bilmemesine yol açabilir. WorkingDirectory tam olarak bunu çözer: uygulamanın require/import çağrılarının ve node_modules aramasının başlayacağı dizini belirtir, dolayısıyla ExecStart ile WorkingDirectory tutarlı olmalıdır.

User, servisin hangi sistem kullanıcısı altında çalışacağını belirler. Uygulamayı root yerine sınırlı yetkilere sahip bir kullanıcı (örneğin www-data veya uygulamaya özel bir kullanıcı) ile çalıştırmak, bir güvenlik açığı istismar edildiğinde saldırganın elde edebileceği yetkiyi sınırlar.

Restart=on-failure ile RestartSec=5 birlikte kullanıldığında, servis çöktüğünde veya sıfırdan farklı bir çıkış koduyla sonlandığında systemd otomatik olarak yeniden başlatır, ama her deneme arasında 5 saniyelik bir bekleme koyar. Bu bekleme olmadan, sürekli çöken bozuk bir servis saniyede onlarca kez yeniden başlatılmaya çalışılıp CPU'yu tüketebilir (crash-loop); RestartSec bu döngüyü yavaşlatarak sistemi stabil tutar. Restart=always çıkış nedeninden bağımsız olarak (temiz kapatma dahil) her zaman yeniden başlatır, Restart=no ise otomatik yeniden başlatmayı tamamen kapatır. Ortam değişkenleri (NODE_ENV=production gibi) Environment="ANAHTAR=DEĞER" satırlarıyla, her değişken için ayrı bir satır olacak şekilde tanımlanır.

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

[Service]
Type=simple
User=www-data
WorkingDirectory=/var/www/myapp
ExecStart=/usr/bin/node /var/www/myapp/server.js
Restart=on-failure
RestartSec=5
Environment="NODE_ENV=production"

[Install]
WantedBy=multi-user.target

systemctl ile kurulum ve izleme iş akışı

Unit dosyası hazırlandıktan sonra izlenecek sıra sabittir:

  • Dosyayı /etc/systemd/system/myapp.service olarak kaydedin.
  • sudo systemctl daemon-reload — systemd'ye yeni veya değişen unit dosyalarını yeniden okumasını söyler.
  • sudo systemctl enable --now myapp — servisi hem açılışta otomatik başlayacak şekilde etkinleştirir hem de anında başlatır.
  • sudo systemctl status myapp — servisin çalışıp çalışmadığını, son birkaç log satırını ve çıkış kodunu gösterir.
  • journalctl -u myapp -f — servisin canlı loglarını izler; bu, uygulamanın standart çıktı ve standart hata akışlarını systemd'nin günlük yönetim sistemi journald üzerinden gösterir.

Sık yapılan hatalar

  • Yanlış WorkingDirectory: ExecStart'taki dosya yolu doğru olsa bile, WorkingDirectory uygulamanın kendi kök dizinini göstermiyorsa node_modules içindeki bağımlılıklar bulunamaz ve servis "Cannot find module" hatasıyla çöker.
  • Gereksiz yere root olarak çalıştırmak: User satırını boş bırakmak servisin root olarak çalışmasına yol açar; bu, en az yetki ilkesine aykırıdır ve bir güvenlik açığının etkisini büyütür.
  • Unit dosyasını düzenledikten sonra daemon-reload çalıştırmayı unutmak: systemd, dosya diskte değişmiş olsa bile hafızadaki eski tanımı kullanmaya devam eder; systemctl restart çalıştırsanız bile değişiklikler etkili olmaz.
  • Restart satırını hiç ayarlamamak: Restart directive'i olmadan varsayılan davranış no'dur; uygulama bir kez çöktüğünde servis kalıcı olarak "durdu" durumunda kalır ve kimse müdahale etmeden tekrar ayağa kalkmaz.
  • RestartSec olmadan agresif bir restart politikası kullanmak: Restart=always ile birlikte RestartSec belirtilmezse ve uygulama başlar başlamaz çöküyorsa, systemd onu saniyede defalarca yeniden başlatmayı dener; bu da sunucu kaynaklarını gereksiz yere tüketir.

Bu directive'lerin her biri tek başına küçük bir detay gibi görünse de, birlikte doğru ayarlandıklarında uygulamanızı terminale ve tek bir SSH oturumuna bağımlı olmaktan çıkarıp işletim sisteminin bir parçası haline getirirler.

Unit dosyasını sıfırdan elle yazmak yerine yukarıdaki alanları doldurup doğrudan kopyalayabileceğiniz bir çıktı almak isterseniz, bu değerleri formda girip aracı kullanabilirsiniz. Üretilen dosya, bu yazıda anlatılan üç bölümü ve directive'leri aynı mantıkla bir araya getirir; siz yalnızca kendi ExecStart yolunuzu, çalışma dizininizi ve kullanıcınızı belirtmeniz yeterlidir.

WhatsApp