Lonjakan HTTP 502 Bad Gateway yang terjadi saat rolling update aplikasi Next.js di Kubernetes hampir selalu bersumber dari race condition pada siklus hidup Pod. Masalah ini umum ditemukan pada deployment Next.js dengan mode output: 'standalone' yang langsung melayani traffic HTTP dari Ingress tanpa sinkronisasi terminasi jaringan.
Akar Masalah: Desinkronisasi Endpoint Lifecycle di Kubernetes
Ketika deployment diperbarui, Kubernetes API Server memulai dua proses terminasi secara paralel dan asinkron pada Pod lama:
- Penghapusan Endpoint Jaringan: Endpoint controller menghapus IP Pod dari resource
EndpointsatauEndpointSlice. Komponen Ingress controller (seperti Ingress-NGINX) dankube-proxymembaca perubahan ini lalu memperbarui tabel routing lokal (iptables, IPVS, atau konfigurasi upstream Ingress). - Pengiriman Sinyal Terminasi: Kubelet mengirim sinyal
SIGTERMlangsung ke PID 1 di container aplikasi untuk memerintahkan shutdown.
Propagasi pembaruan routing Ingress dan iptables memerlukan waktu antara 1 hingga 5 detik tergantung beban cluster. Jika container Next.js langsung menghentikan proses atau berhenti menerima koneksi begitu menerima SIGTERM, paket HTTP yang masih diarahkan oleh Ingress ke IP Pod lama akan ditolak dengan TCP Reset (RST). Akibatnya, Ingress mengembalikan respons HTTP 502 Bad Gateway ke pengguna akhir.
Analisis Log Ingress dan Bukti Metrik
Identifikasi masalah dapat diverifikasi melalui log Ingress Controller dan metrik koneksi TCP. Pada Ingress-NGINX, log akses akan mencatat kode status 502 dengan variabel $upstream_response_time bernilai sangat rendah atau berupa tanda hubung (-).
# Contoh log akses Ingress-NGINX
[24/Feb/2025:10:15:32 +0000] "GET /dashboard HTTP/2.0" 502 157 "https://example.com/" "Mozilla/5.0..." 12 0.001 [default-nextjs-app-3000] [] 10.244.2.45:3000: 0.001 502 0.001Periksa log error Ingress untuk mendeteksi kegagalan koneksi upstream:
2025/02/24 10:15:32 [error] 1124#1124: *894125 connect() failed (111: Connection refused) while connecting to upstream, client: 192.168.1.1, server: example.com, request: "GET /dashboard HTTP/2.0", upstream: "http://10.244.2.45:3000/dashboard"Pesan connect() failed (111: Connection refused) mengonfirmasi bahwa Pod target telah menutup soket TCP port 3000 sementara Ingress masih menganggap Pod tersebut sehat dan siap menerima traffic.
Solusi 1: Mengulur Terminasi Menggunakan preStop Hook
Kubelet mengeksekusi preStop hook sebelum mengirimkan sinyal SIGTERM ke container. Dengan menyisipkan jeda waktu (sleep delay), Ingress controller dan kube-proxy memiliki jendela waktu yang cukup untuk mencabut IP Pod dari upstream pool sebelum aplikasi mulai dimatikan.
apiVersion: apps/v1
kind: Deployment
metadata:
name: nextjs-standalone
spec:
replicas: 3
template:
spec:
containers:
- name: nextjs
image: my-registry/nextjs-app:v1.2.0
ports:
- containerPort: 3000
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 15"]
# Pastikan terminationGracePeriodSeconds lebih besar dari sleep preStop
terminationGracePeriodSeconds: 45Catatan: Nilai sleep 15 memberi waktu propagasi 15 detik. Pastikan terminationGracePeriodSeconds disetel minimal 30–45 detik agar Kubernetes tidak membunuh container secara paksa menggunakan SIGKILL sebelum proses shutdown selesai.
Solusi 2: Sinkronisasi keepAliveTimeout Node.js
Masalah 502 sekunder pasca-deploy muncul ketika reverse proxy mempertahankan koneksi HTTP Keep-Alive, namun server Node.js memutuskan koneksi tersebut lebih awal secara sepihak. Secara default, Node.js HTTP server memiliki keepAliveTimeout sebesar 5 detik, sedangkan banyak Ingress Controller atau Load Balancer (seperti AWS ALB) mempertahankan koneksi idle hingga 60 detik.
Jika client mengirim request baru bersamaan dengan penutupan koneksi oleh Node.js, terjadi race condition yang memicu TCP Reset. Konfigurasikan server Next.js agar nilai keepAliveTimeout lebih besar dari idle timeout upstream proxy.
Untuk project dengan custom server atau wrapper standalone (server.js):
const http = require('http');
const next = require('next');
const dev = process.env.NODE_ENV !== 'production';
const app = next({ dev });
const handle = app.getRequestHandler();
app.prepare().then(() => {
const server = http.createServer((req, res) => {
handle(req, res);
});
// Set timeout melebihi idle timeout Ingress (misal Ingress 60s)
server.keepAliveTimeout = 65000;
server.headersTimeout = 66000;
server.listen(3000, () => {
console.log('Next.js standalone listening on port 3000');
});
// Graceful shutdown
process.on('SIGTERM', () => {
console.log('SIGTERM received. Starting graceful close...');
server.close((err) => {
if (err) {
console.error('Error during close:', err);
process.exit(1);
}
console.log('HTTP server successfully closed');
process.exit(0);
});
});
});Peringatan Arsitektur: NilaiheadersTimeoutharus selalu diatur lebih tinggi darikeepAliveTimeoutsesuai spesifikasi internal Node.js untuk mencegah crash runtime akibat header parsing failure.
Solusi 3: Penanganan Sinyal SIGTERM yang Benar di Dockerfile
Pada Next.js standalone bawaan (tanpa custom server), container dijalankan dengan node server.js. Pastikan perintah eksekusi di Dockerfile menggunakan format exec form, bukan shell form, agar sinyal SIGTERM tidak tertahan di subshell.
# BENAR: Sinyal diteruskan langsung ke proses Node.js
CMD ["node", "server.js"]
# SALAH: Sinyal terperangkap di /bin/sh, node tidak menerima SIGTERM
CMD npm start
# atau
CMD node server.jsTemplate Postmortem Insiden 502
Gunakan format ringkas berikut untuk dokumentasi pasca-insiden:
- Ringkasan Masalah: Peningkatan spike error HTTP 502 sebesar 4.2% selama 3 menit pada saat rolling deploy aplikasi Next.js rilis v1.1.8.
- Akar Masalah (Root Cause): Pod lama menerima
SIGTERMdan langsung terminasi sebelum Ingress-NGINX selesai memperbarui upstream pool. Request baru tetap dirutekan ke IP Pod yang sudah mati. - Dampak: 320 request gagal dengan respons 502 Bad Gateway di endpoint transaksi.
- Tindakan Koreksi: Menambahkan
preStophooksleep 15dan menaikkanterminationGracePeriodSecondsmenjadi 45 detik pada spec Deployment. MenyesuaikankeepAliveTimeoutke 65 detik.
Checklist Verifikasi Zero-Downtime Rollout
Sebelum menandai perbaikan selesai, lakukan verifikasi menggunakan traffic generator (misal: hey atau k6) selama proses deploy berlangsung:
- Jalankan load test konstan:
hey -z 60s -q 20 -c 10 https://app.example.com/api/health. - Lakukan rolling update:
kubectl rollout restart deployment/nextjs-standalone. - Pastikan tidak ada respons non-200 pada output load test: metrik
Status code 200harus mencapai 100%. - Pantau log Ingress: pastikan tidak ada baris
Connection refusedatau502saat pod lama berpindah ke statusTerminating.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!