Пошук уроків, статей та іншого контенту
Додавайте liveness і readiness checks для моніторингу доступності та готовності Node.js сервісу.
Перевірки стану — це HTTP-ендпоїнти, за якими система оркестрації або моніторинг визначає, що відбувається із сервісом.
Зазвичай використовують два типи перевірок:
liveness check — чи живий процес і чи може він відповідати на HTTP-запити;
readiness check — чи готовий сервіс обробляти запити від користувачів або інших сервісів.
Ці перевірки мають різне призначення:
| Перевірка | Що перевіряє | Типова дія у разі помилки | |---|---|---| | Liveness | Процес не завис і може відповідати | Перезапустити процес | | Readiness | Сервіс завершив запуск і має доступ до потрібних залежностей | Прибрати сервіс із балансування |
Важливо не змішувати ці поняття. Тимчасова недоступність бази даних зазвичай означає, що сервіс не готовий, але не обов’язково, що його потрібно перезапускати.
Liveness-перевірка має бути максимально простою. Вона не повинна звертатися до бази даних, зовнішніх API чи інших залежностей.
Приклад відповіді:
GET /health/live
HTTP/1.1 200 OK
Content-Type: application/json
{"status":"ok"}Якщо процес завис, перевірка не отримає відповіді або отримає помилку. Тоді система, яка запускає сервіс, може виконати його перезапуск.
Не слід додавати до liveness:
перевірку з'єднання з базою даних;
запит до стороннього API;
складні обчислення;
довгі асинхронні операції;
перевірку всіх внутрішніх компонентів сервісу.
Якщо зовнішня база даних тимчасово недоступна, процес Node.js може залишатися працездатним. Перезапуск у такій ситуації не завжди допоможе й може створити додаткове навантаження.
Readiness-перевірка показує, чи можна направляти до сервісу робочі запити.
Сервіс може бути неготовим у таких випадках:
ще виконується початкова ініціалізація;
не встановлено з'єднання з базою даних;
недоступна критична залежність;
сервіс перебуває у процесі завершення роботи.
Для неготового сервісу зазвичай повертають статус 503 Service Unavailable:
GET /health/ready
HTTP/1.1 503 Service Unavailable
Content-Type: application/json
{"status":"not_ready"}Коли сервіс готовий:
HTTP/1.1 200 OK
Content-Type: application/json
{"status":"ok"}Клієнти й балансувальники не повинні використовувати HTTP-відповідь 503 як звичайну помилку бізнес-операції. Це службовий сигнал: сервіс тимчасово не може приймати робочий трафік.
Нижче наведено повний приклад HTTP-сервісу без додаткових бібліотек.
Він має такі властивості:
/health/live перевіряє лише доступність процесу;
/health/ready перевіряє завершення запуску та критичну залежність;
для залежності використовується URL із змінної середовища DEPENDENCY_URL;
якщо DEPENDENCY_URL не задано, зовнішня залежність вважається не потрібною;
перевірка залежності має тайм-аут;
відповіді не містять зайвих внутрішніх деталей.
const http = require('node:http');
const port = Number(process.env.PORT) || 3000;
const dependencyUrl = process.env.DEPENDENCY_URL;
let startupComplete = false;
// Імітуємо асинхронний запуск сервісу:
// підключення до бази даних, завантаження конфігурації тощо.
setTimeout(() => {
startupComplete = true;
console.log('Сервіс завершив початкову ініціалізацію');
}, 1000);
function sendJson(response, statusCode, body) {
response.writeHead(statusCode, {
'Content-Type': 'application/json; charset=utf-8',
'Cache-Control': 'no-store'
});
response.end(JSON.stringify(body));
}
async function checkDependency() {
// Якщо критичної зовнішньої залежності немає,
// додаткова перевірка не потрібна.
if (!dependencyUrl) {
return true;
}
const controller = new AbortController();
const timeout = setTimeout(() => {
controller.abort();
}, 1000);
try {
const dependencyResponse = await fetch(dependencyUrl, {
signal: controller.signal
});
return dependencyResponse.ok;
} catch {
return false;
} finally {
clearTimeout(timeout);
}
}
async function isReady() {
if (!startupComplete) {
return false;
}
return checkDependency();
}
const server = http.createServer(async (request, response) => {
if (request.method !== 'GET') {
sendJson(response, 405, { status: 'method_not_allowed' });
return;
}
if (request.url === '/health/live') {
// Liveness не перевіряє зовнішні залежності.
sendJson(response, 200, { status: 'ok' });
return;
}
if (request.url === '/health/ready') {
const ready = await isReady();
sendJson(
response,
ready ? 200 : 503,
{ status: ready ? 'ok' : 'not_ready' }
);
return;
}
sendJson(response, 404, { status: 'not_found' });
});
server.listen(port, () => {
console.log(`Сервер запущено на порту ${port}`);
});
function shutdown(signal) {
console.log(`Отримано ${signal}, починаємо завершення роботи`);
// Після закриття сервера нові робочі запити не приймаються.
server.close((error) => {
if (error) {
console.error('Помилка під час завершення роботи сервера', error);
process.exitCode = 1;
return;
}
process.exit(0);
});
}
process.on('SIGTERM', () => shutdown('SIGTERM'));
process.on('SIGINT', () => shutdown('SIGINT'));Збережіть код у файл server.js і запустіть:
node server.jsОдразу після запуску liveness уже буде успішним:
curl -i http://localhost:3000/health/liveReadiness протягом першої секунди поверне 503:
curl -i http://localhost:3000/health/readyПісля завершення початкової ініціалізації readiness почне повертати 200:
curl -i http://localhost:3000/health/readyДля демонстрації можна запустити сервіс зі змінною DEPENDENCY_URL:
DEPENDENCY_URL=http://localhost:4000/health node server.jsУ такому випадку /health/ready поверне 503, якщо на порту 4000 немає доступного HTTP-сервісу.
Перевірка має обмежений час очікування. Це важливо, тому що readiness-ендпоїнт не повинен зависати через повільну залежність. У прикладі тайм-аут становить одну секунду.
Значення HTTP-відповіді залежності обробляється так:
статуси 200–299 вважаються успішними через властивість response.ok;
помилка мережі означає, що залежність недоступна;
перевищення тайм-ауту також означає, що сервіс не готовий.
Сервіс може приймати TCP-з'єднання ще до того, як завершиться його ініціалізація. Саме для цього потрібен readiness check.
Типовий порядок:
процес Node.js запускається;
liveness починає відповідати;
сервіс підключається до залежностей;
завершується початкова ініціалізація;
readiness починає повертати 200.
Поки кроки 3–4 не завершені, сервіс не повинен отримувати робочий трафік.
Під час контрольованого завершення роботи сервіс повинен перестати приймати новий трафік. Обробник SIGTERM у прикладі викликає server.close().
Це дає змогу:
не приймати нові з'єднання;
завершити вже розпочаті запити;
коректно звільнити ресурси.
У реальному застосунку стан завершення часто також враховують у readiness-перевірці: після отримання сигналу завершення readiness має стати невдалим.
Для health-ендпоїнтів достатньо стабільного мінімального формату:
{"status":"ok"}або:
{"status":"not_ready"}Не варто повертати клієнтам:
паролі;
рядки підключення;
токени;
повні повідомлення помилок від бази даних;
внутрішні адреси сервісів;
стеки викликів.
Health-ендпоїнти часто доступні внутрішній інфраструктурі, але це не означає, що їхній вміст потрібно робити надто детальним.
Для перевірок стану зазвичай достатньо таких кодів:
200 OK — перевірка успішна;
503 Service Unavailable — сервіс тимчасово не готовий;
404 Not Found — невідомий шлях;
405 Method Not Allowed — використано непідтримуваний HTTP-метод.
Відповідь 503 для readiness не означає, що процес зламаний. Вона повідомляє, що сервіс зараз не повинен отримувати робочі запити.
Якщо liveness перевіряє базу даних і база тимчасово недоступна, система може без потреби перезапустити весь процес.
Рішення: залишайте liveness локальною перевіркою здатності процесу відповідати.
200, коли сервіс не готовийЯкщо readiness завжди повертає 200, балансувальник продовжить надсилати трафік до сервісу, який ще запускається або втратив критичну залежність.
Рішення: повертайте 503, доки сервіс не готовий.
Зовнішня перевірка без тайм-ауту може зависнути. У результаті сам readiness-ендпоїнт не зможе швидко відповісти.
Рішення: обмежуйте час очікування через AbortController або інший механізм тайм-ауту.
Повернення технічної інформації з health-ендпоїнта може розкрити внутрішню структуру системи.
Рішення: залишайте публічну відповідь короткою, а деталі записуйте в журнали сервісу.
Health check не повинен перевіряти всю бізнес-логіку застосунку. Велика кількість складних перевірок збільшує навантаження й робить результат нестабільним.
Рішення: перевіряйте лише ті умови, без яких сервіс справді не може приймати робочі запити.
Liveness відповідає на питання: «Чи живий процес?»
Readiness відповідає на питання: «Чи можна надсилати цьому сервісу робочі запити?»
Liveness має бути простою та не залежати від бази даних чи зовнішніх API.
Readiness може перевіряти критичні залежності та стан початкової ініціалізації.
Успішна перевірка повертає 200, а неготовність — 503.
Перевірки зовнішніх залежностей повинні мати тайм-аут.
Відповіді health-ендпоїнтів мають містити мінімум безпечної інформації.