Topik wawancara representatif

Wawancara Linux: Bagaimana Cara Mendiagnosis dan Memperbaiki Zombie Process?

UmumSulit
Tim Redaksi Offer.ccDipublikasikan Diperbarui

Pertanyaan

Sebuah layanan Linux berulang kali memulai helper process yang berumur pendek. Pekerjaan-pekerjaan baru sekarang gagal secara intermiten dengan `fork: Resource temporarily unavailable`; CPU dan RSS terlihat normal, sementara `ps` menunjukkan ribuan child dalam status `Z` di bawah satu parent dan `pids.current` milik container mendekati `pids.max`. Bagaimana Anda akan membuktikan penyebabnya, memulihkan layanan tanpa me-reboot jika memungkinkan, dan mencegah terulangnya kembali pada host atau di dalam container?

Pertanyaan dan Di Mana Ini Berlaku

Sebuah layanan Linux berulang kali memulai helper process yang berumur pendek. Pekerjaan-pekerjaan baru sekarang gagal secara intermiten dengan fork: Resource temporarily unavailable. CPU dan resident memory terlihat normal, tetapi ps menunjukkan ribuan child dalam status Z di bawah satu parent. Di dalam sebuah container, pids.current mendekati pids.max dan penghitung max di pids.events telah meningkat.

Jelaskan apa itu zombie, buktikan apakah child yang belum di-reap menyebabkan kegagalan tersebut, pulihkan layanan tanpa me-reboot jika memungkinkan, dan cegah terulangnya kembali. Cakup baik host normal maupun container yang aplikasinya mungkin berjalan sebagai PID 1. Jumlah dan konfigurasi deployment merupakan asumsi wawancara; jawaban yang kuat harus tetap menguji batas-batas lain yang dapat membuat fork() mengembalikan EAGAIN.

Ini adalah pertanyaan general karena keterampilan intinya adalah siklus hidup proses Linux dan diagnosis insiden. Panduan wawancara Linux publik saat ini mencakup pertanyaan zombie-versus-orphan, sementara dokumentasi Linux dan Docker menyediakan batas-batas operasionalnya. Hal ini mendukung pertanyaan yang representatif dan tahan lama tanpa menyiratkan instruksi atau frekuensi wawancara khusus perusahaan tertentu.

Apa yang Sedang Dinilai oleh Pewawancara

Sinyal pertama adalah apakah kandidat memisahkan status proses dari gejala sumber daya. Sebuah zombie telah selesai dihentikan (terminated). Kernel mempertahankan PID, status terminasi, dan informasi akuntansi hingga parent-nya mengumpulkannya dengan pemanggilan keluarga wait. Zombie tidak mengonsumsi CPU normal maupun memori user-space dari proses yang sudah keluar tersebut, tetapi masih menempati slot tabel proses/PID yang terbatas.

Sinyal kedua adalah kepemilikan (ownership). Komponen yang bermasalah biasanya adalah parent yang masih hidup yang memulai child tetapi gagal melakukan reap terhadapnya. Mengirim SIGKILL ke sebuah zombie tidak dapat membuatnya keluar lagi. Sebuah shell juga tidak dapat memanggil wait() untuk child dari proses yang tidak terkait. Kandidat harus mengidentifikasi parent, supervisor-nya, unit deployment-nya, dan kode manajemen child-nya sebelum mengambil tindakan.

Sinyal ketiga adalah diagnosis kausal. EAGAIN dari fork() dapat berarti batas RLIMIT_NPROC per-user, batas sistem threads-max, pid_max, atau batas cgroup pids.max. Melihat keberadaan zombie adalah bukti kuat, bukan izin untuk melewatkan pemeriksaan-pemeriksaan ini. Thread dan proses yang tidak terkait dapat berkontribusi pada batas maksimum yang sama.

