Aktivasi opsi Prefork: true pada web framework Go Fiber di dalam klaster Kubernetes sering kali berakhir dengan status CrashLoopBackOff atau OOMKilled. Fitur prefork menduplikasi proses biner menggunakan mekanisme child process untuk memanfaatkan port sharing (SO_REUSEPORT). Model multi-proses ini bertabrakan dengan isolasi resource cgroup container dan mekanisme liveness probe Kubernetes.

Observasi Insiden: Sinyal Kerusakan pada Pod

Saat rilis bermasalah diluncurkan ke klaster, pod langsung mengalami restart berulang. Diagnostik awal dilakukan dengan memeriksa ringkasan status workload dan riwayat event pod.

kubectl get pods -l app=order-api
# Output menunjukkan CrashLoopBackOff dan restart bertambah
# NAME                         READY   STATUS             RESTARTS      AGE
# order-api-7bbf977464-92xkq   0/1     CrashLoopBackOff   4 (85s ago)   3m

Periksa detail kegagalan menggunakan kubectl describe pod untuk mengekstrak termination reason dan K8s events:

kubectl describe pod order-api-7bbf977464-92xkq

Cari bagian Last State dan Events:

Last State:     Terminated
  Reason:       OOMKilled
  Exit Code:    137
...
Events:
  Warning  Unhealthy  30s  kubelet  Liveness probe failed: HTTP probe failed with statuscode: 503
  Warning  BackOff    12s  kubelet  Back-off restarting failed container

Exit code 137 menandakan sistem operasi (Linux OOM Killer) mematikan container secara paksa karena penggunaan memori melampaui batasan cgroup (limits.memory). Jika container tidak terbunuh oleh OOM, log Fiber sering kali menunjukkan kegagalan child process:

kubectl logs order-api-7bbf977464-92xkq --previous
# Log error:
# [Fiber-Prefork] Child process 42 terminated with exit code 1
# [Fiber-Prefork] Master process failed to bind port or spawn child

Triage Cepat: Eksekusi Rollback Deployment

Langkah pertama pada level operasional adalah mengembalikan status deployment ke revisi kerja terakhir untuk memulihkan ketersediaan layanan.

  1. Batalkan deployment yang sedang bermasalah:
    kubectl rollout undo deployment/order-api -n production
  2. Pantau progress rollback sampai pod lama kembali melayani traffic:
    kubectl rollout status deployment/order-api -n production
  3. Verifikasi bahwa seluruh pod kembali berada pada status 1/1 Running tanpa ada restart tambahan.

Postmortem Ringkas: Benturan Prefork dengan Cgroup

Go Fiber memanfaatkan model prefork untuk memaksimalkan utilisasi multi-core CPU dengan memanggil os/exec dari binary yang sama berulang kali. Benturan arsitektur terjadi di lingkungan container karena faktor-faktor berikut:

  • Deteksi CPU Core Host vs Container Quota: Go secara default mendeteksi total CPU fisik node host (misalnya 32 core) melalui runtime.NumCPU(), bukan kuota cgroup (misalnya limits.cpu: 1000m). Fiber akan men-spawn 32 child process di dalam satu container yang hanya memiliki jatah 1 core.
  • Multiplikasi Alokasi Memori: Setiap child process menjalankan instance Go runtime independen dengan heap dan stack tersendiri. Jika base memory satu proses adalah 30MiB, spawning 16 child process langsung mengonsumsi 480MiB, menembus batas cgroup pod (misal limit 256MiB) dan memicu sinyal SIGKILL dari kernel.
  • Kegagalan Liveness Probe: Spawning puluhan proses secara serentak menyebabkan CPU throttling parah. Akibatnya, server gagal merespons HTTP probe kubelet tepat waktu (timeout), memicu restart berulang dari kubelet meski memori mencukupi.

Pencegahan Teknis & Mitigasi Jangka Panjang

Model konkurensi native Go (goroutine dan M:N scheduler) sudah sangat efisien dalam menangani I/O bound workload dalam satu single OS process. Di Kubernetes, replikasi komputasi harus didelegasikan ke level pod melalui Horizontal Pod Autoscaler (HPA), bukan multi-process prefork di dalam satu container.

1. Proteksi Konfigurasi Bootstrap Go Fiber

Pasang guard rail pada bootstrap aplikasi untuk melarang aktivasi prefork secara eksplisit saat binary berjalan di dalam container Kubernetes.

package main

import (
	"log"
	"os"

	"github.com/gofiber/fiber/v2"
)

func main() {
	isKube := os.Getenv("KUBERNETES_SERVICE_HOST") != ""
	enablePrefork := os.Getenv("ENABLE_PREFORK") == "true"

	// ponytail: disallow prefork inside k8s. Use HPA instead.
	if isKube && enablePrefork {
		log.Println("WARN: Prefork diabaikan karena runtime berada di dalam Kubernetes.")
		enablePrefork = false
	}

	app := fiber.New(fiber.Config{
		Prefork: enablePrefork,
	})

	app.Get("/healthz", func(c *fiber.Ctx) error {
		return c.SendStatus(fiber.StatusOK)
	})

	if err := app.Listen(":8080"); err != nil {
		log.Fatalf("Gagal menjalankan server: %v", err)
	}
}

2. Delegasi Skalabilitas ke Kubernetes HPA

Jalankan container Go Fiber sebagai single-process pod, tentukan resource request dan limit secara realistis, lalu biarkan HPA menambah replika pod ketika beban trafik meningkat.

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: order-api-hpa
  namespace: production
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: order-api
  minReplicas: 3
  maxReplicas: 20
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70

Pendekatan single-process per pod menjaga overhead memori tetap deterministik, menghilangkan risiko perebutan socket listener antar-child process, dan menyelaraskan siklus hidup aplikasi dengan kontrol orkestrasi Kubernetes.