Topik temu duga representatif

Temu Duga Linux: Bagaimana Anda Mendiagnosis dan Membetulkan Proses Zombi?

UmumSukar
Pasukan Editorial Offer.ccDiterbitkan Dikemas kini

Soalan

Satu perkhidmatan Linux berulang kali memulakan proses pembantu (helper) jangka pendek. Kerja-kerja baharu kini gagal secara berselang-seli dengan `fork: Resource temporarily unavailable`; CPU dan RSS kelihatan normal, manakala `ps` menunjukkan ribuan anak dalam keadaan `Z` di bawah satu induk dan `pids.current` kontena menghampiri `pids.max`. Bagaimanakah anda akan membuktikan puncanya, memulihkan perkhidmatan tanpa but semula jika boleh, dan menghalang perulangan pada hos atau dalam kontena?

Kehendak Soalan dan Skop Penggunaannya

Satu perkhidmatan Linux berulang kali memulakan proses pembantu jangka pendek. Kerja-kerja baharu kini gagal secara berselang-seli dengan fork: Resource temporarily unavailable. CPU dan memori pemastautin (resident memory) kelihatan normal, tetapi ps menunjukkan ribuan anak dalam keadaan Z di bawah satu induk. Dalam sebuah kontena, pids.current menghampiri pids.max dan pembilang max dalam pids.events telah meningkat.

Terangkan maksud zombi, buktikan sama ada anak yang belum dituai (unreaped) menyebabkan kegagalan tersebut, pulihkan perkhidmatan tanpa but semula jika boleh, dan cegah perulangan. Rangkumi kedua-dua hos biasa dan kontena yang aplikasinya mungkin berjalan sebagai PID 1. Kiraan dan pelaksanaan (deployment) adalah andaian temu duga; jawapan yang mantap mesti tetap menguji had-had lain yang boleh menyebabkan fork() mengembalikan EAGAIN.

Ini adalah soalan general kerana kemahiran utamanya ialah kitaran hayat proses Linux dan diagnosis insiden. Panduan temu duga Linux awam semasa merangkumi soalan zombi-lawan-yatim, manakala dokumentasi Linux dan Docker menyediakan sempadan operasi. Ini menyokong soalan yang representatif dan berdaya tahan tanpa membayangkan kehendak khusus syarikat atau kekerapan temu duga tertentu.

Perkara yang Dinilai oleh Penemu Duga

Isyarat pertama ialah sama ada calon memisahkan keadaan proses daripada gejala sumber. Suatu zombi telah pun ditamatkan. Kernel mengekalkan PID, status penamatan dan maklumat perakaunan sehingga induknya mengumpulkannya dengan panggilan keluarga wait. Ia tidak menggunakan CPU biasa mahupun memori ruang pengguna proses yang telah keluar itu, tetapi ia masih menduduki slot jadual proses/PID yang terhad.

Isyarat kedua ialah pemilikan (ownership). Komponen yang rosak biasanya ialah induk hidup yang memulakan anak tetapi gagal menuainya. Menghantar SIGKILL kepada zombi tidak boleh menjadikannya keluar sekali lagi. Cangkang (shell) juga tidak boleh memanggil wait() untuk anak daripada proses yang tidak berkaitan. Calon perlu mengenal pasti induk, penyelianya, unit pelepasannya dan kod pengurusan anaknya sebelum mengambil tindakan.

Isyarat ketiga ialah diagnosis sebab akibat. EAGAIN daripada fork() boleh bermaksud RLIMIT_NPROC bagi setiap pengguna, threads-max seluruh sistem, pid_max, atau had pids.max cgroup. Melihat zombi adalah bukti kukuh, bukannya kebenaran untuk melangkau pemeriksaan ini. Thread dan proses yang tidak berkaitan mungkin menyumbang kepada had siling yang sama.

Isyarat terakhir ialah pembetulan yang bertahan daripada lonjakan beban (bursts), ralat, penutupan (shutdown) dan kontena. Satu panggilan waitpid() bagi setiap SIGCHLD adalah tidak mencukupi kerana isyarat mungkin digabungkan (coalesced). Induk mesti mengosongkan (drain) semua anak yang telah keluar, dan sesebuah kontena mesti mempunyai PID 1 yang mengambil dan menuai keturunan yatim dengan betul.