Sinyal terakhir adalah perbaikan yang mampu bertahan terhadap lonjakan beban (bursts), kesalahan, shutdown, dan container. Satu pemanggilan waitpid() per SIGCHLD tidak cukup karena sinyal dapat digabungkan (coalesced). Parent harus menguras (drain) semua child yang telah keluar, dan sebuah container harus memiliki PID 1 yang mengadopsi dan me-reap keturunan yatim (orphaned descendants) dengan benar.

Pertanyaan untuk Diklarifikasi Sebelum Menjawab

  • Di mana kegagalan tersebut teramati? Pemadaman di seluruh host, satu akun pengguna, dan satu container mengarah pada batasan dan radius dampak (blast radii) yang berbeda.
  • Apa error dan syscall yang sebenarnya terjadi? EAGAIN mengindikasikan batas proses/thread; ENOMEM atau penolakan antrean aplikasi mengikuti jalur yang lain.
  • Apakah entri-entri Z terkonsentrasi di bawah satu PPID? Satu PPID yang dominan mengidentifikasi jalur kepemilikan. Banyak PPID dapat mengindikasikan wrapper bersama atau pola init-container yang rusak.
  • Apakah parent masih hidup, sehat, dan diawasi (supervised)? Parent yang hidup dapat diperbaiki atau di-restart dengan aman. Jika ia keluar, child akan dialihkan ke subreaper terdekat atau init namespace, yang kemudian harus me-reap mereka.
  • Apakah aplikasi berjalan sebagai PID 1 di dalam container? PID 1 memiliki tanggung jawab tambahan untuk me-reap orphan; runtime mungkin memerlukan proses init kecil.
  • Bisakah trafik atau asupan pekerjaan dikuras (drained)? Restart yang terkendali lebih aman setelah menghentikan fork baru dan menjaga pekerjaan yang sedang berlangsung (in-flight work).
  • Apa yang harus dipertahankan dari keluarnya child? Kode keluar (exit codes), output error, dan keputusan percobaan ulang (retry) menentukan apakah proses reaping harus berada dalam blocking call, event loop, worker pool, atau API proses spesifik runtime.

Kerangka Jawaban 30 Detik

“Zombie sudah mati; parent-nya belum mengambil status keluar. Saya akan menghitung status Z, mengelompokkannya berdasarkan PPID, memeriksa parent tersebut, dan mengorelasikan deret waktu dengan fork yang gagal. Saya juga akan memeriksa RLIMIT_NPROC, threads-max, pid_max, serta pids.current, pids.max, dan pids.events milik cgroup, karena EAGAIN memiliki beberapa kemungkinan batas. Saya tidak dapat memperbaiki zombie dengan kill -9; saya akan menghentikan spawning baru, menguras trafik, lalu me-restart atau memperbaiki parent dengan aman sehingga subreaper yang berfungsi atau PID 1 mengadopsi dan me-reap child tersebut. Secara permanen, parent harus menguras waitpid(-1, ..., WNOHANG) hingga tidak ada child yang telah keluar yang tersisa, termasuk pada jalur error dan shutdown. Di dalam container, saya juga akan menggunakan init yang tepat ketika aplikasi tidak dapat menjalankan tugas PID 1, lalu melakukan uji beban lonjakan dan memverifikasi bahwa jumlah zombie serta penggunaan PID tetap terbatas.”

Pembahasan Mendalam Langkah-demi-Langkah

Langkah 1: Tetapkan Status Proses dan Pemiliknya

Mulailah dengan perintah-perintah yang tidak membuat pipeline besar saat ruang PID yang tersisa sudah langka:

bash
ps -eo pid=,ppid=,stat=,etime=,comm= | awk '$3 ~ /^Z/'
ps -eo ppid=,stat= | awk '$2 ~ /^Z/ { count[$1]++ } END { for (p in count) print count[p], p }' | sort -nr

Dalam ps, status yang diawali dengan Z adalah zombie. /proc/PID/stat juga mengekspos status Z dan PPID-nya. Konfirmasikan PPID dengan lebih dari sekadar nama proses yang terpotong, lalu periksa parent yang masih hidup:

