Пошук уроків, статей та іншого контенту
Створите readiness- і liveness-перевірки для контролю доступності залежностей та сервісу.
Перевірка стану — це спеціальний HTTP-метод, який повідомляє, чи може застосунок нормально працювати.
Зазвичай створюють два типи перевірок:
liveness — застосунок запущений і відповідає на запити;
readiness — застосунок готовий обробляти запити, а його необхідні залежності доступні.
Ці перевірки можуть використовувати:
оркестратори контейнерів;
балансувальники навантаження;
системи моніторингу;
CI/CD-процеси.
Наприклад, застосунок може бути запущений, але база даних ще недоступна. У такому разі liveness-перевірка має пройти успішно, а readiness-перевірка — завершитися помилкою.
Для створення health checks у NestJS використовується пакет @nestjs/terminus.
npm install @nestjs/terminusПакет надає:
HealthCheckService для запуску перевірок;
декоратор @HealthCheck();
базовий клас HealthIndicator для власних перевірок;
стандартний формат відповіді та HTTP-статуси.
Створимо власний індикатор, який перевіряє доступність іншого HTTP-сервісу.
Нехай адреса залежності зберігається в змінній середовища DEPENDENCY_URL. Якщо змінну не задано, буде використано http://localhost:3001/health.
Файл src/health/dependency.health.ts:
import { Injectable } from '@nestjs/common';
import {
HealthCheckError,
HealthIndicator,
HealthIndicatorResult,
} from '@nestjs/terminus';
@Injectable()
export class DependencyHealthIndicator extends HealthIndicator {
async check(key: string): Promise<HealthIndicatorResult> {
const url =
process.env.DEPENDENCY_URL ?? 'http://localhost:3001/health';
const controller = new AbortController();
// Перериваємо запит, якщо залежність не відповідає протягом двох секунд
const timeout = setTimeout(() => controller.abort(), 2000);
try {
const response = await fetch(url, {
signal: controller.signal,
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return this.getStatus(key, true, {
url,
});
} catch {
throw new HealthCheckError(
'Залежність недоступна',
this.getStatus(key, false, {
url,
}),
);
} finally {
clearTimeout(timeout);
}
}
}У цьому прикладі:
виконується HTTP-запит до залежності;
успішною вважається відповідь із HTTP-статусом від 200 до 299;
запит переривається після двох секунд;
якщо залежність недоступна, створюється HealthCheckError;
Terminus сформує відповідь із помилковим станом.
Для роботи глобального fetch потрібен Node.js 18 або новіший.
Створимо контролер із двома маршрутами:
GET /health/live;
GET /health/ready.
Файл src/health/health.controller.ts:
import { Controller, Get } from '@nestjs/common';
import {
HealthCheck,
HealthCheckService,
} from '@nestjs/terminus';
import { DependencyHealthIndicator } from './dependency.health';
@Controller('health')
export class HealthController {
constructor(
private readonly health: HealthCheckService,
private readonly dependency: DependencyHealthIndicator,
) {}
@Get('live')
@HealthCheck()
checkLiveness() {
return this.health.check([]);
}
@Get('ready')
@HealthCheck()
checkReadiness() {
return this.health.check([
() => this.dependency.check('dependency'),
]);
}
}return this.health.check([]);Для liveness-перевірки не передається жодна залежність. Якщо NestJS може обробити цей запит, застосунок вважається живим.
Така перевірка не повинна звертатися до:
бази даних;
зовнішніх API;
черг повідомлень;
інших сервісів.
Якщо додати залежності до liveness-перевірки, тимчасова проблема із зовнішнім сервісом може призвести до перезапуску самого застосунку.
Readiness-перевірка запускає індикатор залежності:
return this.health.check([
() => this.dependency.check('dependency'),
]);Якщо залежність доступна, перевірка повертає успішний результат. Якщо ні — Terminus повертає помилку, а HTTP-статус відповіді буде 503 Service Unavailable.
Створимо модуль для перевірок стану.
Файл src/health/health.module.ts:
import { Module } from '@nestjs/common';
import { TerminusModule } from '@nestjs/terminus';
import { DependencyHealthIndicator } from './dependency.health';
import { HealthController } from './health.controller';
@Module({
imports: [TerminusModule],
controllers: [HealthController],
providers: [DependencyHealthIndicator],
})
export class HealthModule {}Підключимо його в головному модулі застосунку.
Файл src/app.module.ts:
import { Module } from '@nestjs/common';
import { HealthModule } from './health/health.module';
@Module({
imports: [HealthModule],
})
export class AppModule {}Тепер запустіть застосунок:
npm run start:devДля перевірки readiness можна задати адресу залежності:
DEPENDENCY_URL=http://localhost:3001/health npm run start:devАбо встановити змінну середовища окремо перед запуском:
export DEPENDENCY_URL=http://localhost:3001/health
npm run start:devЯкщо залежність доступна, запит:
curl http://localhost:3000/health/liveповерне відповідь зі статусом 200:
{
"status": "ok",
"info": {},
"error": {},
"details": {}
}Readiness-перевірка за доступної залежності також поверне статус 200:
curl http://localhost:3000/health/readyПриклад відповіді:
{
"status": "ok",
"info": {
"dependency": {
"status": "up",
"url": "http://localhost:3001/health"
}
},
"error": {},
"details": {
"dependency": {
"status": "up",
"url": "http://localhost:3001/health"
}
}
}Якщо залежність недоступна, /health/ready поверне HTTP-статус 503:
{
"status": "error",
"info": {},
"error": {
"dependency": {
"status": "down",
"url": "http://localhost:3001/health"
}
},
"details": {
"dependency": {
"status": "down",
"url": "http://localhost:3001/health"
}
}
}Readiness може перевіряти кілька залежностей. Для цього додайте кілька функцій до масиву:
@Get('ready')
@HealthCheck()
checkReadiness() {
return this.health.check([
() => this.dependency.check('database'),
() => this.dependency.check('payments'),
]);
}У реальному застосунку для різних типів залежностей зазвичай створюють окремі індикатори:
перевірку підключення до бази даних;
перевірку Redis;
перевірку брокера повідомлень;
перевірку зовнішнього HTTP API.
Усі критично необхідні залежності варто включати саме до readiness-перевірки.
| Перевірка | Що перевіряє | Коли завершується помилкою | |---|---|---| | Liveness | Чи працює процес застосунку | Коли застосунок не відповідає | | Readiness | Чи готовий застосунок обробляти запити | Коли недоступна критична залежність |
Liveness відповідає на запитання:
Чи потрібно перезапустити застосунок?
Readiness відповідає на запитання:
Чи можна направляти до цього застосунку нові запити?
Якщо база даних тимчасово недоступна, liveness-перевірка стане невдалою. Оркестратор може вирішити, що застосунок зламаний, і перезапустити його.
Залежності потрібно перевіряти в readiness.
HTTP-запит до залежності може зависнути. Без тайм-ауту health check також може довго не завершуватися.
Для кожної зовнішньої перевірки встановлюйте обмеження часу очікування.
Health check має бути швидким і простим. Не слід виконувати в ньому важкі запити або повні бізнес-операції.
Health check може розкривати технічну інформацію про залежності. Якщо застосунок доступний з інтернету, продумайте, хто має право викликати такі маршрути.
Liveness і readiness мають різне призначення. Об'єднання їх в один маршрут ускладнює правильне налаштування моніторингу та перезапусків.
@nestjs/terminus спрощує створення health checks у NestJS.
Liveness перевіряє, чи відповідає сам застосунок.
Readiness перевіряє готовність застосунку та доступність критичних залежностей.
Liveness не повинна залежати від бази даних або зовнішніх сервісів.
Для перевірки зовнішніх залежностей потрібно використовувати тайм-аут.
Якщо readiness-перевірка не проходить, Terminus повертає HTTP-статус 503.