Soalan untuk Dijelaskan Sebelum Menjawab

  • Di manakah kegagalan diperhatikan? Gangguan seluruh hos, satu akaun pengguna, dan satu kontena menunjuk kepada had dan radius impak (blast radii) yang berbeza.
  • Apakah ralat dan syscall yang tepat? EAGAIN mencadangkan had proses/thread; ENOMEM atau penolakan baris gilir aplikasi mengikut laluan lain.
  • Adakah entri Z tertumpu di bawah satu PPID? Satu PPID yang dominan mengenal pasti laluan pemilikan. Banyak PPID mungkin menunjukkan pembungkus (wrapper) yang dikongsi atau corak init kontena yang rosak.
  • Adakah induk masih hidup, sihat dan diselia? Induk yang hidup boleh dibetulkan atau dimulakan semula dengan selamat. Jika ia keluar, anak-anak akan diserahkan kepada subreaper terdekat atau init ruang nama (namespace), yang kemudiannya mesti menuai mereka.
  • Adakah aplikasi berjalan sebagai PID 1 dalam kontena? PID 1 mempunyai tanggungjawab menuai anak yatim tambahan; masa jalanan (runtime) mungkin memerlukan proses init yang kecil.
  • Bolehkah trafik atau pengambilan kerja dikosongkan (drained)? Permulaan semula terkawal adalah lebih selamat selepas menghentikan fork baharu dan mengekalkan kerja yang sedang berjalan (in-flight).
  • Apakah yang mesti dipelihara daripada keluar anak? Kod keluar, output ralat, dan keputusan percubaan semula menentukan sama ada penuaian sesuai dalam blocking call, event loop, worker pool, atau API proses khusus runtime.

Rangka Jawapan 30 Saat

“Zombi sudah pun mati; induknya belum mengumpul status keluar. Saya akan mengira keadaan Z, mengelompokkannya mengikut PPID, memeriksa induk tersebut, dan mengaitkan siri masa dengan fork yang gagal. Saya juga akan menyemak RLIMIT_NPROC, threads-max, pid_max, dan pids.current, pids.max, serta pids.events cgroup, kerana EAGAIN mempunyai beberapa kemungkinan had. Saya tidak boleh membetulkan zombi dengan kill -9; saya akan menghentikan pembiakan baharu, mengosongkan trafik, kemudian memulakan semula atau membaiki induk dengan selamat supaya subreaper yang berfungsi atau PID 1 mengambil dan menuai anak-anak tersebut. Secara kekal, induk mesti mengosongkan waitpid(-1, ..., WNOHANG) sehingga tiada anak yang telah keluar tertinggal, termasuk laluan ralat dan penutupan. Dalam kontena, saya juga akan menggunakan init yang betul apabila aplikasi tidak dapat melaksanakan tugas PID 1, kemudian menguji beban lonjakan dan mengesahkan kiraan zombi serta penggunaan PID kekal terhad.”

Perbincangan Mendalam Langkah-demi-Langkah

Langkah 1: Tentukan Keadaan Proses dan Pemiliknya

Mulakan dengan arahan yang tidak mencipta talian paip (pipeline) yang besar apabila ruang PID sedia ada sudah terhad:

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 bermula dengan Z ialah zombi. /proc/PID/stat juga mendedahkan keadaan Z dan PPID. Sahkan PPID dengan lebih daripada sekadar nama proses yang terpotong, kemudian periksa induk 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

Rekodkan kiraan zombi, kadar zombi baharu, versi pelepasan induk, dan masa kegagalan pertama. Kiraan stabil yang ditinggalkan seketika semasa penutupan terkawal adalah berbeza daripada kiraan yang meningkat dengan setiap kerja.

Langkah 2: Buktikan Had Mana yang Menolak Anak Baharu

Jangan membuat kesimpulan “kehabisan memori” daripada frasa yang dilihat pengguna “Resource temporarily unavailable.” Linux mendokumentasikan beberapa laluan fork() yang mengembalikan EAGAIN:

  • RLIMIT_NPROC pengguna sebenar;
  • /proc/sys/kernel/threads-max;
  • /proc/sys/kernel/pid_max;
  • had PIDs cgroup yang berkesan.

Untuk cgroup v2, periksa fail dalam cgroup sebenar perkhidmatan dan bukannya menganggap laluan 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

Pembilang peristiwa max yang meningkat secara langsung membuktikan bahawa fork mencecah had siling pengawal PIDs. Bandingkan pids.current dengan kiraan zombi dan kiraan thread/proses yang hidup; pengawal mengira tugas merentasi semua keturunan, jadi zombi mungkin bukan satu-satunya pengguna. Pada hos, bandingkan juga kiraan tugas setiap pengguna dan jumlah sistem dengan hadnya. Rantaian sebab akibat adalah paling kukuh apabila pertumbuhan zombi, baki ruang PID, EAGAIN, dan kadar pembiakan induk sejajar dari segi masa.

