Kodokon kodokon.com

Выход в продакшен: менеджер процессов, Docker, cluster

Выпусти проект в продакшен: NODE_ENV, менеджер процессов, правильный Docker-образ и масштабирование на все ядра через cluster.

10 мин · 3 вопросов

Открыть этот урок в Kodokon

NODE_ENV - это всего лишь соглашение библиотек: ядро Node почти полностью его игнорирует. Но Express и другие используют его, чтобы кешировать шаблоны и скрывать стеки вызовов; некоторые фреймворки в несколько раз быстрее с NODE_ENV=production. Со стороны зависимостей npm ci --omit=dev устанавливает ровно то, что в lock-файле, без dev-зависимостей - воспроизводимо и быстрее, чем npm install. Начиная с Node 20.6 --env-file загружает файл .env штатно, без дополнительных зависимостей.

BASH
NODE_ENV=production node server.js

npm ci --omit=dev

node --env-file=.env server.js
Три базовые команды для выхода в продакшен.

Процесс Node рано или поздно умирает: перерасход памяти, баг, исключение. Роль менеджера процессов - перезапустить его. Две школы: на голой машине pm2 (или systemd) следит, перезапускает и перезагружает без простоя; в контейнере эту роль играет оркестратор (Kubernetes, ECS) - один процесс на контейнер, и pm2 становится бесполезен, даже вреден, потому что скрывает падения от оркестратора.

BASH
npm install -g pm2
pm2 start server.js -i max --name api
pm2 reload api
pm2 logs api --lines 100
pm2: -i max запускает по воркеру на ядро, reload проходит без простоя.

Две ловушки Docker, специфичные для Node. Первая: образ должен работать от непривилегированного пользователя node, который есть в официальных образах. Вторая: Node не рассчитан на роль PID 1 - он не подбирает процессы-зомби; запускай контейнер с docker run --init (или init: true в Compose), чтобы вставить минимальный init. Наконец, пиши CMD в exec-форме: shell-форма вставляет /bin/sh, который не пробрасывает SIGTERM - корректная остановка из предыдущего урока просто никогда не сработает.

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: пользователь node, CMD в exec-форме.

Один процесс Node использует только одно ядро. Модуль cluster создаёт по процессу-воркеру на ядро: главный процесс слушает порт и раздаёт соединения воркерам по кругу (поведение по умолчанию, кроме Windows). У каждого воркера своя память и свой цикл событий: никакого общего состояния - сессии и кеши должны жить в Redis или его аналоге. Для чистых вычислений предпочитай worker_threads, которые разделяют память через 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);
}
По воркеру на ядро, с автоматическим перезапуском при падении.

Проверка знаний

Убедись, что запомнил ключевые моменты этого урока.

  1. Что на самом деле меняет NODE_ENV=production?
    • Node.js включает более агрессивный JIT-компилятор
    • Библиотеки вроде Express включают свои оптимизации (кеш шаблонов, неподробные ошибки)
    • Node.js отказывается запускаться без HTTPS
  2. Почему в Dockerfile стоит предпочесть exec-форму CMD ["node", "server.js"]?
    • Exec-форма стартует быстрее
    • Exec-форма позволяет подставлять переменные
    • Shell-форма проходит через /bin/sh, который не пробрасывает SIGTERM процессу Node
  3. При использовании cluster с 8 воркерами что происходит с памятью и состоянием приложения?
    • Каждый воркер - отдельный процесс: память умножается на 8, общего состояния нет
    • Воркеры разделяют одну кучу V8
    • Главный процесс периодически объединяет кучи воркеров