Deploy aplikasi Server-Side Rendering (SSR) seperti Next.js langsung ke 100% trafik menyimpan risiko runtime failure yang tidak terdeteksi saat tahap build. Kesalahan parsing JSON pada getServerSideProps, kegagalan koneksi upstream database, atau memory leak pada Node.js runtime dapat melumpuhkan layanan seketika. Pola rolling update standar Kubernetes sering kali terlambat mendeteksi kegagalan logis ini selama container berstatus Running.

Solusi deterministik untuk masalah ini adalah Canary Release dengan evaluasi metrik otomatis menggunakan Argo Rollouts dan Prometheus. Artikel ini menguraikan instrumentasi metrik pada Next.js (standalone output), pembuatan AnalysisTemplate untuk memantau HTTP 5xx dan latensi P95, serta konfigurasi rollback otomatis saat metrik performa melampaui batas toleransi.

Arsitektur Canary Deployment Next.js Standalone

Saat menggunakan opsi output: 'standalone' pada next.config.js, Next.js menghasilkan build server minimal tanpa memerlukan seluruh direktori node_modules. Arsitektur canary membagi alur trafik menjadi dua subset pod: stable dan canary.

Argo Rollouts bertindak sebagai pengganti Kubernetes Deployment controller standar. Komponen ingress controller (seperti Ingress-NGINX, Traefik, atau Istio) mengatur persentase trafik jaringan ke Service stable dan Service canary sesuai langkah (steps) yang ditentukan. Selama proses transisi, metrik dari container diekspos ke Prometheus untuk dianalisis oleh Rollout Controller.

Eksposur Metrik SSR ke Prometheus dengan prom-client

Next.js secara default tidak mengekspos endpoint metrik Prometheus. Untuk menangkap status code HTTP dan durasi rendering SSR, buat custom server minimal atau gunakan Node.js wrapper di sekitar file server.js hasil build standalone.

Instal dependensi prom-client:

npm install prom-client

Buat file metrics-server.js untuk membungkus server standalone Next.js dan mengekspos endpoint /metrics pada port terpisah atau path internal:

const http = require('http');
const { parse } = require('url');
const client = require('prom-client');
const nextHandler = require('./server'); // Standalone entrypoint Next.js

const register = new client.Registry();
client.collectDefaultMetrics({ register });

const httpRequestDurationMicroseconds = new client.Histogram({
  name: 'http_request_duration_seconds',
  help: 'Durasi request HTTP Next.js dalam detik',
  labelNames: ['method', 'route', 'status_code'],
  buckets: [0.05, 0.1, 0.25, 0.5, 1, 2.5, 5]
});
register.registerMetric(httpRequestDurationMicroseconds);

const server = http.createServer(async (req, res) => {
  const parsedUrl = parse(req.url, true);

  if (parsedUrl.pathname === '/metrics') {
    res.setHeader('Content-Type', register.contentType);
    res.end(await register.metrics());
    return;
  }

  const endTimer = httpRequestDurationMicroseconds.startTimer({
    method: req.method,
    route: parsedUrl.pathname
  });

  res.on('finish', () => {
    endTimer({ status_code: res.statusCode });
  });

  // Delegasikan request ke internal handler Next.js
  nextHandler.render(req, res, parsedUrl);
});

