Menjalankan worker antrean pada virtual machine (VM) konvensional membawa beban userland yang signifikan: systemd, udev, journald, syslog, cron, dan runtime library dinamis. Ketika beban kerja berfokus murni pada throughput eksekusi task terdistribusi, layer sistem operasi standar ini memakan puluhan megabyte memori dan menambah latensi boot. Terinspirasi oleh konsep boot naked Linux, pendekatan minimal init worker Linux menggantikan seluruh userland dengan satu binary C yang dikompilasi statis yang langsung bertindak sebagai proses PID 1.

Arsitektur Worker Tanpa Userland Bloat

Pada Linux, kernel menyerahkan eksekusi pertama di user-space ke program yang didefinisikan oleh parameter boot init=. Jika program tersebut adalah binary C mandiri:

  • Kernel me-mount root filesystem (initramfs/initrd atau disk minimal).
  • Kernel mengeksekusi binary target sebagai PID 1.
  • Tidak ada shell, tidak ada dynamic linker (ld.so), dan tidak ada manajemen session.

Arsitektur ini membutuhkan pustaka C minimalis seperti musl libc untuk menghasilkan binary statis berukuran di bawah 1 MB dengan konsumsi baseline memori kernel + userland kurang dari 16-32 MB RAM.

Kewajiban Khusus PID 1: Sinyal dan Zombie Reaping

Ketika program berjalan sebagai PID 1, Linux menerapkan aturan kernel unik yang berbeda dari proses biasa:

  1. Penanganan Sinyal Default Hilang: PID 1 mengabaikan sinyal fatal default (seperti SIGTERM dan SIGINT) kecuali handler didaftarkan secara eksplisit.
  2. Subreaper dan Zombie Reaping: Proses orphan yang orang tuanya terminasi akan di-reparenting ke PID 1. Jika PID 1 tidak menjalankan wait() atau waitpid() saat menerima SIGCHLD, tabel proses kernel akan penuh oleh proses zombie (state defunct) hingga batas PID sistem habis.

Implementasi Kode C Minimal untuk Init Worker

Binary berikut bertindak sebagai PID 1, menangani reap zombie child, mengatur batas memori via syscall setrlimit, dan melakukan polling antrean via raw TCP socket.

#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/resource.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>

static volatile sig_atomic_t keep_running = 1;

static void sigterm_handler(int sig) {
    (void)sig;
    keep_running = 0;
}

static void sigchld_handler(int sig) {
    (void)sig;
    // Reap semua zombie child non-blocking
    while (waitpid(-1, NULL, WNOHANG) > 0);
}

static void setup_process_limits(void) {
    struct rlimit mem_limit;
    // Batas memori virtual 256MB per task
    mem_limit.rlim_cur = 256 * 1024 * 1024;
    mem_limit.rlim_max = 256 * 1024 * 1024;
    setrlimit(RLIMIT_AS, &mem_limit);
}

static void execute_task(const char *payload) {
    pid_t pid = fork();
    if (pid == 0) {
        // Child process
        printf("[TASK] Processing payload: %s\n", payload);
        _exit(0);
    }
    // Parent lanjut ke loop polling
}

int main(void) {
    // 1. Tangani sinyal PID 1
    struct sigaction sa_chld, sa_term;
    memset(&sa_chld, 0, sizeof(sa_chld));
    sa_chld.sa_handler = sigchld_handler;
    sa_chld.sa_flags = SA_RESTART | SA_NOCLDSTOP;
    sigaction(SIGCHLD, &sa_chld, NULL);

    memset(&sa_term, 0, sizeof(sa_term));
    sa_term.sa_handler = sigterm_handler;
    sigaction(SIGTERM, &sa_term, NULL);

    setup_process_limits();

    printf("[INIT] Minimal Init Worker running as PID %d\n", getpid());

    // 2. Loop polling antrean (Simulasi raw socket / HTTP queue)
    while (keep_running) {
        // Polling socket mentah / endpoint HTTP sederhana dilakukan di sini
        execute_task("job_id_8192");
        sleep(2); // Interval poll
    }

    // Flush & reap tersisa sebelum shutdown
    while (waitpid(-1, NULL, 0) > 0);
    printf("[INIT] Exiting PID 1\n");
    return 0;
}

Kompilasi: Gunakan musl-gcc untuk menghasilkan file ELF statis tanpa ketergantungan runtime glibc: musl-gcc -static -O2 -o init worker.c.

Konfigurasi Boot Kernel dan Isolasi Task

Untuk menjalankan worker langsung di atas hardware atau QEMU/microVM tanpa init manager pihak ketiga, tentukan path biner pada parameter boot kernel (cmdline):

console=ttyS0 quiet init=/init panic=-1 isolcpus=1 nohz_full=1 rcu_nocbs=1

Penjelasan konfigurasi:

  • init=/init: Memerintahkan kernel memanggil biner kustom kita di root direktori sebagai PID 1.
  • panic=-1: Mencegah kernel diam permanen saat init crash; sistem langsung me-reboot VM.
  • isolcpus=1 dan nohz_full=1: Mengisolasi core CPU 1 dari scheduler interrupt sistem operasi untuk task komputasi berlatensi stabil.

Trade-Off: Minimal Init vs OCI Container

Meskipun performa efisien, arsitektur ini memindahkan tanggung jawab operasional penuh ke pengembang:

  • Observabilitas: Tidak ada dmesg, top, ss, atau shell interaktif untuk debug langsung via SSH. Metrik kinerja wajib dikirim sendiri via raw socket atau UDP/HTTP agent internal.
  • Networking & DNS: Resolusi DNS dan konfigurasi antarmuka jaringan (DHCP atau static IP) harus dikonfigurasi manual menggunakan ioctl atau netlink syscall sebelum membuka socket.
  • Kapan Memilih Minimal Init: Cocok untuk microVM (seperti AWS Firecracker atau Cloud Hypervisor) pada use case burst serverless, isolated code execution sandbox, atau worker queue throughput tinggi di lingkungan edge dengan sumber daya CPU/RAM sangat terbatas.
  • Kapan Memilih Container Standar: Gunakan runtime container standar (containerd/Docker) jika task membutuhkan dependensi bahasa dinamis (Python, Node.js), observabilitas standard (Prometheus scraping, logs collector), dan fleksibilitas volume mounting.