bash
ps -o pid=,ppid=,stat=,lstart=,etime=,cmd= -p PARENT_PID
cat /proc/PARENT_PID/status
cat /proc/PARENT_PID/limits

Catat jumlah zombie, laju zombie baru, versi deployment parent, dan waktu kegagalan pertama. Jumlah stabil yang tertinggal sebentar selama shutdown terkendali berbeda dari jumlah yang terus meningkat pada setiap pekerjaan.

Langkah 2: Buktikan Batas Mana yang Menolak Child Baru

Jangan menyimpulkan “kehabisan memori” dari frasa yang dilihat pengguna “Resource temporarily unavailable.” Linux mendokumentasikan beberapa jalur fork() yang mengembalikan EAGAIN:

  • RLIMIT_NPROC milik pengguna sebenarnya;
  • /proc/sys/kernel/threads-max;
  • /proc/sys/kernel/pid_max;
  • batas PIDs cgroup yang efektif.

Untuk cgroup v2, periksa file-file di dalam cgroup aktual layanan alih-alih mengasumsikan jalur root:

bash
cat /proc/PARENT_PID/cgroup
cat /sys/fs/cgroup/SERVICE_CGROUP/pids.current
cat /sys/fs/cgroup/SERVICE_CGROUP/pids.max
cat /sys/fs/cgroup/SERVICE_CGROUP/pids.events

Penghitung peristiwa max yang meningkat secara langsung membuktikan bahwa fork mencapai batas atas pengontrol PIDs. Bandingkan pids.current dengan jumlah zombie dan jumlah thread/proses yang hidup; pengontrol menghitung tugas di seluruh keturunan (descendants), sehingga zombie mungkin bukan satu-satunya konsumen. Pada sebuah host, bandingkan juga jumlah tugas per-user dan total sistem dengan batasnya masing-masing. Rantai kausal paling kuat terjadi ketika pertumbuhan zombie, sisa ruang PID, EAGAIN, dan laju spawn parent sejajar dalam waktu.

Langkah 3: Jelaskan Mengapa “Perbaikan” yang Umum Dilakukan Gagal

kill -9 ZOMBIE_PID tidak dapat menjalankan logika keluar karena proses tersebut telah keluar. Catatan kernel yang tersisa hanya akan hilang ketika parent-nya, atau adopter berikutnya, menunggu (waits) untuknya. Built-in wait milik shell hanya mengelola child dari shell itu sendiri.

Menaikkan pids.max, pid_max, atau RLIMIT_NPROC secara membabi buta mungkin memberikan waktu pemulihan, tetapi membiarkan kebocoran tetap aktif dan dapat memperbesar radius dampak berikutnya. Membunuh proses hidup secara acak membebaskan slot tetapi tidak memperbaiki parent. Me-reboot berhasil dengan cara menghancurkan seluruh pohon proses, tetapi menghilangkan bukti dan menciptakan downtime yang seharusnya dapat dihindari.

Bedakan juga status D: proses uninterruptible sleeping masih hidup dan sedang menunggu di dalam kernel. Status ini memiliki diagnosis yang berbeda meskipun SIGKILL tampak tidak efektif. Memperlakukan setiap PID yang membandel sebagai zombie akan membawa investigasi ke arah yang salah.

Langkah 4: Pulihkan Layanan dengan Transisi Parent yang Terkendali

Pertama, hentikan atau batasi (throttle) asupan pekerjaan baru agar parent yang bermasalah tidak menghabiskan slot yang tersisa. Pertahankan satu sesi administratif dan kumpulkan log parent, bukti /proc, batas-batas, serta informasi versi sebelum mengubah status.