Langkah 3: Jelaskan Mengapa “Penyelesaian” Biasa Gagal

kill -9 ZOMBIE_PID tidak boleh menjalankan logik keluar kerana proses tersebut telah pun keluar. Rekod kernel yang tinggal hanya hilang apabila induknya, atau pengambil (adopter) kemudiannya, menunggu untuknya. Binaan dalaman wait milik shell hanya menguruskan anak bagi shell itu sendiri.

Meningkatkan pids.max, pid_max, atau RLIMIT_NPROC secara membuta tuli mungkin memberi masa pemulihan, tetapi ia membiarkan kebocoran aktif dan boleh membesarkan radius impak seterusnya. Mematikan proses hidup secara rawak membebaskan slot tetapi tidak membaiki induk. But semula berfungsi dengan memusnahkan keseluruhan pepohon proses, tetapi ia membuang bukti dan mewujudkan masa henti (downtime) yang boleh dielakkan.

Bezakan juga keadaan D: proses uninterruptible sleeping masih hidup dan sedang menunggu dalam kernel. Ia mempunyai diagnosis yang berbeza walaupun SIGKILL kelihatan tidak berkesan. Menganggap setiap PID yang degil sebagai zombi akan membawa siasatan ke arah yang salah.

Langkah 4: Pulihkan Perkhidmatan dengan Peralihan Induk Terkawal

Mula-mula hentikan atau kurangkan (throttle) pengambilan kerja baharu supaya induk yang rosak tidak menghabiskan slot yang tinggal. Kekalkan satu sesi pentadbiran dan kumpulkan log induk, bukti /proc, had, dan maklumat versi sebelum menukar keadaan.

Jika induk mempunyai mod muat semula (reload) yang didokumentasikan yang mencipta semula gelung pengurusan anaknya dengan selamat, gunakannya. Jika tidak, kosongkan kerja yang sedang berjalan dan mulakan semula induk itu melalui penyelianya. Apabila induk keluar, keturunan yang belum dituai akan diambil oleh subreaper anak terdekat atau init ruang nama PID; pengambil yang betul akan menunggu mereka. Sahkan kiraan zombi menurun sebelum membuka semula pengambilan kerja.

Jika pengambil tidak menuai mereka, memulakan semula pekerja sahaja tidak akan menyelesaikan pemulihan. Pada hos, periksa penyelia perkhidmatan/subreaper. Dalam kontena, penggantian kontena terkawal mungkin diperlukan kerana PID 1 ruang nama memegang tanggungjawab muktamad. Meningkatkan had PID hanya boleh diterima sebagai langkah ruang kelegaan (headroom) sementara yang didokumentasikan dipadankan dengan pendikit (throttling) dan pelepasan yang telah dibetulkan.

Langkah 5: Laksanakan Penuaian yang Mengendalikan Lonjakan dan Ralat

Bagi induk yang mesti menyekat (block) sehingga anak tertentu selesai, panggil waitpid(child_pid, ...) dan kendalikan gangguan. Bagi induk tak segerak (asynchronous), aturkan agar SIGCHLD membangkitkan event loop, kemudian kosongkan 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;
}

Gelung ini tergolong dalam konteks event-loop biasa; pengendali isyarat mentah (raw signal handler) hanya boleh melakukan operasi yang dibenarkan oleh peraturan keselamatan isyarat (signal-safety) runtime, sering kali menetapkan bendera atau menulis ke self-pipe. Mengosongkan (draining) adalah penting kerana beberapa penamatan anak boleh menghasilkan satu pemberitahuan yang diperhatikan. Rangkumi kegagalan antara pembiakan dan pendaftaran, had masa tamat (timeouts), pembatalan, penutupan induk, dan setiap laluan percubaan semula. Dalam runtime terurus, peraturan yang setara adalah tetap menunggu (await) atau menggunakan setiap penyelesaian proses anak.

Mengabaikan SIGCHLD secara eksplisit atau menggunakan SA_NOCLDWAIT boleh meminta pembersihan automatik pada sistem yang menyokongnya, tetapi ia juga membuang pengumpulan status keluar biasa dan mempunyai implikasi kebolehportan/API. Ia merupakan pilihan reka bentuk yang disengajakan, bukan jalan pintas bagi pengurus pekerja yang memerlukan hasil.

Langkah 6: Jadikan PID 1 Kontena dan Pengesahan sebagai Sebahagian daripada Pembetulan

