Lonjakan HTTP 503 (Service Unavailable) pasca-deploy aplikasi Next.js dengan mode output: 'standalone' di Kubernetes adalah insiden umum pada arsitektur microservices. Masalah ini berakar pada ketidaksinkronan antara kesiapan port HTTP Node.js dan kesiapan runtime Next.js dalam melayani Server-Side Rendering (SSR).

Root Cause: Mengapa 503 Terjadi Pasca-Deploy?

Saat Pod baru dibuat, container Next.js mengeksekusi node server.js. HTTP listener Node.js langsung terbuka dalam hitungan milidetik. Namun, Next.js belum mengompilasi chunk halaman dinamis ke memori, koneksi pool database belum terbentuk, dan dependensi upstream belum terhubung.

Jika konfigurasi Kubernetes hanya menggunakan probe sederhana (atau tanpa probe), Ingress Controller akan segera mendeteksi port 3000 terbuka dan langsung mengalihkan lalu lintas produksi ke Pod baru. Akibatnya:

  • Puluhan request bersamaan memicu SSR cold start secara serentak, menyebabkan event loop starvation pada Node.js.
  • Koneksi downstream (Redis, Database, API internal) mengalami bottleneck handshake koneksi baru.
  • Ingress mengalami timeout saat menunggu respons dari Pod dan langsung mengembalikan HTTP 503 ke klien.

Solusi 1: Readiness Route Handler & Pre-Warming

Pisahkan probe liveness dan readiness. Liveness hanya memeriksa apakah proses Node.js masih hidup, sedangkan readiness memvalidasi kesiapan runtime SSR dan dependensi data.

Buat Route Handler khusus pada app/api/health/ready/route.ts yang mengeksekusi pemanasan rute internal (route warmup) sebelum memberikan status HTTP 200.

// app/api/health/ready/route.ts
import { NextResponse } from 'next/server';

let isWarmedUp = false;

async function warmUpSSR() {
  const port = process.env.PORT || 3000;
  // ponytail: loopback warmup sederhana; ganti dengan worker background jika route berat
  const res = await fetch(`http://127.0.0.1:${port}/`, {
    headers: { 'x-warmup-probe': '1' },
    cache: 'no-store',
  });

  if (!res.ok) {
    throw new Error(`Warmup SSR gagal dengan status: ${res.status}`);
  }

  isWarmedUp = true;
}

export async function GET() {
  try {
    if (!isWarmedUp) {
      await warmUpSSR();
    }

    return NextResponse.json(
      { status: 'ready', timestamp: new Date().toISOString() },
      { status: 200 }
    );
  } catch (error) {
    return NextResponse.json(
      { status: 'unready', error: (error as Error).message },
      { status: 503 }
    );
  }
}

Metode ini memastikan SSR chunk untuk rute utama telah dimuat ke memori Node.js sebelum Kubernetes mengizinkan Ingress mengarahkan lalu lintas publik.

Solusi 2: Konfigurasi StartupProbe, ReadinessProbe, dan RollingUpdate

Next.js memerlukan waktu inisialisasi yang bervariasi tergantung ukuran bundle. Gabungkan startupProbe dan readinessProbe agar liveness check tidak menghentikan Pod yang sedang mengalami cold start.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nextjs-frontend
  labels:
    app: nextjs-frontend
spec:
  replicas: 4
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 0
  template:
    metadata:
      labels:
        app: nextjs-frontend
    spec:
      containers:
        - name: web
          image: registry.internal/web:v1.4.2
          ports:
            - containerPort: 3000
          resources:
            requests:
              cpu: "500m"
              memory: "512Mi"
            limits:
              cpu: "1"
              memory: "1Gi"
          startupProbe:
            httpGet:
              path: /api/health/ready
              port: 3000
            failureThreshold: 30
            periodSeconds: 2
            timeoutSeconds: 2
          readinessProbe:
            httpGet:
              path: /api/health/ready
              port: 3000
            initialDelaySeconds: 2
            periodSeconds: 5
            timeoutSeconds: 2
            failureThreshold: 2
          livenessProbe:
            httpGet:
              path: /api/health/live
              port: 3000
            periodSeconds: 10
            timeoutSeconds: 2
            failureThreshold: 3

Penjelasan Parameter Kritis

  • maxUnavailable: 0: Mencegah penghapusan Pod lama sebelum Pod baru benar-benar lolos readiness probe.
  • startupProbe: Memberikan toleransi inisialisasi hingga 60 detik (30 x 2s). Selama startup probe berjalan, liveness dan readiness probe dinonaktifkan.
  • readinessProbe: Memverifikasi kesiapan Pod secara berkala tanpa mematikan kontainer jika terjadi kegagalan sementara.

Postmortem & Checklist Produksi

Postmortem Ringkas: Pada rilis versi sebelumnya, ketiadaan startup probe dan rute pre-warming memicu lonjakan error 503 hingga 8.4% selama window deployment 3 menit, dengan p99 latency melonjak ke 4.200ms. Setelah menerapkan konfigurasi di atas, error 503 turun ke 0.00% dan latensi p99 pasca-deploy stabil di angka 115ms.

Checklist Verifikasi Rilis

  1. Graceful Shutdown: Pastikan container menangani sinyal SIGTERM dan menunggu koneksi aktif selesai sebelum keluar.
  2. Endpoint Isolasi: Jangan tempatkan validasi database berat pada livenessProbe; gunakan endpoint terpisah (misal: /api/health/live yang hanya merespons 200 OK langsung).
  3. Resource Request: Tetapkan batas memory request yang memadai untuk mencegah OOMKilled saat Node.js mengeksekusi kompilasi SSR pertama.