Jika parent memiliki reload terdokumentasi yang membuat ulang loop manajemen child-nya dengan aman, gunakan itu. Jika tidak, kuras pekerjaan yang sedang berjalan dan restart parent tersebut melalui supervisor-nya. Ketika parent keluar, keturunan yang belum di-reap akan diadopsi oleh subreaper child terdekat atau init PID-namespace; adopter yang benar akan menunggu mereka. Verifikasi bahwa jumlah zombie menurun sebelum membuka kembali asupan pekerjaan.

Jika adopter tidak me-reap mereka, hanya me-restart worker tidak akan menyelesaikan pemulihan. Pada sebuah host, periksa supervisor layanan/subreaper. Di dalam container, penggantian container yang terkendali mungkin diperlukan karena namespace PID 1 memegang tanggung jawab akhir. Menaikkan batas PID hanya dapat diterima sebagai langkah cadangan sementara yang terdokumentasi, dipadukan dengan throttling dan deployment yang telah diperbaiki.

Langkah 5: Terapkan Reaping yang Menangani Bursts dan Error

Untuk parent yang harus memblokir hingga child tertentu selesai, panggil waitpid(child_pid, ...) dan tangani interupsi. Untuk parent asinkron, atur agar SIGCHLD membangunkan event loop, lalu kuras setiap status yang tersedia:

c
for (;;) {
  pid_t pid = waitpid(-1, &status, WNOHANG);
  if (pid > 0) {
    record_child_result(pid, status);
    continue;
  }
  if (pid == 0) break;
  if (errno == EINTR) continue;
  if (errno == ECHILD) break;
  report_wait_error(errno);
  break;
}

Loop ini berada dalam konteks event-loop normal; signal handler mentah hanya boleh melakukan operasi yang diizinkan oleh aturan signal-safety runtime, sering kali hanya menyetel flag atau menulis ke self-pipe. Menguras (draining) itu penting karena beberapa child exit dapat menghasilkan satu notifikasi yang teramati. Cakup kegagalan antara spawn dan registrasi, timeout, pembatalan, shutdown parent, dan setiap jalur percobaan ulang (retry). Dalam runtime terkelola, aturan yang setara adalah tetap menunggu (await) atau mengonsumsi setiap penyelesaian child-process.

Secara eksplisit mengabaikan SIGCHLD atau menggunakan SA_NOCLDWAIT dapat meminta pembersihan otomatis pada sistem yang mendukung, tetapi ini juga menghilangkan pengumpulan status keluar normal dan memiliki konsekuensi portabilitas/API. Ini adalah pilihan desain yang disengaja, bukan jalan pintas untuk worker manager yang membutuhkan hasil.

Langkah 6: Jadikan PID 1 Container dan Verifikasi sebagai Bagian dari Perbaikan

Proses utama container bertanggung jawab atas proses-proses yang dimulainya dan dapat menjadi adopter bagi keturunannya. Jika aplikasi tidak dapat meneruskan sinyal dan me-reap dengan benar sebagai PID 1, jalankan di bawah fasilitas init kecil dari runtime, seperti --init milik Docker atau pengaturan Compose yang setara. Proses init tidak membebaskan aplikasi dari keharusan menunggu child langsungnya yang hasilnya menjadi milik aplikasi; init menutup celah adopsi orphan.

Verifikasi perbaikan dengan lonjakan (burst) yang lebih besar dari konkurensi awal, ditambah kegagalan child dan keluarnya proses secara cepat. Kondisi penerimaan harus mencakup:

  1. setiap child yang dimulai menghasilkan satu status penyelesaian yang dikonsumsi;
  2. jumlah zombie kembali ke nol atau batas singkat yang terdokumentasi setelah setiap burst;
  3. pids.current stabil alih-alih naik terus dan pids.events tidak mencatat batas yang terbentur baru;
  4. tidak ada fork()/spawn EAGAIN yang terjadi pada beban yang diharapkan;
  5. shutdown menguras atau menghentikan child lalu me-reap mereka;
  6. crash pada parent menyerahkan keturunan ke subreaper atau PID 1 yang telah teruji;
  7. alert terpicu pada laju pertumbuhan zombie dan sisa ruang PID sebelum pembuatan pekerjaan gagal.