Proses utama kontena bertanggungjawab terhadap proses yang dimulakannya dan mungkin menjadi pengambil untuk keturunannya. Jika aplikasi tidak dapat memajukan isyarat dan menuai dengan betul sebagai PID 1, jalankannya di bawah kemudahan init kecil runtime, seperti --init Docker atau tetapan Compose yang setara. Proses init tidak mengecualikan aplikasi daripada menunggu anak langsungnya yang mana hasilnya dimiliki oleh aplikasi itu; ia menutup jurang pengangkatan anak yatim.

Sahkan pembetulan dengan lonjakan yang lebih besar daripada keserentakan (concurrency) asal, berserta kegagalan anak dan penamatan pantas. Syarat penerimaan hendaklah merangkumi:

  1. setiap anak yang dimulakan menghasilkan satu status penyiapan yang digunakan;
  2. kiraan zombi kembali kepada sifar atau had singkat yang didokumentasikan selepas setiap lonjakan;
  3. pids.current mendap dan tidak terus meningkat serta pids.events tidak merekodkan pelanggaran had baharu;
  4. tiada fork()/spawn EAGAIN berlaku pada beban yang dijangkakan;
  5. penutupan mengosongkan atau menamatkan anak dan kemudian menuai mereka;
  6. nahas induk meninggalkan keturunan kepada subreaper atau PID 1 yang telah diuji;
  7. amaran dicetuskan pada kadar pertumbuhan zombi dan baki ruang PID sebelum penciptaan kerja gagal.

Contoh Jawapan Berkualiti Tinggi

“Saya akan terlebih dahulu mengesahkan bahawa entri benar-benar bermula dengan keadaan Z, mengelompokkannya mengikut PPID, dan memeriksa induk hidup yang dominan. Zombi telah menyelesaikan pelaksanaan, jadi CPU dan RSS normal sememangnya dijangkakan. Kernel menyimpan PID dan status keluar sehingga induk memanggil fungsi keluarga wait. Itulah sebabnya kill -9 pada zombi tidak berkesan dan mengapa induk adalah sasaran pembaikan.

“Saya kemudiannya akan membuktikan had fork yang gagal. Untuk induk, saya akan menyemak RLIMIT_NPROC; pada hos saya akan menyemak threads-max dan pid_max; dalam cgroup perkhidmatan saya akan membaca pids.current, pids.max, dan pids.events. Jika pembilang max meningkat pada masa yang sama dengan ralat dan kumpulan itu hampir dengan silingnya, itu membuktikan bahagian cgroup. Saya tetap akan mengira thread hidup dan tugas keturunan supaya saya tidak mengaitkan setiap slot kepada zombi semata-mata.

“Untuk pemulihan, saya akan mendikit kerja baharu, mengekalkan diagnostik, mengosongkan kerja, dan memulakan semula atau memuat semula induk melalui penyelianya. Zombinya harus diambil dan dituai oleh subreaper yang berfungsi atau PID 1 ruang nama. Jika PID 1 kontena adalah pengambil yang rosak, saya akan menggantikan kontena dengan konfigurasi berdaya init. Menaikkan had hanyalah ruang kelegaan sementara.

“Pembetulan kod kekal adalah dengan menggunakan setiap hasil anak. Pemilik segerak menunggu PID yang tepat. Pemilik berasaskan peristiwa menganggap SIGCHLD sebagai pembangkit dan bergelung dengan waitpid() tidak menyekat sehingga tiada anak yang selesai tinggal, di samping mengendalikan kegagalan pembiakan, pembatalan, dan penutupan. Saya akan menguji beban bagi penamatan pantas, kegagalan paksa, nahas induk, dan penutupan anggun. Pelepasan hanya lulus apabila perakaunan penyiapan adalah satu-dengan-satu, zombi kekal terhad, penggunaan PID mendap, dan tiada peristiwa had baharu berlaku.”

Kesilapan Biasa

  • Menghantar kill -9 kepada setiap zombi → anak telah pun ditamatkan, jadi isyarat tidak dapat mengumpul status keluarnya → kenal pasti dan baiki, muat semula, atau mulakan semula induk/pengambil.
  • Memanggilnya sebagai kebocoran memori → zombi mengekalkan simpan kira kernel dan bukannya ruang alamat biasa proses yang telah keluar → huraikan ia sebagai keletihan jadual proses/PID dan ukur sumber pengehad sebenar.
  • Menganggap keletihan cgroup daripada satu tangkapan skrin ps EAGAIN mempunyai beberapa had dan thread hidup juga menggunakan kapasiti tugas → kaitkan had, pembilang, kiraan tugas dan cap masa.
  • Memanggil waitpid() sekali bagi setiap isyarat → isyarat keluar anak boleh digabungkan, meninggalkan status tambahan tanpa dituai → kosongkan wait tidak menyekat sehingga panggilan melaporkan tiada lagi yang sedia.
  • Menaikkan pids.max sebagai pembetulan kekal → induk yang rosak terus membocorkan slot → gunakan ruang kelegaan tambahan hanya untuk pemulihan, dengan pendikit dan pelepasan pembetulan yang berjadual.
  • Menambah proses init dan mengabaikan anak langsung → aplikasi masih memiliki hasil anak langsung dan semantik percubaan semula → tunggu anak langsung; gunakan init untuk mengendalikan tugas PID 1 dan keturunan yang diambil.
  • Memulakan semula sebelum mengumpul bukti → insiden hilang tanpa membuktikan had atau laluan kod mana yang gagal → rekodkan PPID, had, pembilang, versi, kadar dan log terlebih dahulu apabila ruang membenarkan.

