Systemd & Service Management — DevOps

Apa itu Systemd? Systemd adalah init system default di hampir semua distro Linux modern (Ubuntu, Debian, RHEL, CentOS). Tugasnya: boot system, start service…

systemd adalah program pertama yang jalan saat server Linux menyala, dan dia yang menyalakan, menjaga, dan mencatat semua service, program yang harus hidup terus seperti catatan-api. Tiap service punya unit file, mirip kartu SOP seorang karyawan: siapa yang bekerja (User=), bekalnya apa (EnvironmentFile=), apa yang dilakukan kalau dia pingsan (Restart=), dan apakah dia masuk setiap pagi (enable). Dasar systemctl, daemon-reload, dan journalctl ada di pelajaran systemd & Services, track Terminal. Di sini kita menulis unit production untuk catatan-api di web-1.

🧪 Tebakan cepat: kamu menjalankan sudo systemctl enable catatan-api, lalu systemctl is-active catatan-api. Apa yang dicetak, active atau inactive? Tebak dulu, jawabannya ada di bagian "enable dan start: dua hal berbeda".

Unit file catatan-api

# /etc/systemd/system/catatan-api.service
[Unit]
Description=catatan-api
After=network.target postgresql.service
Requires=postgresql.service

[Service]
User=www-data
WorkingDirectory=/srv/catatan-api
ExecStart=/usr/bin/node app.js
Environment=NODE_ENV=production
Environment=PORT=3000
EnvironmentFile=/etc/catatan-api.env
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

After= dan Requires=

Baris Artinya Kalau PostgreSQL dihentikan
After=postgresql.service cuma urutan: catatan-api dinyalakan setelah PostgreSQL catatan-api tetap jalan, query-nya gagal
Requires=postgresql.service ketergantungan keras: PostgreSQL ikut dinyalakan catatan-api ikut dihentikan

After= hanya mengatur urutan start. Requires= juga membuat service berhenti kalau dependensinya gagal atau dihentikan. Biasanya ditulis berpasangan, seperti di unit catatan-api. Versi lunaknya Wants=: ikut dinyalakan, tapi gak ikut mematikan.

Restart=: on-failure atau always

Restart= Exit 0 (keluar normal) Exit selain 0 (crash) systemctl stop
no (bawaan) diam diam diam
on-failure diam restart diam
always restart restart diam

Untuk server HTTP seperti catatan-api, on-failure paling pas: crash dibangkitkan, keluar normal dibiarkan. always cocok untuk worker yang memang sengaja keluar dengan exit 0 dan harus jalan lagi. systemctl stop gak pernah memicu restart. Ini journal saat PostgreSQL sempat mati dan app crash:

Oct 09 09:01:12 web-1 node[5120]: Database gagal tersambung: connect ECONNREFUSED 127.0.0.1:5432
Oct 09 09:01:12 web-1 systemd[1]: catatan-api.service: Main process exited, code=exited, status=1/FAILURE
Oct 09 09:01:12 web-1 systemd[1]: catatan-api.service: Failed with result 'exit-code'.
Oct 09 09:01:17 web-1 systemd[1]: catatan-api.service: Scheduled restart job, restart counter is at 1.
Oct 09 09:01:17 web-1 systemd[1]: Started catatan-api.service - catatan-api.
Oct 09 09:01:17 web-1 node[5207]: Server jalan di http://localhost:3000

Restart gak membetulkan config. Kalau app gagal karena secret-nya kosong, dia gagal lagi setiap lima detik, dan restart counter naik terus.

enable dan start: dua hal berbeda

start menyalakan sekarang. enable membuat symlink di multi-user.target.wants supaya service ikut nyala setiap boot, tanpa menyalakannya sekarang:

deploy@web-1:~$ sudo systemctl enable catatan-api
Created symlink /etc/systemd/system/multi-user.target.wants/catatan-api.service → /etc/systemd/system/catatan-api.service.
deploy@web-1:~$ systemctl is-active catatan-api; echo $?
inactive
3
deploy@web-1:~$ sudo systemctl start catatan-api
deploy@web-1:~$ systemctl is-active catatan-api; echo $?
active
0

Membaca status dan journal

deploy@web-1:~$ systemctl status catatan-api
● catatan-api.service - catatan-api
     Loaded: loaded (/etc/systemd/system/catatan-api.service; enabled; preset: enabled)
     Active: active (running) since Fri 2026-10-09 09:00:09 WIB; 3s ago
   Main PID: 5120 (node)
      Tasks: 7 (limit: 2274)
     Memory: 41.6M (peak: 42.0M)
        CPU: 389ms
     CGroup: /system.slice/catatan-api.service
             └─5120 /usr/bin/node app.js

Oct 09 09:00:09 web-1 systemd[1]: Started catatan-api.service - catatan-api.
Oct 09 09:00:09 web-1 node[5120]: Server jalan di http://localhost:3000
deploy@web-1:~$ journalctl -u catatan-api -n 4
Oct 09 09:00:03 web-1 systemd[1]: Started catatan-api.service - catatan-api.
Oct 09 09:00:03 web-1 node[5120]: Config error: DATABASE_URL, JWT_SECRET belum diset. Salin .env.example jadi .env, lalu isi.
Oct 09 09:00:03 web-1 systemd[1]: catatan-api.service: Main process exited, code=exited, status=1/FAILURE
Oct 09 09:00:03 web-1 systemd[1]: catatan-api.service: Failed with result 'exit-code'.

journalctl -u catatan-api -f mengikuti log secara live, dan -n 50 mengambil 50 baris terakhir.

Di latihan: membuat unit file dengan JavaScript

generateUnitFile(config) menyusun teks unit file dari object. Tiap section adalah baris-baris yang digabung dengan "\n", dan antar section dipisah satu baris kosong ("\n\n"). [Service] di latihan menulis Type=simple secara eksplisit: itu tipe bawaan untuk service dengan ExecStart=, artinya proses di ExecStart adalah service-nya. Kalau array after kosong, baris After= gak ditulis sama sekali.

⚠️ Jebakan yang sering ketemu

TL;DR: Unit production catatan-api: [Unit] dengan After= (urutan start saja) dan Requires= (ikut berhenti kalau dependensinya berhenti), [Service] dengan User=www-data, ExecStart= berpath lengkap, EnvironmentFile=/etc/catatan-api.env (dibaca systemd sebagai root), Restart=on-failure (crash dibangkitkan, exit 0 dan stop dibiarkan; always juga membangkitkan exit 0) dan RestartSec=5, lalu [Install] WantedBy=multi-user.target agar ikut nyala saat boot. start = sekarang, enable = setiap boot, enable --now = dua-duanya. systemctl is-active keluar dengan 0 kalau jalan (pakai --quiet di script), status menampilkan keadaan dan log terakhir, journalctl -u catatan-api menunjukkan kenapa app gagal.

Yang akan kamu pelajari