Contoh Jawaban Berkualitas Tinggi

“Pertama-tama, saya akan memverifikasi bahwa entri-entri tersebut benar-benar diawali dengan status Z, mengelompokkannya berdasarkan PPID, dan memeriksa parent hidup yang dominan. Zombie telah selesai dieksekusi, sehingga CPU dan RSS normal memang sudah diperkirakan. Kernel menyimpan PID dan status keluarnya sampai parent memanggil fungsi keluarga wait. Itulah mengapa kill -9 pada zombie tidak efektif dan mengapa parent adalah target perbaikannya.

“Saya kemudian akan membuktikan batas fork yang gagal. Untuk parent, saya akan memeriksa RLIMIT_NPROC; pada host saya akan memeriksa threads-max dan pid_max; di cgroup layanan saya akan membaca pids.current, pids.max, dan pids.events. Jika penghitung max meningkat pada saat yang sama dengan terjadinya error dan grup berada di dekat batas atasnya, itu membuktikan bagian cgroup. Saya akan tetap menghitung thread yang hidup dan tugas keturunan agar saya tidak mengaitkan setiap slot dengan zombie semata.

“Untuk pemulihan, saya akan membatasi pekerjaan baru, menyimpan diagnostik, menguras pekerjaan, dan me-restart atau me-reload parent melalui supervisor-nya. Zombie-nya harus diadopsi dan di-reap oleh subreaper yang berfungsi atau namespace PID 1. Jika PID 1 container adalah adopter yang bermasalah, saya akan mengganti container dengan konfigurasi yang mengaktifkan init. Menaikkan batas hanyalah ruang cadangan sementara.

“Perbaikan kode permanen adalah mengonsumsi setiap hasil child. Pemilik yang sinkron menunggu PID yang tepat. Pemilik yang berbasis event memperlakukan SIGCHLD sebagai pemicu bangun dan melakukan loop dengan waitpid() non-blocking hingga tidak ada child yang selesai tersisa, sambil menangani kegagalan spawn, pembatalan, dan shutdown. Saya akan melakukan uji beban terhadap rapid exit, kegagalan paksa, crash parent, dan graceful shutdown. Rilis dinyatakan lulus hanya jika penghitungan penyelesaian satu-banding-satu terpenuhi, zombie tetap terbatas, penggunaan PID stabil, dan tidak ada peristiwa batas baru yang terjadi.”

Kesalahan Umum

  • Mengirim kill -9 ke setiap zombie → child telah selesai, sehingga sinyal tidak dapat mengumpulkan status keluarnya → identifikasi dan perbaiki, reload, atau restart parent/adopter.
  • Menyebutnya sebagai kebocoran memori (memory leak) → zombie mempertahankan pencatatan kernel alih-alih ruang alamat normal dari proses yang telah keluar → jelaskan sebagai kehabisan tabel proses/PID dan ukur sumber daya pembatas yang sebenarnya.
  • Mengasumsikan kehabisan cgroup dari satu snapshot ps saja → EAGAIN memiliki beberapa batasan dan thread yang hidup juga mengonsumsi kapasitas tugas → korelasikan batasan, penghitung, jumlah tugas, dan timestamp.
  • Memanggil waitpid() sekali per sinyal → sinyal child-exit dapat digabungkan, meninggalkan status tambahan yang belum di-reap → kuras non-blocking wait hingga pemanggilan melaporkan tidak ada yang siap.
  • Menaikkan pids.max sebagai perbaikan permanen → parent yang bermasalah terus membocorkan slot → gunakan ruang cadangan ekstra hanya untuk pemulihan, dengan throttling dan deployment perbaikan yang terjadwal.
  • Menambahkan proses init tetapi mengabaikan child langsung → aplikasi tetap memiliki hasil child langsung dan semantik retry → tunggu child langsung; gunakan init untuk menangani tugas PID 1 dan keturunan yang diadopsi.
  • Me-restart sebelum mengumpulkan bukti → insiden hilang tanpa membuktikan batas atau jalur kode mana yang gagal → catat PPID, batasan, penghitung, versi, laju, dan log terlebih dahulu jika sisa ruang memungkinkan.

