Zero-downtime деплой JavaScript сервисов: как не ронять продакшн

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Zero-downtime деплой: почему это критично для вашего JavaScript сервиса?

Представьте: пятница, вечер, вы деплоите новую фичу на Node.js — и через минуту в чат техподдержки летят панические сообщения: «Сайт лежит!», «500 ошибка!», «Пропали данные!». Знакомо? Если ваш JavaScript сервис перестаёт отвечать на время обновления — вы теряете не только пользователей, но и деньги, и репутацию.

Zero-downtime деплой (развёртывание без простоев) — это подход, при котором обновление кода происходит незаметно для пользователя. Все запросы обрабатываются, соединения не рвутся, а новый код вступает в работу плавно. Для Node.js, где всё завязано на асинхронности и событиях, это особенно важно: одно резкое прерывание может привести к утечкам памяти или необработанным промисам.

В этой статье я покажу, как настроить бесшовный деплой JavaScript сервисов на практике. Мы разберём три рабочих стратегии — Rolling Update, Blue-Green Deployment и Graceful Shutdown — с реальными примерами кода. Поехали!

Стратегия 1: Rolling Update — обновление без остановки

Rolling update (постепенное обновление) — самая популярная техника в мире Node.js. Суть проста: у вас работает несколько экземпляров приложения (инстансов), и вы обновляете их по одному. Пока один перезапускается, остальные продолжают принимать трафик.

Как это работает на практике

Допустим, у вас 4 процесса Node.js, запущенных через PM2 (популярный менеджер процессов). Команда pm2 reload app автоматически делает rolling restart: убивает по одному процессу и запускает новый код. Но чтобы это работало без ошибок, нужно правильно настроить graceful shutdown (об этом ниже).

Пример настройки PM2 для zero-downtime

Создайте файл ecosystem.config.js в корне проекта:

module.exports = {
  apps: [{
    name: 'my-javascript-service',
    script: './server.js',
    instances: 4,                // 4 инстанса
    exec_mode: 'cluster',       // кластерный режим
    wait_ready: true,           // ждём сигнал ready
    listen_timeout: 5000,       // таймаут на готовность
    kill_timeout: 5000,         // таймаут на убийство
    max_restarts: 10,
    env: {
      NODE_ENV: 'production'
    }
  }]
};

А в коде сервера (server.js) добавьте обработку сигналов:

const http = require('http');
const app = require('./app'); // ваше Express/Koa приложение

const server = http.createServer(app);
const PORT = process.env.PORT || 3000;

server.listen(PORT, () => {
  console.log(`Сервер запущен на порту ${PORT}`);
  // Отправляем PM2 сигнал, что процесс готов
  if (process.send) {
    process.send('ready');
  }
});

// Graceful shutdown
process.on('SIGINT', () => {
  console.log('Получен SIGINT, начинаем graceful shutdown...');
  server.close(() => {
    console.log('HTTP сервер закрыт');
    process.exit(0);
  });
});

process.on('SIGTERM', () => {
  console.log('Получен SIGTERM, завершаем работу...');
  server.close(() => {
    console.log('HTTP сервер закрыт');
    process.exit(0);
  });
});

Теперь при выполнении pm2 reload ecosystem.config.js PM2 будет по очереди останавливать и запускать инстансы, дожидаясь сигнала ready. Пользователи не заметят переключения.

Плюсы и минусы Rolling Update

  • + Не требует дополнительных серверов или прокси.
  • + Легко настраивается через PM2 или Kubernetes.
  • - Если новый код содержит критическую ошибку, «плохой» инстанс может успеть обработать несколько запросов.
  • - Требует аккуратного управления состоянием (базы данных, кэш).

Стратегия 2: Blue-Green Deployment — две среды без риска

Blue-Green deployment — это подход, при котором у вас всегда работают две идентичные среды: синяя (Blue) — текущая стабильная версия, зелёная (Green) — новая версия. Вы переключаете трафик с Blue на Green мгновенно, а если что-то пошло не так — откатываетесь назад.

Как реализовать на JavaScript

Вам потребуется балансировщик нагрузки (например, Nginx или HAProxy), который будет направлять запросы к нужной группе процессов. Предположим, у вас два порта: 3001 (Blue) и 3002 (Green).

Пример конфигурации Nginx для Blue-Green:

upstream backend {
    server 127.0.0.1:3001;  # Blue (текущая версия)
    server 127.0.0.1:3002;  # Green (новая версия)
}

server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://backend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

Чтобы сделать деплой, вы:

  1. Запускаете новую версию на порту 3002 (Green).
  2. Проверяете health check (curl http://localhost:3002/health).
  3. Переключаете Nginx на Green, меняя upstream на server 127.0.0.1:3002;.
  4. Останавливаете Blue (порт 3001).

Вот скрипт деплоя на Node.js (с использованием child_process):

const { execSync } = require('child_process');

const deploy = async () => {
  console.log('Запускаем Green (новую версию)...');
  execSync('pm2 start server.js --name green -- --port=3002', { stdio: 'inherit' });

  console.log('Проверяем health...');
  const health = execSync('curl -s -o /dev/null -w "%{http_code}" http://localhost:3002/health');
  
  if (health.trim() === '200') {
    console.log('Green здоров, переключаем трафик...');
    // Меняем конфиг Nginx и перезагружаем
    execSync('cp nginx-green.conf /etc/nginx/conf.d/default.conf');
    execSync('nginx -s reload');
    
    console.log('Останавливаем Blue...');
    execSync('pm2 stop blue');
    execSync('pm2 delete blue');
    
    console.log('Деплой завершён!');
  } else {
    console.error('Health check провален, откат...');
    execSync('pm2 stop green');
    execSync('pm2 delete green');
  }
};

deploy().catch(console.error);

Плюсы и минусы Blue-Green

  • + Мгновенное переключение — почти zero-downtime.
  • + Лёгкий откат: просто переключаете обратно на Blue.
  • - Нужно вдвое больше ресурсов (сервера, память).
  • - Сложнее с база

Блоги

Рекомендаций по книгам