Anatomi Masalah: OOMKilled Pasca-Rilis
Penyebaran rilis baru layanan HTTP berbasis Go Fiber sering kali memicu insiden OOMKilled (Exit Code 137) di lingkungan Kubernetes. Exit code 137 terjadi ketika Linux OOM Killer mengirim sinyal SIGKILL (9) ke proses karena konsumsi memori cgroup melampaui resources.limits.memory.
Go Fiber berjalan di atas engine fasthttp. Secara default, fasthttp dan Fiber mengalokasikan dan membaca seluruh request body ke dalam memori sebelum handler dieksekusi. Ketika endpoint menerima payload upload berukuran puluhan megabyte secara konkuren dan developer mengeksekusi c.BodyParser(), alokasi heap melonjak drastis dalam orde milidetik. Garbage Collector (GC) Go tidak memiliki cukup jendela waktu untuk membebaskan alokasi tersebut sebelum kernel Linux mengeksekusi OOM killer.
Observasi Runtime Go via Prometheus
Identifikasi lonjakan alokasi memori memerlukan eksposur metrik internal runtime Go ke Prometheus secara real-time. Metrik kunci yang harus dipantau meliputi:
go_memstats_heap_alloc_bytes: Memori heap aktif yang sedang dialokasikan oleh objek Go.go_memstats_sys_bytes: Total memori virtual yang diperoleh runtime Go dari sistem operasi.go_memstats_heap_inuse_bytes: Span heap yang aktif digunakan.container_memory_working_set_bytes: Metrik cgroup Kubernetes yang memicu OOM Killer.
Integrasikan Prometheus middleware pada instance Fiber untuk mengekspos metrik tersebut:
package main
import (
"github.com/ansrivas/fiberprometheus/v2"
"github.com/gofiber/fiber/v2"
)
func main() {
app := fiber.New(fiber.Config{
// Batasan default akan dibahas pada bagian mitigasi
})
// Setup Prometheus middleware untuk metrik runtime dan HTTP
prometheus := fiberprometheus.New("order-service")
prometheus.RegisterAt(app, "/metrics")
app.Use(prometheus.Middleware)
app.Post("/api/v1/upload", func(c *fiber.Ctx) error {
// Handler logic
return c.SendStatus(fiber.StatusOK)
})
_ = app.Listen(":8080")
}
Korelasikan metrik go_memstats_heap_alloc_bytes dengan saturasi cgroup. Jika container_memory_working_set_bytes mendekati limit tanpa didahului oleh pembersihan GC yang signifikan, sistem sedang mengalami memory bloat akibat payload besar tanpa streaming.
Automated Rollback pada CrashLoopBackOff
Ketika Pod mengalami restart berulang dengan exit code 137, Kubernetes menandai status Pod sebagai CrashLoopBackOff. Deployment pipelines harus mendeteksi kegagalan ini secara otomatis dan melakukan rollback versi sebelum seluruh replica pool mati.
1. Health Check & Progress Deadline (Native Kubernetes)
Konfigurasikan progressDeadlineSeconds pada spec Deployment agar rollout gagal jika tidak stabil dalam periode tertentu:
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-service
spec:
replicas: 4
progressDeadlineSeconds: 120
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 0
template:
spec:
containers:
- name: app
image: registry.example.com/order-service:v2.1.0
resources:
limits:
memory: "512Mi"
requests:
memory: "256Mi"
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
2. Automasi Rollback via CI/CD Script
Jalankan verifikasi status rollout langsung pada pipeline CD (misal: GitHub Actions, GitLab CI, atau ArgoCD post-sync hook):
# Verifikasi deployment pasca apply
kubectl apply -f deployment.yaml
# Pantau rollout; jika timeout atau crash, trigger rollback otomatis
if ! kubectl rollout status deployment/order-service --timeout=90s; then
echo "Rollout gagal, kemungkinan OOMKilled atau probe failure. Memulai rollback..."
kubectl rollout undo deployment/order-service
kubectl rollout status deployment/order-service --timeout=60s
exit 1
fi
Postmortem Ringkas Insiden
Akar Masalah (Root Cause): Endpoint /api/v1/documents menggunakan c.BodyParser(&payload) untuk memproses request body JSON yang memuat file terenkripsi base64 sebesar ~45MB. fiber.Config.BodyLimit diatur sebesar 100MB di level instance untuk mendukung upload tersebut. Saat 10 request konkuren masuk bersamaan, fasthttp mengalokasikan >450MB ke heap dalam durasi <50ms, melampaui memory limit container sebesar 512MiB dan memicu SIGKILL dari kernel.
Dampak: Pod mengalami restart terus-menerus (CrashLoopBackOff) selama 7 menit di region production hingga traffic dialihkan.
Langkah Pencegahan dan Solusi Kode
1. Terapkan Batas BodyLimit yang Ketat
Jangan biarkan BodyLimit global disetel longgar hanya untuk mengakomodasi satu endpoint upload. Batasi default global ke angka yang konservatif (misalnya 4MB):
app := fiber.New(fiber.Config{
BodyLimit: 4 * 1024 * 1024, // 4 MB limit default
StreamRequestBody: true, // Dukung streaming untuk endpoint tertentu
})
2. Gunakan Multipart Streaming untuk Payload Besar
Hindari memanggil c.BodyParser() atau c.Body() untuk pemrosesan file besar. Manfaatkan stream reader fasthttp untuk membaca data ke disk atau cloud storage (misal: S3) secara chunked:
app.Post("/api/v1/upload-stream", func(c *fiber.Ctx) error {
// Dapatkan multipart form reader tanpa memuat seluruh body ke RAM
req := c.Request()
multipartReader := req.BodyStream()
if multipartReader == nil {
return c.Status(fiber.StatusBadRequest).SendString("Request body stream is empty")
}
formReader, err := req.MultipartForm()
if err != nil {
return c.Status(fiber.StatusBadRequest).SendString("Invalid multipart data")
}
defer formReader.RemoveAll()
files := formReader.File["file"]
if len(files) == 0 {
return c.Status(fiber.StatusBadRequest).SendString("File missing")
}
// Simpan file langsung menggunakan fasthttp SaveFile untuk efisiensi disk buffering
fileHeader := files[0]
dst := "/tmp/uploads/" + fileHeader.Filename
if err := c.SaveFile(fileHeader, dst); err != nil {
return c.Status(fiber.StatusInternalServerError).SendString(err.Error())
}
return c.Status(fiber.StatusOK).JSON(fiber.Map{
"status": "uploaded",
"path": dst,
})
})
3. Tuning Kubernetes Resources dan GOMEMLIMIT
Mulai Go 1.19, tetapkan environment variable GOMEMLIMIT agar Go GC lebih agresif membersihkan memori sebelum OOM Killer aktif. Set GOMEMLIMIT sekitar 80-90% dari container memory limit:
env:
- name: GOMEMLIMIT
value: "400MiB" # 80-90% dari memory limit 512MiB
- name: GODEBUG
value: "madvdontneed=1"
Kombinasi pembatasan buffer fasthttp, pemrosesan multipart streaming, integrasi GOMEMLIMIT, dan script automated rollback mengeliminasi crash OOMKilled berulang pada arsitektur Go Fiber.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!