Canary деплой на JavaScript: безопасные релизы без страха

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

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

Начать курс

Canary deployments с JavaScript: как выпускать обновления, не боясь сломать всё

Вы когда-нибудь выкатывали новую фичу в полночь, молясь, чтобы сервер не лёг? Или просыпались от уведомления «500 ошибка на всех страницах»? Знакомо, да? Релизы — это всегда стресс. Но есть способ сделать их спокойными, как утро выходного дня: Canary deployments (канареечные развертывания).

Название пошло от старой шахтёрской практики — брать с собой канареек в шахту. Если птичка замолкала — значит, воздух отравлен, и нужно срочно выбираться. В мире JavaScript-разработки принцип тот же: сначала пускаем маленький процент пользователей на новую версию, следим за «самочувствием» системы, и только потом — полный релиз.

В этой статье мы разберём, как реализовать canary deployment на чистом JavaScript (и немного Node.js). Без сложных корпоративных инструментов — только код, логика и здравый смысл.

Почему canary, а не синий-зелёный или rolling update?

Существует несколько стратегий деплоя. Blue-green — это когда у вас две идентичные среды, и вы просто переключаете трафик. Rolling update — постепенная замена подов в Kubernetes. Но у canary есть суперсила: реальный A/B тест на живых пользователях.

  • Меньше риска: если новая версия «убивает» базу данных, пострадает только 1% пользователей, а не все 100%.
  • Быстрая обратная связь: вы видите метрики нового кода до того, как он доберётся до каждого.
  • Автоматический откат: если ошибка растёт — canary гасится сам.

Архитектура canary deployment на JavaScript

Представьте, что у нас есть Express-приложение, которое раздаёт HTML-страницы. Мы хотим выкатить новую вёрстку главной страницы. Вместо того чтобы менять роутер для всех, мы сделаем прослойку — canary middleware.

Компоненты системы

  • Feature flag — флаг, который включает новую версию для определённой группы.
  • Распределитель трафика — логика, которая решает: отдать старую версию (v1) или новую (v2).
  • Мониторинг — сбор метрик (ошибки, latency, конверсии).
  • Автоматический откат — если метрики упали ниже порога.

Простой пример на Node.js и Express

Начнём с базового сервера. Допустим, у нас есть два обработчика: v1 (старый) и v2 (новый).

const express = require('express');
const app = express();
const PORT = 3000;

// Старая версия
app.get('/api/v1/home', (req, res) => {
  res.json({ version: 'v1', message: 'Старый добрый дизайн' });
});

// Новая версия (canary)
app.get('/api/v2/home', (req, res) => {
  res.json({ version: 'v2', message: 'Новый, сверкающий дизайн!' });
});

app.listen(PORT, () => console.log(`Server running on ${PORT}`));

Теперь нам нужно решить, кому показывать v2. Самый простой способ — рандомный выбор на основе user ID.

Распределение трафика: детерминированный рандом

Мы не хотим, чтобы один и тот же пользователь прыгал между версиями при каждом запросе. Поэтому используем хеш от user ID:

function getCanaryGroup(userId, percentage = 5) {
  // Простейший хеш: сумма кодов символов
  const hash = userId.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0);
  return (hash % 100) < percentage;
}

// Пример использования
console.log(getCanaryGroup('user_123')); // true или false

Теперь в middleware мы проверяем, попадает ли пользователь в canary-группу:

app.use('/api/home', (req, res, next) => {
  const userId = req.headers['x-user-id'] || 'anonymous';
  
  if (getCanaryGroup(userId, 5)) { // 5% трафика
    req.url = '/api/v2/home';
  } else {
    req.url = '/api/v1/home';
  }
  
  next();
});

app.get('/api/home', (req, res) => {
  // Этот роут уже не используется напрямую
  res.redirect(req.url);
});

Готово! Теперь 5% пользователей увидят новую версию. Но это только начало.

Продвинутая логика: взвешенное распределение и сегменты

Иногда нужно пускать canary не на всех подряд, а на определённые сегменты: например, только на пользователей из Европы или только на премиум-аккаунты. Реализуем это через конфигурационный объект.

const canaryConfig = {
  enabled: true,
  percentage: 5,
  segments: {
    premium: 20,  // 20% премиум-пользователей
    free: 2       // 2% обычных
  }
};

function shouldShowCanary(user) {
  if (!canaryConfig.enabled) return false;
  
  const segmentKey = user.isPremium ? 'premium' : 'free';
  const segmentPercentage = canaryConfig.segments[segmentKey];
  
  return getCanaryGroup(user.id, segmentPercentage);
}

Такой подход позволяет точечно тестировать фичи на «теплой» аудитории, которая простит ошибки, или наоборот — на самых лояльных.

Мониторинг и автоматический откат

Самое страшное в canary — это не заметить, что новая версия сыпется. Нам нужно собирать метрики и сравнивать их с базовой линией (v1).

Сбор метрик в памяти (для демонстрации)

const metrics = {
  v1: { errors: 0, requests: 0, latency: [] },
  v2: { errors: 0, requests: 0, latency: [] }
};

// Middleware для сбора метрик
app.use('/api/home', (req, res, next) => {
  const start = Date.now();
  const version = req.url.includes('v2') ? 'v2' : 'v1';
  
  res.on('finish', () => {
    metrics[version].requests++;
    metrics[version].latency.push(Date.now() - start);
    
    if (res.statusCode >= 500) {
      metrics[version].errors++;
    }
  });
  
  next();
});

Проверка здоровья canary

Каждые 10 секунд мы проверяем, не превысила ли ошибка в v2 порог (например, 1%):

setInterval(() => {
  const v2 = metrics.v2;
  const v1 = metrics.v1;
  
  if (v2.requests < 10) return; // слишком мало данных
  
  const v2ErrorRate = v2.errors / v2.requests;
  const v1ErrorRate = v1.errors / v1.requests;
  
  console.log(`V2 error rate: ${(v2                

Блоги

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