Soalan Susulan dan Maklum Balas

Bagaimana jika induk tidak boleh dimulakan semula semasa waktu perniagaan?

Hentikan laluan yang bocor terlebih dahulu: lumpuhkan jenis kerja, kurangkan keserentakan, atau halakan kerja ke replika yang sihat. Jika dasar membenarkan, tingkatkan hanya had PIDs berkesan yang mencukupi untuk mengekalkan kapasiti pentadbiran dan pemeriksaan kesihatan. Pantau kecerunan (slope) dan bukannya kiraan semata-mata dan kira tarikh akhir keletihan secara konservatif. Induk yang hidup boleh menuai anaknya sendiri hanya jika ia mendedahkan atau boleh menerima tindakan pembaikan yang disokong; proses luaran tidak boleh menunggu untuk mereka. Jadualkan peralihan induk terkawal sebelum ruang kelegaan sementara habis digunakan.

Bagaimana jika zombi hanya muncul di dalam kontena?

Masuk ke pandangan ruang nama PID dan kenal pasti PID 1 ruang nama serta PPID zombi. Sahkan laluan cgroup perkhidmatan dan pembilang PIDs daripada hos atau orkestrator. Jika aplikasi ialah PID 1 dan tidak melaksanakan penuaian/pemajuan isyarat, pasang proses init yang kecil. Jika pembungkus ialah PID 1, sahkan ia tidak keluar awal atau hanya menunggu satu anak. Uji penamatan kontena supaya isyarat sampai ke aplikasi, anak keluar, dan semua status dikumpulkan sebelum tempoh tenggang (grace period) tamat.

Mengapakah satu pelaksanaan pengendali SIGCHLD bukan satu penamatan anak?

Isyarat tradisional adalah pemberitahuan, bukan baris gilir tahan lama satu peristiwa bagi setiap anak. Beberapa anak boleh keluar sebelum proses mengendalikan isyarat, dan pemberitahuan mungkin digabungkan. Kontrak yang teguh ialah “pemberitahuan bermakna periksa keadaan anak,” diikuti dengan panggilan wait tidak menyekat berulang kali sehingga tiada anak yang selesai tinggal. Aplikasi merekodkan setiap PID yang dikembalikan tepat sekali.

Adakah menetapkan SIGCHLD kepada SIG_IGN dapat menyelesaikan masalah?

Pada Linux, mengabaikan SIGCHLD secara eksplisit atau menetapkan SA_NOCLDWAIT mengubah tingkah laku zombi, tetapi aplikasi kemudiannya tidak boleh bergantung pada pengumpulan status keluar biasa melalui wait(). Kecenderungan lalai yang digambarkan sebagai “abaikan” tidak sama dengan memasang SIG_IGN secara eksplisit. Gunakan mod ini hanya apabila hasil anak benar-benar tiada nilai perniagaan dan kontrak bahasa/runtime disahkan; pengurus pekerja biasanya memerlukan perakaunan penyiapan yang eksplisit.

Bagaimanakah anda akan memberi amaran sebelum pengguna melihat fork yang gagal?

Beri amaran mengenai kadar pertumbuhan zombi positif yang berterusan dikelompokkan mengikut induk, baki ruang PID cgroup yang rendah, peningkatan dalam pids.events:max, dan kegagalan pembiakan. Padankan isyarat tersebut dengan daya pemprosesan (throughput) kerja supaya lonjakan jangka pendek yang sah tidak mencetuskan panggilan amaran (page) berdasarkan kiraan semata-mata. Simpan ruang kelegaan yang mencukupi untuk penyelia, telemetri dan arahan pemulihan, kemudian uji amaran terhadap kebocoran terkawal dalam ruang nama bukan pengeluaran.

Sumber awam

Soalan berkaitan