server.listen(3000, () => {
  console.log('Next.js standalone runner with Prometheus listening on port 3000');
});
Catatan: Pisahkan metrik asset statis (/_next/static/*) jika trafik tersebut dialihkan ke CDN untuk menghindari bias pembacaan latensi SSR murni.

Konfigurasi AnalysisTemplate Argo Rollouts

AnalysisTemplate mendefinisikan query Prometheus dan kriteria lolos/gagal. Konfigurasi ini mengevaluasi dua parameter kritis: HTTP 5xx Error Rate harus di bawah 1% dan Latency P95 tidak boleh melebihi 500 milidetik.

apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
  name: nextjs-canary-analysis
  namespace: production
spec:
  metrics:
  - name: error-rate
    interval: 30s
    successCondition: result[0] < 0.01
    failureLimit: 2
    provider:
      prometheus:
        address: http://prometheus-k8s.monitoring.svc.cluster.local:9090
        query: |
          sum(rate(http_request_duration_seconds_count{app="nextjs-frontend",status_code=~"5.."}[1m]))
          /
          sum(rate(http_request_duration_seconds_count{app="nextjs-frontend"}[1m]))
  - name: p95-latency
    interval: 30s
    successCondition: result[0] < 0.5
    failureLimit: 2
    provider:
      prometheus:
        address: http://prometheus-k8s.monitoring.svc.cluster.local:9090
        query: |
          histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket{app="nextjs-frontend"}[1m])) by (le))

Parameter failureLimit: 2 memberi toleransi maksimum 2 kali kegagalan query berturut-turut sebelum memicu status Failed dan membatalkan deployment secara otomatis.

Manifest Rollout Next.js dengan Traffic Routing Bertahap

Ganti resource Deployment menjadi Rollout. Konfigurasi berikut menerapkan skema canary bertahap: 10% trafik, jeda analisis selama 5 menit, naik ke 50% trafik selama 5 menit, sebelum akhirnya beralih penuh ke 100%.

apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: nextjs-frontend
  namespace: production
spec:
  replicas: 10
  strategy:
    canary:
      canaryService: nextjs-frontend-canary
      stableService: nextjs-frontend-stable
      trafficRouting:
        nginx:
          stableIngress: nextjs-frontend-ingress
      analysis:
        templates:
        - templateName: nextjs-canary-analysis
        startingStep: 1
      steps:
      - setWeight: 10
      - pause: { duration: 5m }
      - setWeight: 50
      - pause: { duration: 5m }
  selector:
    matchLabels:
      app: nextjs-frontend
  template:
    metadata:
      labels:
        app: nextjs-frontend
    spec:
      containers:
      - name: nextjs
        image: registry.internal/nextjs-frontend:v2.4.0
        ports:
        - containerPort: 3000
        env:
        - name: NODE_ENV
          value: "production"
        resources:
          limits:
            cpu: "1"
            memory: 1Gi
          requests:
            cpu: 250m
            memory: 512Mi
        readinessProbe:
          httpGet:
            path: /api/health
            port: 3000
          initialDelaySeconds: 5
          periodSeconds: 5

Mekanisme Rollback Otomatis

Alur kerja saat terjadi kegagalan sistem adalah sebagai berikut:

  1. Argo Rollouts mengalihkan 10% trafik ke versi canary baru (v2.4.0).
  2. Prometheus mulai mengumpulkan metrik dari pod canary via AnalysisTemplate.
  3. Jika rilis canary menyebabkan unhandled exception pada getServerSideProps, metrik HTTP 5xx meningkat melampaui batas 0.01 (1%).
  4. AnalysisRun menandai metrik berstatus Failed.
  5. Argo Rollouts membatalkan siklus rilis (Aborted), mengalihkan kembali 100% routing trafik ke nextjs-frontend-stable, dan mengecilkan replika canary ke 0 secara otomatis tanpa campur tangan engineer.

Postmortem Skenario: SSR Runtime Failure & Validasi Synthetic

Insiden

Pada rilis v2.3.1, tim frontend menambahkan pemanggilan data downstream API baru di dalam SSR layout utama. Variabel environment URL endpoint internal tertinggal di level ConfigMap. Saat deployment dijalankan pada arsitektur lama (non-canary), seluruh pod baru lulus fase readiness probe karena endpoint /api/health hanya mengecek status server lokal. Dampaknya: 100% pengguna menerima tampilan Internal Server Error (HTTP 500) selama 8 menit sebelum rollback manual selesai dieksekusi.

Hasil Evaluasi dengan Argo Rollouts

Dengan implementasi Canary Release via Prometheus:

  • Kegagalan hanya berdampak pada 10% pengguna selama rentang 60 detik.
  • Query Prometheus mendeteksi kenaikan error 5xx hingga 18% pada interval scraping pertama.
  • Rollback otomatis selesai dalam 90 detik setelah verifikasi kegagalan kedua.

Langkah Preventif: Validasi Synthetic Traffic

Kelemahan canary murni pada trafik rendah adalah lambatnya pengumpulan data sampel metrik. Untuk rute halaman kritikal yang jarang diakses namun berdampak tinggi, pasang step synthetic run sebelum trafik dialihkan ke pengguna publik.

Gunakan prePromotionAnalysis pada manifest Rollout dengan job synthetic tester (misalnya k6 atau curl-runner) yang memvalidasi rendering SSR pada Service canary:

apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
  name: nextjs-smoke-test
  namespace: production
spec:
  metrics:
  - name: smoke-test-job
    provider:
      job:
        spec:
          template:
            spec:
              containers:
              - name: test
                image: curlimages/curl:8.5.0
                command: ["sh", "-c"]
                args:
                - |
                  STATUS=$(curl -s -o /dev/null -w "%{http_code}" http://nextjs-frontend-canary.production.svc.cluster.local:3000/dashboard)
                  if [ "$STATUS" -ne 200 ]; then exit 1; fi
              restartPolicy: Never
          backoffLimit: 0

Kombinasi antara synthetic smoke testing pada tahap prePromotionAnalysis dan evaluasi metrik runtime pasif via Prometheus memastikan rilis Next.js tidak lagi menjadi sumber insiden produksi fatal.