Health check и SIGTERM: как подготовить API к обновлениям

Зачем различать живой процесс и готовое приложение, как завершать HTTP-запросы при SIGTERM и что проверять после релиза в Inquir Compute.

Health check и SIGTERM: как подготовить API к обновлениям

Зелёный статус контейнера ещё не означает, что пользователь может оформить заказ. HTTP-сервер может отвечать, пока приложение не загрузило конфигурацию или потеряло доступ к обязательной базе. Обратная проблема возникает при обновлении: работающий процесс завершают, а текущие запросы не успевают закончиться.

Эти две границы — начало и конец жизни процесса — стоит проектировать вместе. Разберём небольшой API на Node.js, а затем настроим проверку здоровья и время завершения в Inquir Compute. Пример не обещает обновлений без единой потерянной связи: длинные соединения, нехватка ресурсов и состояние базы требуют отдельных решений.

Разделите «жив» и «готов»

Простая проверка процесса отвечает на вопрос: способен ли сервер обработать HTTP-запрос? Проверка готовности отвечает на другой: может ли эта версия сейчас обслуживать полезный трафик? Не делайте готовность успешной раньше завершения обязательной инициализации.

Не включайте в каждую проверку все внешние сервисы. Если недоступен необязательный сервис рекомендаций, отключение всего API может усилить сбой. Для обязательных зависимостей используйте ограниченные таймауты и учитывайте нагрузку от самих проверок. Общие понятия health checks и graceful shutdown разобраны в документации Express; конкретные проверки платформы нужно настраивать отдельно.

Минимальный сервер с завершением запросов

Сохраните код в server.mjs. Он использует стандартную библиотеку Node.js и не требует npm-зависимостей:

import http from 'node:http';

let stopping = false;
const server = http.createServer((req, res) => {
  if (req.url === '/readyz') {
    res.writeHead(stopping ? 503 : 200);
    return res.end(stopping ? 'stopping' : 'ready');
  }
  if (stopping) {
    res.writeHead(503, { Connection: 'close' });
    return res.end('retry later');
  }
  res.writeHead(200, { 'Content-Type': 'application/json' });
  res.end(JSON.stringify({ version: 'v1' }));
});

server.listen(3000, '0.0.0.0');
process.on('SIGTERM', () => {
  if (stopping) return;
  stopping = true;
  const deadline = setTimeout(() => process.exit(1), 25_000);
  deadline.unref();
  server.close(() => {
    clearTimeout(deadline);
    process.exit(0);
  });
});

У этого примера нет базы, поэтому готовность означает готовность HTTP-процесса. В реальном API добавьте инициализацию до начала приёма трафика, а при завершении закройте пул подключений после окончания запросов. Не копируйте process.exit(0) перед закрытием своих ресурсов: незавершённые операции потеряют шанс закончиться.

Здесь внутренний дедлайн — 25 секунд. Это выбранная настройка примера, не лимит HTTP-запроса платформы. Если операция может работать несколько минут, лучше вынести её в устойчивую фоновую задачу, а клиенту вернуть идентификатор. Для WebSocket нужны собственная процедура закрытия и повторное подключение клиента: server.close() не решает жизненный цикл всех upgraded-соединений.

Упакуйте сервер и настройте Inquir

FROM node:22-bookworm-slim
WORKDIR /app
COPY --chown=node:node server.mjs ./
USER node
EXPOSE 3000
CMD ["node", "server.mjs"]

После авторизации CLI создайте отдельное тестовое приложение:

inquir apps create readiness-demo --ingress public --port 3000 --health-path /readyz --drain-grace 30
inquir apps deploy readiness-demo --promote
inquir apps status readiness-demo

В примере процессу даётся 30 секунд после SIGTERM, а его собственный дедлайн короче на 5 секунд. Запас позволяет закончить самостоятельно. Поддерживаемый диапазон --drain-grace в CLI — от 0 до 3600 целых секунд. Это время завершения старого контейнера; оно не гарантирует доступность дополнительной реплики во время обновления.

Проверьте переход между версиями

Сначала запросите /readyz и / на адресе из status. Затем измените version на v2, выполните новый deploy и убедитесь, что публичный ответ изменился. Во время теста посылайте несколько запросов в секунду и записывайте HTTP-статусы, задержки и ошибки соединения. Не делайте вывод по единственному удачному запросу.

Отдельно в тестовом окружении проверьте нездоровый релиз: временно возвращайте 503 из /readyz. Цель проверки — увидеть, что нездоровая версия не становится нормально обслуживающим production. Сохраните идентификатор предыдущего релиза и проверьте текущее состояние; не повторяйте promote вслепую после таймаута CLI.

Для диагностики используйте inquir apps logs readiness-demo --no-follow --tail 100. Руководство по приложениям описывает релизы, а разбор ошибок деплоя помогает отделить проблемы порта, запуска и маршрутизации. Откат приложения не отменяет миграцию базы и не восстанавливает удалённые файлы — эти изменения требуют собственного плана.