Lonjakan HTTP 502 Bad Gateway saat deployment monolitik Inertia.js dengan Server-Side Rendering (SSR) umumnya disebabkan oleh terminasi proses Node.js secara instan tanpa memberi waktu bagi in-flight requests untuk selesai. Ketika eksekusi deployment script mematikan daemon SSR sebelum proses baru siap melayani trafik, socket TCP terputus mendadak dan gateway (Nginx atau runtime PHP) mengembalikan status 502.
Masalah ini dapat dieliminasi secara deterministik dengan menerapkan graceful shutdown berbasis sinyal POSIX, isolasi port upstream, health checking aktif, dan mekanisme safe rollback otomatis pada skrip deployment.
Postmortem: Mengapa HTTP 502 Terjadi Saat Rilis
Arsitektur Inertia SSR standar melibatkan komunikasi internal: Nginx menerima request web, meneruskannya ke PHP-FPM, lalu ekstensi HTTP client Laravel memanggil daemon Node.js lokal (default port 13714) untuk merender HTML awal. Jika SSR crash atau socket tertutup, alur eksekusi menghasilkan error.
Observasi Log dan Metrik APM
Saat deployment berlangsung tanpa koordinasi proses, metrik error rate Nginx dan APM memperlihatkan anomali spesifik:
- Nginx
error.log: Muncul pesanconnect() failed (111: Connection refused) while connecting to upstreamataurecv() failed (104: Connection reset by peer). - APM Tracer: Span HTTP request dari runtime PHP ke endpoint render SSR mengalami status
Connection refuseddalam rentang 1 hingga 5 detik selama tahapanartisan inertia:stop-ssratausystemctl restart. - Metrik Ketersediaan: Terjadi spike HTTP 502 pada Nginx jika PHP dikonfigurasi melempar
HttpExceptiontanpa fallback, atau HTTP 500 langsung ke user akhir.
Akar masalah teknisnya adalah deployment script memicu perintah terminasi paksa (seperti SIGKILL atau kill -9) atau supervisor mematikan worker tanpa menunggu siklus render React/Vue selesai dikompilasi ke string HTML.
Graceful Shutdown dan Connection Draining pada Node.js
Runtime Node.js SSR harus menangkap sinyal SIGTERM dan menolak menerima koneksi baru sembari menuntaskan request yang sedang diproses. Gunakan handler sinyal eksplisit pada file bootstrap SSR (misalnya bootstrap/ssr/ssr.js):
// Inisialisasi HTTP server Inertia SSR
import { createServer } from 'node:http';
const server = createServer((req, res) => {
// Logika render Inertia
});
const PORT = process.env.PORT || 13714;
server.listen(PORT, '127.0.0.1');
// Tangani sinyal terminasi dari OS/supervisor
let isShuttingDown = false;
function gracefulShutdown(signal) {
if (isShuttingDown) return;
isShuttingDown = true;
console.warn(`[SSR] Menerima sinyal ${signal}. Menutup listener koneksi baru...`);
// Hentikan penerimaan koneksi TCP baru
server.close((err) => {
if (err) {
console.error('[SSR] Error saat menutup server:', err);
process.exit(1);
}
console.info('[SSR] Seluruh in-flight request selesai. Exit bersih.');
process.exit(0);
});
// Batas toleransi pembersihan (connection draining timeout)
const FORCE_TIMEOUT_MS = 10000;
setTimeout(() => {
console.error('[SSR] Timeout draining terlampaui. Mematikan paksa.');
process.exit(1);
}, FORCE_TIMEOUT_MS).unref();
}
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
Konfigurasi Systemd Service Unit
Jika Node.js dikelola menggunakan Systemd, pastikan direktif KillSignal dan timeout penghentian dikonfigurasi agar tidak mengirimkan SIGKILL secara prematur:
[Unit]
Description=Inertia SSR Node.js Daemon
After=network.target
[Service]
Type=simple
User=www-data
Group=www-data
WorkingDirectory=/var/www/app/current
ExecStart=/usr/bin/node bootstrap/ssr/ssr.js
Restart=always
RestartSec=3
# Berikan waktu 15 detik untuk graceful shutdown sebelum SIGKILL dikirim
KillSignal=SIGTERM
KillMode=mixed
TimeoutStopSec=15
# Konfigurasi env
Environment=NODE_ENV=production
Environment=PORT=13714
[Install]
WantedBy=multi-user.target
Konfigurasi Upstream dan Fallback Nginx
Di sisi infrastruktur, Nginx dapat dikonfigurasi agar mentoleransi kegagalan instan upstream melalui mekanisme retry otomatis dan connection pooling.
upstream inertia_ssr_cluster {
server 127.0.0.1:13714 max_fails=2 fail_timeout=3s;
# Keepalive pool untuk mengurangi overhead TCP handshake
keepalive 32;
}
server {
listen 80;
server_name example.com;
root /var/www/app/current/public;
location @ssr {
proxy_pass http://inertia_ssr_cluster;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
# Proteksi kegagalan instan
proxy_connect_timeout 2s;
proxy_read_timeout 5s;
proxy_next_upstream error timeout invalid_header http_502;
proxy_next_upstream_tries 2;
}
}
Catatan Fallback: Pada arsitektur monolit Laravel Inertia, jika server Node SSR mati total, method bawaan Inertia akan otomatis fallback merender client-side template (SPA biasa) selama Anda tidak memaksakan throw exception pada konfigurasi
config/inertia.php(opsissr.throwdiatur kefalse).
Skrip Deployment Zero-Downtime dan Health Probe Rollback
Pola zero-downtime yang aman memanfaatkan struktur direktori releases dan symlink current. Daemon baru dijalankan pada port sementara atau divalidasi via health check sebelum symlink dialihkan.
#!/usr/bin/env bash
set -euo pipefail
RELEASE_DIR="/var/www/app/releases/$(date +%Y%m%d%H%M%S)"
PREVIOUS_DIR="$(readlink -f /var/www/app/current || true)"
TARGET_PORT=13714
echo "Deploying ke: ${RELEASE_DIR}"
mkdir -p "${RELEASE_DIR}"
git clone --depth 1 [email protected]:org/repo.git "${RELEASE_DIR}"
cd "${RELEASE_DIR}"
composer install --no-dev --optimize-autoloader
npm ci && npm run build
# Verifikasi bundle SSR berhasil dikompilasi
if [ ! -f "bootstrap/ssr/ssr.js" ]; then
echo "[ERROR] Bundle SSR tidak ditemukan. Batalkan deployment!"
rm -rf "${RELEASE_DIR}"
exit 1
fi
# Switch symlink ke rilis baru
ln -sfn "${RELEASE_DIR}" /var/www/app/current_candidate
# Reload systemd service (memicu SIGTERM ke worker lama via graceful reload)
sudo systemctl reload-or-restart inertia-ssr.service
# Health check probe loop
MAX_RETRIES=5
RETRY_COUNT=0
HEALTHY=false
echo "Menjalankan health probe SSR..."
until [ "$RETRY_COUNT" -ge "$MAX_RETRIES" ]; do
if curl -s -f -o /dev/null "http://127.0.0.1:${TARGET_PORT}/healthz"; then
HEALTHY=true
break
fi
RETRY_COUNT=$((RETRY_COUNT + 1))
echo "Probe gagal (${RETRY_COUNT}/${MAX_RETRIES}). Coba lagi dalam 1s..."
sleep 1
done
if [ "$HEALTHY" = true ]; then
# Promosi rilis candidate ke active
mv -Tf /var/www/app/current_candidate /var/www/app/current
echo "[SUKSES] Rilis aktif berhasil dialihkan."
else
echo "[CRITICAL] SSR Service gagal merespons. Menjalankan Rollback!"
rm -f /var/www/app/current_candidate
if [ -n "${PREVIOUS_DIR}" ]; then
ln -sfn "${PREVIOUS_DIR}" /var/www/app/current
sudo systemctl restart inertia-ssr.service
echo "Rollback selesai ke: ${PREVIOUS_DIR}"
fi
exit 1
fi
Pencegahan Regresi pada Pipeline CI/CD
Untuk mencegah kode yang merusak siklus hidup SSR lolos ke server produksi, tambahkan validasi otomatis pada tahap pengujian CI:
- Dry-run SSR Initialization: Jalankan
node bootstrap/ssr/ssr.js --check-onlyatau jalankan proses di background lalu eksekusi single-render test menggunakancurlsebelum build artifact dipaketkan. - Linting Memory Leaks: Pastikan tidak ada global state yang di-share antar-request di komponen Vue/React SSR. Global state yang tidak dibersihkan menyebabkan leak memori, memicu OOM (Out Of Memory) killer mematikan Node secara mendadak dengan sinyal
SIGKILL. - Timeout Monitoring: Pasang alert APM khusus untuk HTTP request antar PHP dan daemon Node.js. Jika latensi SSR internal melebihi 200ms, tim teknis harus segera menerima notifikasi sebelum kapasitas pool habis.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!