Pertanyaan Lanjutan dan Tanggapannya

Bagaimana jika parent tidak dapat di-restart selama jam kerja?

Hentikan jalur yang bocor terlebih dahulu: nonaktifkan jenis pekerjaan tersebut, kurangi konkurensi, atau rutekan pekerjaan ke replika yang sehat. Jika kebijakan mengizinkan, naikkan hanya batas PIDs efektif yang cukup untuk mempertahankan kapasitas administratif dan health-check. Pantau kemiringan (slope) alih-alih jumlahnya saja dan hitung batas waktu kehabisan secara konservatif. Parent yang hidup dapat me-reap child-nya sendiri hanya jika ia mengekspos atau dapat menerima tindakan perbaikan yang didukung; proses eksternal tidak dapat menunggu mereka. Jadwalkan transisi parent yang terkendali sebelum ruang cadangan sementara habis.

Bagaimana jika zombie hanya muncul di dalam container?

Masuk ke tampilan namespace PID dan identifikasi namespace PID 1 beserta PPID dari zombie-zombie tersebut. Konfirmasikan jalur cgroup layanan dan penghitung PIDs dari host atau orchestrator. Jika aplikasi adalah PID 1 dan tidak menerapkan reaping/penerusan sinyal, deploy proses init kecil. Jika sebuah wrapper adalah PID 1, verifikasi bahwa ia tidak keluar lebih awal atau hanya menunggu satu child. Uji penghentian container sehingga sinyal mencapai aplikasi, child keluar, dan semua status terkumpul sebelum masa tenggang (grace period) berakhir.

Mengapa satu pemanggilan handler SIGCHLD bukan berarti satu child exit?

Sinyal tradisional adalah notifikasi, bukan antrean tahan lama dari satu event per child. Beberapa child dapat keluar sebelum proses menangani sinyal tersebut, dan notifikasi dapat digabungkan. Kontrak yang tangguh adalah “sebuah notifikasi berarti periksa status child,” diikuti oleh non-blocking wait berulang hingga tidak ada child yang selesai tersisa. Aplikasi mencatat setiap PID yang dikembalikan tepat satu kali.

Apakah menyetel SIGCHLD ke SIG_IGN akan menyelesaikan masalah?

Di Linux, secara eksplisit mengabaikan SIGCHLD atau menyetel SA_NOCLDWAIT mengubah perilaku zombie, tetapi aplikasi kemudian tidak dapat mengandalkan pengumpulan status keluar biasa melalui wait(). Disposisi default yang digambarkan sebagai “ignore” tidak sama dengan secara eksplisit memasang SIG_IGN. Gunakan mode ini hanya ketika hasil child benar-benar tidak memiliki nilai bisnis dan kontrak bahasa/runtime telah diverifikasi; worker manager biasanya membutuhkan akuntansi penyelesaian yang eksplisit.

Bagaimana Anda akan membuat alert sebelum pengguna melihat kegagalan fork?

Buat alert pada laju pertumbuhan zombie positif yang berkelanjutan yang dikelompokkan berdasarkan parent, sisa ruang PID cgroup yang rendah, peningkatan dalam pids.events:max, dan kegagalan spawn. Pasangkan sinyal-sinyal tersebut dengan throughput pekerjaan sehingga lonjakan singkat yang sah tidak memicu halaman peringatan (page) hanya berdasarkan jumlah semata. Cadangkan ruang yang cukup untuk supervisor, telemetri, dan perintah pemulihan, lalu uji alert tersebut terhadap kebocoran terkendali di namespace non-produksi.

Sumber publik

Pertanyaan terkait