Ответ
Чтобы Node.js-приложение оставалось доступным пользователям во время обновления, я использую стратегии, которые позволяют запустить новую версию до отключения старой.
Основные стратегии и их реализация в Node.js-окружении:
-
Blue-Green Deployment:
- Суть: Разворачиваются два идентичных окружения — «Blue» (текущая версия) и «Green» (новая версия). Весь трафик идёт на Blue.
- Деплой: Новая версия разворачивается в Green. После успешного запуска и health-чеков трафик с балансировщика нагрузки (Nginx, HAProxy, облачный LB) мгновенно переключается на Green.
- Откат: В случае проблем — переключение обратно на Blue занимает секунды.
-
Canary Releases:
- Суть: Новая версия разворачивается для небольшого процента пользователей (например, 5%), затем доля постепенно увеличивается.
- Реализация: Используются возможности балансировщиков (например, веса в Nginx
weight) или feature flags на уровне приложения.# Пример конфигурации Nginx для canary upstream backend { server old_app:3000 weight=95; server new_app:3000 weight=5; }
-
Rolling Update (в оркестраторах, например, Kubernetes):
- Суть: Новые поды (контейнеры) с обновлённым приложением создаются по одному/несколько, а старые удаляются после их готовности.
- Настройка: В манифесте Kubernetes задаются
strategy.type: RollingUpdateи параметрыmaxUnavailable(сколько подов может быть недоступно) иmaxSurge(сколько дополнительных подов можно создать).
Технические практики для Node.js приложения:
- Graceful Shutdown: Приложение должно корректно завершать работу, дожидаясь завершения текущих запросов.
process.on('SIGTERM', () => { server.close(() => { console.log('Server closed'); db.disconnect(() => process.exit(0)); }); }); - Health Checks: Добавление эндпоинтов
/healthи/readyдля проверки балансировщиком состояния приложения и его зависимостей (БД, кэш). - Миграции БД: Выполняются до переключения трафика на новую версию и должны быть обратно совместимы со старой версией кода (например, добавление nullable-колонки).
Ответ 18+ 🔞
Э, слушай, тут один вопрос меня просто выносит постоянно — как обновлять продакшен-приложение на Node.js, чтобы пользователи даже не заметили, что ты там что-то ковыряешь. Ну, чтобы не было этой дичи, когда сайт на пять минут ложится, а все начинают орать «чё за хуйня, ничего не работает!». Так вот, есть несколько проверенных способов, которые реально работают.
Первая стратегия — Blue-Green, или «синий-зелёный», ёпта.
Представь, у тебя есть два абсолютно одинаковых стенда. Один, условно, синий — на нём крутится твоя текущая, рабочая версия. Весь трафик идёт туда. Ты спокойно, без спешки, раскатываешь новую версию на зелёный стенд. Поднимаешь, проверяешь, что всё живое. А потом, одним движением рубильника на балансировщике (Nginx, HAProxy или что у тебя там), просто переключаешь весь поток запросов на зелёный. Бах — и новая версия в работе. Если вдруг что-то пошло не так — охуеть, да похуй! — ты просто переключаешь рубильник обратно на синий. Откат занимает секунды. Это как иметь запасной выход, который всегда открыт.
Вторая — Canary Releases, или «канареечное развёртывание».
Тут идея хитрая, как жопа. Ты не сразу всех пользователей кидаешь на новую версию, а начинаешь с малого. Скажем, 5% трафика отправляешь на обновлённое приложение, а остальные 95% пусть себе работают на старой, проверенной. Смотришь метрики: не падают ли ошибки, не полетела ли производительность. Если всё ок — постепенно увеличиваешь процент, скажем, до 20%, потом до 50%, и так далее, пока все не переедут. В Nginx это делается через веса (weight), выглядит просто, но работает, блядь, как часы.
upstream backend {
server old_app:3000 weight=95;
server new_app:3000 weight=5;
}
Если что-то пошло не так — ты быстро локализовал проблему на маленькой группе, а не устроил пиздец всем сразу. Умно, да?
Третья — Rolling Update, особенно если ты в Kubernetes.
Тут оркестратор сам всё делает, красота. Он по одному или по несколько создаёт новые поды (контейнеры) с новой версией твоего приложения. Каждый новый под проходит health-чеки. Как только он готов принимать трафик, оркестратор убивает один старый под. И так потихоньку, пока все поды не обновятся. В манифесте просто прописываешь strategy.type: RollingUpdate и настраиваешь, сколько максимум может быть недоступно (maxUnavailable) и сколько дополнительных подов можно создать сверх плана (maxSurge). Главное — терпения ебать ноль, но процесс идёт сам.
А теперь, чувак, ключевые технические штуки, без которых нихуя не получится:
-
Graceful Shutdown — корректное завершение работы. Это когда приложение получает сигнал «пора на покой» (например,
SIGTERMот оркестратора), оно не тупо умирает, а говорит: «Окей, я закончу обрабатывать текущие запросы, закрою соединения с базой, и только потом выключусь». Иначе пользователи получат ошибки в самый неподходящий момент. Делается это так:process.on('SIGTERM', () => { server.close(() => { console.log('Server closed'); db.disconnect(() => process.exit(0)); }); });Без этого — манда с ушами, а не деплой.
-
Health Checks — проверки здоровья. Ты должен сделать в приложении эндпоинты типа
/healthи/ready. Балансировщик или оркестратор будут постоянно стучаться туда и спрашивать: «Ты живой? Ты готов работать?». Если приложение отвечает «я болен» (например, БД недоступна), его исключат из пула получателей трафика. Доверия ебать ноль — только холодная проверка статуса. -
Миграции базы данных — самая опасная часть. Их нужно выполнять до того, как ты переключишь трафик на новую версию. И они должны быть обратно совместимыми со старой версией кода! Представь: ты добавил новую обязательную колонку в таблицу, а старый код про неё не знает и пытается вставить запись — будет тебе хиросима и нигерсраки. Поэтому правило: сначала миграция (например, добавление nullable-колонки), потом деплой нового кода, который уже умеет с этой колонкой работать. И только потом, когда все переехали, можно делать следующую миграцию, которая, например, сделает колонку NOT NULL. Если проебешь этот момент — сам от себя охуеешь.
В общем, суть в том, чтобы никогда не останавливать систему целиком. Всегда должен быть работающий экземпляр, который отвечает на запросы. А новые версии подключаются и отключаются плавно, как в хорошем театре. Ну, или почти как.