Kodokon kodokon.com

Pasar a producción: gestor de procesos, Docker, cluster

Lleva a producción: NODE_ENV, gestor de procesos, una imagen Docker correcta y escalado multinúcleo con cluster.

10 min · 3 preguntas

Abrir esta lección en Kodokon

NODE_ENV no es más que una convención de bibliotecas: el núcleo de Node lo ignora casi por completo. Pero Express, entre otros, lo usa para cachear vistas y ocultar trazas de pila; algunos frameworks son varias veces más rápidos con NODE_ENV=production. Del lado de las dependencias, npm ci --omit=dev instala exactamente el lockfile, sin dependencias de desarrollo - reproducible y más rápido que npm install. Desde Node 20.6, --env-file carga un archivo .env de forma nativa, sin ninguna dependencia.

BASH
NODE_ENV=production node server.js

npm ci --omit=dev

node --env-file=.env server.js
Los tres comandos básicos para pasar a producción.

Un proceso de Node siempre acaba muriendo: desbordamiento de memoria, bug, excepción. El papel del gestor de procesos es reiniciarlo. Dos escuelas: en una máquina desnuda, pm2 (o systemd) supervisa, reinicia y recarga sin tiempo de inactividad; en un contenedor, es el orquestador (Kubernetes, ECS) el que desempeña este papel - un proceso por contenedor, y pm2 se vuelve inútil, incluso perjudicial, porque oculta las caídas al orquestador.

BASH
npm install -g pm2
pm2 start server.js -i max --name api
pm2 reload api
pm2 logs api --lines 100
pm2: -i max crea un worker por núcleo, recarga sin tiempo de inactividad.

Dos trampas de Docker específicas de Node. Una: la imagen debería ejecutarse como el usuario node sin privilegios, que proporcionan las imágenes oficiales. Dos: Node no está diseñado para ser PID 1 - no recolecta los procesos zombis; lanza el contenedor con docker run --init (o init: true en Compose) para insertar un init mínimo. Por último, escribe CMD en forma exec: la forma shell inserta /bin/sh, que no reenvía SIGTERM - el graceful shutdown de la lección anterior nunca se activaría.

BASH
FROM node:20-slim AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev

FROM node:20-slim
ENV NODE_ENV=production
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
USER node
EXPOSE 3000
CMD ["node", "server.js"]
Dockerfile multietapa: usuario node, CMD en forma exec.

Un solo proceso de Node usa un único núcleo. El módulo cluster crea un proceso worker por núcleo: el primario escucha en el puerto y distribuye las conexiones a los workers en round-robin (el comportamiento por defecto, salvo en Windows). Cada worker tiene su propia memoria y su propio event loop: sin estado compartido - las sesiones y las cachés deben vivir en Redis o algo equivalente. Para el cálculo puro, prefiere worker_threads, que comparte memoria mediante SharedArrayBuffer.

JAVASCRIPT
import cluster from "node:cluster";
import { createServer } from "node:http";
import { availableParallelism } from "node:os";

if (cluster.isPrimary) {
  const count = availableParallelism();
  for (let i = 0; i < count; i += 1) cluster.fork();
  cluster.on("exit", (worker) => {
    console.log(`worker ${worker.process.pid} down`);
    cluster.fork();
  });
} else {
  createServer((req, res) => {
    res.end(`pid ${process.pid}`);
  }).listen(3000);
}
Un worker por núcleo, reiniciado automáticamente si muere.

Prueba de conocimientos

Comprueba que has retenido los puntos clave de esta lección.

  1. ¿Qué cambia realmente NODE_ENV=production?
    • Node.js activa un compilador JIT más agresivo
    • Bibliotecas como Express activan sus optimizaciones (caché de vistas, errores no verbosos)
    • Node.js se niega a arrancar sin HTTPS
  2. ¿Por qué preferir la forma exec CMD ["node", "server.js"] en un Dockerfile?
    • La forma exec arranca más rápido
    • La forma exec permite la interpolación de variables
    • La forma shell pasa por /bin/sh, que no reenvía SIGTERM al proceso de Node
  3. Con cluster y 8 workers, ¿qué ocurre con la memoria y el estado de la aplicación?
    • Cada worker es un proceso separado: memoria multiplicada por 8, sin estado compartido
    • Los workers comparten el mismo heap de V8
    • El primario fusiona periódicamente los heaps de los workers