Keluhan insiden service jarang datang dalam bentuk kalimat yang rapi — yang sampai ke kita biasanya hanya “situsnya mati” atau “aplikasinya tidak bisa dibuka”. Padahal di dalam sistem, systemd telah merekam hampir semua kejadian: kapan service mencoba hidup, mengapa ia mati, dan siapa yang memicunya. Chapter ini melatih alur triase dari gejala di luar menuju bukti di dalam: membaca status, menambatkan jurnal pada baris penyebab, membedakan kegagalan konfigurasi dari bentrok port, lalu memverifikasi pemulihan dari sisi klien — bukan sekadar berharap restart menyelesaikan semuanya.
Alur Triase: Dari Gejala ke Bukti #
Ketika sebuah service dilaporkan mati, kita memeriksa lima hal secara berurutan — dari yang paling murah ke yang paling dalam:
- Tajamkan gejalanya lebih dulu. “Mati” itu kabar buruk yang kabur: apakah
curlke port-nya ditolak, halaman balik error 500, atau hanya satu user yang bermasalah? Gejala yang tidak terukur tidak bisa diverifikasi perbaikannya. - Baca keadaan dengan
systemctl status nama.service— terutama barisActivedan kode hasilnya. - Baca jurnal dengan
journalctl -u nama.service -e— opsi-emelompat ke ujung, karena pesan pemberani hampir selalu ada di baris-baris terakhir. Teknik menyaring jurnal lengkapnya sudah kita bahas di Monitoring Logs . - Periksa tiga penyebab klasik pasca-edit: file konfigurasi yang salah isi, port yang bentrok dengan proses lain, dan izin file yang tidak cocok dengan
User=unit. - Perbaiki satu hal, restart, lalu ukur ulang dari luar dengan
curl— kalau gejala awalnya “curl ditolak”, bukti pulihnya juga harus “curl berhasil”.
Dua keadaan di baris Active perlu dibedakan sejak awal. inactive (dead) berarti service memang tidak diminta jalan — bisa jadi unitnya lupa di-enable, atau dependency-nya yang lebih dulu gagal. failed berarti service mencoba hidup lalu mati oleh error — di sinilah jurnal menjadi saksi kunci. Daftar lengkap lima keadaan beserta contoh unit yang salah tulis sudah dibedah di Troubleshooting Services
; fokus kita sekarang adalah membaca jurnal korban dan memutuskan tindakan dari isinya.
Membaca Jurnal Sang Korban #
Kita simulasikan webapp.service yang mati setiap kali dihidupkan. Berikut potongan jurnalnya — persis yang ditampilkan journalctl -u webapp.service -e — yang kita simpan sebagai fixture unit.log agar bisa disaring berulang:
cat > unit.log <<'AKHIR'
Jan 15 08:41:02 server-kantor systemd[1]: Starting webapp.service...
Jan 15 08:41:02 server-kantor webapp[4712]: memuat konfigurasi /etc/webapp/config.json
Jan 15 08:41:02 server-kantor webapp[4712]: fatal: /etc/webapp/config.json: Permission denied
Jan 15 08:41:02 server-kantor systemd[1]: webapp.service: Main process exited, code=exited, status=1/FAILURE
Jan 15 08:41:02 server-kantor systemd[1]: webapp.service: Failed with result 'exit-code'.
Jan 15 08:41:12 server-kantor systemd[1]: webapp.service: Scheduled restart job, restart counter is at 1.
AKHIR
grep -E 'fatal|FAILURE|Failed with result' unit.logOutput yang Dihasilkan:
Jan 15 08:41:02 server-kantor webapp[4712]: fatal: /etc/webapp/config.json: Permission denied
Jan 15 08:41:02 server-kantor systemd[1]: webapp.service: Main process exited, code=exited, status=1/FAILURE
Jan 15 08:41:02 server-kantor systemd[1]: webapp.service: Failed with result 'exit-code'.Tiga baris hasil saringan itu membentuk satu cerita sebab-akibat. Baris fatal berasal dari aplikasi sendiri — inilah penyebabnya: file konfigurasi tidak bisa dibaca. Dua baris berikutnya adalah konsekuensi yang dicatat systemd: proses utama keluar dengan status=1, lalu unit ditandai gagal. Baris terakhir memperlihatkan restart counter is at 1 — Restart=on-failure sedang mencoba lagi, dan jika terus gagal, hitungan ini yang pada akhirnya menyentuh batas StartLimitBurst. Aturan bacanya sederhana: cari baris error dari aplikasi lebih dulu; baris-baris systemd hanyalah berita susulannya.
Skrip Triase Pesan Error Umum #
Pola di atas bisa dibungkus menjadi skrip triase mini: saring baris mencurigakan, lalu petakan pesan ke tersangka dan tindakan pertamanya. Blok berikut menulis ulang fixture unit.log lebih dulu supaya tetap berdiri sendiri:
cat > unit.log <<'AKHIR'
Jan 15 08:41:02 server-kantor systemd[1]: Starting webapp.service...
Jan 15 08:41:02 server-kantor webapp[4712]: memuat konfigurasi /etc/webapp/config.json
Jan 15 08:41:02 server-kantor webapp[4712]: fatal: /etc/webapp/config.json: Permission denied
Jan 15 08:41:02 server-kantor systemd[1]: webapp.service: Main process exited, code=exited, status=1/FAILURE
Jan 15 08:41:02 server-kantor systemd[1]: webapp.service: Failed with result 'exit-code'.
Jan 15 08:41:12 server-kantor systemd[1]: webapp.service: Scheduled restart job, restart counter is at 1.
AKHIR
cat > triase.sh <<'AKHIR'
#!/bin/bash
# Triase jurnal service: saring error, usul tersangka.
grep -i 'fatal\|error\|failed' unit.log | tail -n 3
echo "---"
if grep -q 'Permission denied' unit.log; then
echo "tersangka : izin file — cocokkan User= unit dengan ls -l file config"
elif grep -q 'Address already in use' unit.log; then
echo "tersangka : port bentrok — periksa dengan ss -tlnp"
elif grep -q 'No such file or directory' unit.log; then
echo "tersangka : path salah — cocokkan ExecStart dengan isi disk"
else
echo "tersangka : belum jelas — baca konteks penuh di journalctl -e"
fi
AKHIR
bash triase.shOutput yang Dihasilkan:
Jan 15 08:41:02 server-kantor webapp[4712]: fatal: /etc/webapp/config.json: Permission denied
Jan 15 08:41:02 server-kantor systemd[1]: webapp.service: Failed with result 'exit-code'.
---
tersangka : izin file — cocokkan User= unit dengan ls -l file configMari kita bedah skrip ini baris-per-baris:
- Baris 3 (
grep -i ... | tail -n 3): saringan mengambil semua baris berisi fatal, error, atau failed tanpa peduli besar-kecil huruf;tail -n 3memangkas ke tiga terakhir agar layar tidak dibanjiri crash berulang. - Baris 5 (
if grep -q 'Permission denied' ...): rantai keputusan memeriksa pesan paling khas lebih dulu;grep -qtidak mencetak apa pun — ia hanya mengembalikan exit code untuk diujiif. - Baris 12-13 (
else…): cabut penutup untuk pesan yang belum dikenali — skrip yang jujur mengaku butuh data lebih banyak lebih berguna daripada skrip yang menebak nekat.
Untuk kasus Permission denied, langkah lanjutnya berlanjut ke luar jurnal: ls -l /etc/webapp/config.json menunjukkan pemilik dan izinnya, systemctl cat webapp.service menunjukkan User= yang dipakai — dan kalau keduanya tidak bersinggungan, itulah akar masalahnya.
Insiden Mini: Service Mati, Terverifikasi dari Luar #
Alur triase menuntut verifikasi dari luar, dan pola itu bisa dilatih dengan server lokal sebagai korban: python3 -m http.server kita perlakukan sebagai “service”, curl sebagai klien — alamat tujuannya ditulis tanpa skema karena curl menganggap alamat tanpa skema sebagai HTTP biasa. Blok berikut memainkan satu siklus insiden utuh — service hidup, service kedua bentrok port, service utama mati, lalu hidup kembali:
python3 -m http.server 8447 --bind 127.0.0.1 >/dev/null 2>&1 &
server=$!
sleep 1
curl -s -o /dev/null -w 'sebelum insiden : HTTP %{http_code}\n' 127.0.0.1:8447/
python3 -m http.server 8447 --bind 127.0.0.1 2>&1 | tail -n 1
kill "$server"; wait "$server" 2>/dev/null
sleep 1
curl -sS 127.0.0.1:8447/ > berkas-kesalahan 2>&1
status=$?
sed 's/after [0-9]* ms/after N ms/' berkas-kesalahan
echo "curl keluar dengan exit code $status"
python3 -m http.server 8447 --bind 127.0.0.1 >/dev/null 2>&1 &
server=$!
sleep 1
curl -s -o /dev/null -w 'setelah restart : HTTP %{http_code}\n' 127.0.0.1:8447/
kill "$server"; wait "$server" 2>/dev/null
sleep 1
ss -tln | awk '$4 ~ /:8447$/ {n++} END {print "pendengar tersisa di port 8447 :", n+0}'Output yang Dihasilkan:
sebelum insiden : HTTP 200
OSError: [Errno 98] Address already in use
curl: (7) Failed to connect to 127.0.0.1 port 8447 after N ms: Could not connect to server
curl keluar dengan exit code 7
setelah restart : HTTP 200
pendengar tersisa di port 8447 : 0Kita bedahkan peristiwa-peristiwa pentingnya:
- Baris 5 (
curl -s -o /dev/null -w ...): pengukuran awal — baseline. Gejala “mati” kini punya definisi terbaliknya:HTTP 200. - Baris 6 (
python3 -m http.server 8447 ... | tail -n 1): service kedua gagal jalan karena port sudah dipakai — pesanAddress already in useinilah wujudOSErroryang sama yang akan muncul di jurnal service nyata; di lapangan kita mencarinya denganss -tlnplalu memilih: usir proses lama, atau ganti port di konfigurasi. - Baris 8-11 (
killlalucurl -sS): proses utama dibunuh — di produksi bisa jadi OOM killer — dancurlkini ditolak. Exit code 7 berarti connection refused: tidak ada yang mendengarkan; exit code 28 berarti timeout — keduanya penyakit berbeda yang tampak sama dari luar. - Baris 12 (
sed 's/after [0-9]* ms/after N ms/'): pesan galatcurlmencantumkan durasi dalam milidetik yang berbeda-beda di setiap mesin;seddi sini membekukannya agar output materi selalu sama — angka persisnya tidak penting untuk diagnosis. - Baris 14-17 (start ulang lalu ukur lagi): pemulihan hanya sah bila pengukuran yang sama dengan baseline kini kembali
HTTP 200— dan baris terakhir membuktikan tidak ada proses latihan yang tertinggal mendengarkan port.
Pola Penyakit Umum #
Di lapangan, kegagalan service berulang dalam pola yang sama. Tabel ini memetakan gejala di status dan jurnal menuju penyebab serta tindakan pertamanya:
| Gejala | Penyebab paling umum | Tindakan pertama |
|---|---|---|
inactive (dead) padahal layanan dibutuhkan | unit tidak di-enable, atau dependency gagal | systemctl is-enabled; systemctl list-dependencies |
failed + Permission denied di jurnal | User= unit tak berhak ke file/port | ls -l file terkait; sesuaikan pemilik atau User= |
failed + Address already in use | port dipakai proses lain | ss -tlnp; usir proses lama atau ganti port |
failed + No such file or directory | path di ExecStart atau config salah | cocokkan path; uji config sebelum restart |
start request repeated too quickly | crash loop menyentuh StartLimitBurst | systemctl reset-failed, lalu perbaiki akarnya |
| gagal hanya di satu user | systemd user service yang bermasalah | systemctl --user status nama |
Dua baris terakhir layak dijelaskan. reset-failed hanya menghapus status gagal beserta hitungan restart supaya systemd mau mencoba lagi — ia membuka pintu, bukan memperbaiki mesinnya; tanpa perbaikan di akar, crash loop akan kembali menghantam batas yang sama. Untuk baris terakhir: service yang dinyalakan per-user (systemctl --user) punya jurnal sendiri, dan diagnosa sering keliru karena kita memeriksa systemctl system-wide padahal yang rusak ada di sisi user.
Validasi konfigurasi sebelum restart adalah kebiasaan yang menghemat malam minggu kita: nginx -t dan apachectl configtest menguji sintaks, sshd -t menguji file sshd_config, bash -n menguji skrip ExecStart tanpa menjalankannya — semua murah, semua instan. Pengelolaan service yang lebih dalam — menulis unit, mengatur dependency antar unit, target boot — adalah wilayah Managing Services
dan Service Dependencies
. Namun service yang sudah hidup pun belum tentu sehat: kadang ia justru hidup terlalu serak — menggerus CPU dan memori sampai mesin sekelilingnya ikut terseret. Cara menangkap proses rakus semacam itu adalah materi chapter berikutnya.
Referensi resminya: opsi-opsi status, reset-failed, dan is-enabled di systemctl(1)
, penyaringan per-unit dengan -u dan -e di journalctl(1)
, serta pembacaan socket dengan ss di ss(8)
.