Módulo 6·teoria·3h
Objetivos de aprendizaje
- Entender el problema de rendimiento de un proceso único en Node.js
- Instalar y usar PM2 para gestionar procesos Node.js
- Configurar PM2 en modo cluster para aprovechar múltiples núcleos de CPU
- Configurar NGINX como reverse proxy y balanceador de carga
- Monitorear el estado de los procesos con PM2
Escalabilidad con PM2 y Load Balancing
El problema del proceso único
Node.js es single-threaded: por defecto, tu aplicación corre en un solo proceso y un solo hilo. Si el servidor tiene 8 núcleos de CPU, 7 quedan sin usar.
code
Servidor con 8 núcleos:
Sin clustering: Con clustering (PM2):
┌────────────────┐ ┌────┬────┬────┬────┐
│ Núcleo 1 ✅ │ │ P1 │ P2 │ P3 │ P4 │ (todos activos)
│ Núcleo 2 ❌ │ ├────┼────┼────┼────┤
│ Núcleo 3 ❌ │ │ P5 │ P6 │ P7 │ P8 │
│ ... │ └────┴────┴────┴────┘
└────────────────┘ 8 procesos × 1 CPU cada unoAdemás, si tu proceso cae (error no capturado), el servidor queda caído hasta que alguien lo reinicia manualmente.
PM2 — Process Manager for Node.js
PM2 es el gestor de procesos más popular para aplicaciones Node.js en producción:
bash
# Instalación global
npm install -g pm2
# Verificar instalación
pm2 --versionComandos básicos de PM2
bash
# Iniciar una aplicación
pm2 start server.js
# Iniciar con nombre descriptivo
pm2 start server.js --name "mi-api"
# Iniciar en modo watch (reinicia si hay cambios)
pm2 start server.js --watch
# Ver procesos activos
pm2 list
pm2 status
# Ver logs en tiempo real
pm2 logs
pm2 logs mi-api # Solo de un proceso
# Detener
pm2 stop mi-api
pm2 stop all
# Reiniciar
pm2 restart mi-api
# Recargar sin downtime (zero-downtime reload)
pm2 reload mi-api
# Eliminar del registro de PM2
pm2 delete mi-api
# Monitoreo en tiempo real (CPU, RAM, logs)
pm2 monitModo Cluster — múltiples núcleos
bash
# Iniciar en cluster mode (detecta cantidad de núcleos automáticamente)
pm2 start server.js --name "api" -i max
# Especificar número de instancias
pm2 start server.js --name "api" -i 4
# Recargar sin downtime (actualiza instancias una a una)
pm2 reload apiEjemplo del servidor con información del proceso:
javascript
// server.js
import express from "express";
const app = express();
const PORT = process.env.PORT ?? 3000;
app.get("/health", (req, res) => {
res.json({
ok: true,
pid: process.pid,
uptime: process.uptime(),
memory: process.memoryUsage().heapUsed,
});
});
app.get("/info", (req, res) => {
res.json({
pid: process.pid,
mensaje: `Atendido por el proceso ${process.pid}`,
timestamp: new Date().toISOString(),
});
});
app.listen(PORT, () => {
console.log(`[PID ${process.pid}] Servidor en puerto ${PORT}`);
});Al hacer múltiples requests a /info, verás que diferentes PIDs responden: el balanceador de carga de PM2 distribuye el trabajo.
Archivo de configuración ecosystem.config.js
Para configuraciones más complejas, usa un archivo de configuración:
javascript
// ecosystem.config.js
export default {
apps: [
{
name: "mi-api",
script: "./server.js",
// Cluster mode
instances: "max", // O un número fijo: 4
exec_mode: "cluster",
// Variables de entorno
env: {
NODE_ENV: "development",
PORT: 3000,
},
env_production: {
NODE_ENV: "production",
PORT: 8080,
},
// Opciones de reinicio
watch: false,
max_memory_restart: "500M", // Reinicia si supera 500MB de RAM
// Logs
log_date_format: "YYYY-MM-DD HH:mm:ss",
error_file: "./logs/error.log",
out_file: "./logs/out.log",
merge_logs: true,
},
],
};bash
# Usar el archivo de configuración
pm2 start ecosystem.config.js
pm2 start ecosystem.config.js --env production
pm2 reload ecosystem.config.jsPM2 al iniciar el sistema
bash
# Generar script de arranque automático
pm2 startup
# El comando anterior te da un comando para ejecutar (cópialo y ejecútalo)
# Ejemplo: sudo env PATH=... pm2 startup systemd -u usuario --hp /home/usuario
# Guardar el estado actual de PM2
pm2 save
# Ahora PM2 y tus apps se inician automáticamente al reiniciar el servidorNGINX como reverse proxy
NGINX se coloca delante de Node.js para:
- Terminar SSL/TLS (HTTPS)
- Balancear carga entre múltiples instancias
- Servir archivos estáticos directamente (más eficiente)
- Rate limiting y protección básica
nginx
# /etc/nginx/sites-available/mi-api
upstream nodejs_cluster {
# PM2 gestiona los workers en el mismo puerto
server 127.0.0.1:3000;
# Si tienes múltiples servidores físicos:
# server 192.168.1.10:3000;
# server 192.168.1.11:3000;
# server 192.168.1.12:3000;
}
server {
listen 80;
server_name api.midominio.com;
# Logs
access_log /var/log/nginx/mi-api.access.log;
error_log /var/log/nginx/mi-api.error.log;
# Archivos estáticos — NGINX los sirve directamente
location /public/ {
root /var/www/mi-api;
expires 30d;
add_header Cache-Control "public, immutable";
}
# Todo lo demás va a Node.js
location / {
proxy_pass http://nodejs_cluster;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_cache_bypass $http_upgrade;
}
}Arquitectura final en producción
code
Internet
│
▼
┌──────────┐
│ NGINX │ ← SSL, archivos estáticos, rate limiting
└────┬─────┘
│ proxy_pass http://localhost:3000
▼
┌──────────────────────────────┐
│ PM2 Cluster │
│ ┌───┐ ┌───┐ ┌───┐ ┌───┐ │
│ │ W1│ │ W2│ │ W3│ │ W4│ │ ← Procesos Node.js
│ └───┘ └───┘ └───┘ └───┘ │ (uno por CPU)
└──────────────────────────────┘