Пошук уроків, статей та іншого контенту
Підключите Terminus і реалізуєте health indicators для бази даних, диска та зовнішніх сервісів.
Health check — це спеціальний endpoint, який повідомляє, чи готовий застосунок обробляти запити.
Зазвичай перевіряють:
доступність бази даних;
наявність вільного місця на диску;
доступність зовнішніх HTTP-сервісів;
інші критичні залежності застосунку.
У NestJS для цього використовується пакет @nestjs/terminus. Він містить готові health indicators — перевірки для типових інфраструктурних компонентів.
Health endpoint часто використовують:
оркестратори контейнерів;
балансувальники навантаження;
системи моніторингу;
CI/CD-процеси;
команди підтримки під час діагностики проблем.
Для прикладу використаємо TypeORM із SQLite та HTTP-клієнт NestJS:
npm install @nestjs/terminus @nestjs/axios @nestjs/typeorm typeorm sqlite3@nestjs/terminus надає health checks, а @nestjs/axios потрібен для перевірки зовнішніх HTTP-сервісів через HttpHealthIndicator.
Створимо контролер із endpoint GET /health.
import { Module } from '@nestjs/common';
import { HttpModule } from '@nestjs/axios';
import { TypeOrmModule } from '@nestjs/typeorm';
import { TerminusModule } from '@nestjs/terminus';
import { HealthController } from './health.controller';
@Module({
imports: [
TerminusModule,
HttpModule,
TypeOrmModule.forRoot({
type: 'sqlite',
database: ':memory:',
autoLoadEntities: true,
synchronize: true,
}),
],
controllers: [HealthController],
})
export class AppModule {}У реальному застосунку конфігурацію TypeORM зазвичай виносять у змінні середовища. SQLite in-memory використовується тут лише для простого прикладу: застосунок не потребує окремого сервера бази даних.
Створимо health.controller.ts:
import { Controller, Get } from '@nestjs/common';
import {
DiskHealthIndicator,
HealthCheck,
HealthCheckService,
HttpHealthIndicator,
TypeOrmHealthIndicator,
} from '@nestjs/terminus';
@Controller('health')
export class HealthController {
constructor(
private readonly health: HealthCheckService,
private readonly database: TypeOrmHealthIndicator,
private readonly disk: DiskHealthIndicator,
private readonly http: HttpHealthIndicator,
) {}
@Get()
@HealthCheck()
check() {
return this.health.check([
() => this.database.pingCheck('database'),
() =>
this.disk.checkStorage('storage', {
path: process.cwd(),
thresholdPercent: 0.9,
}),
() =>
this.http.pingCheck(
'external-api',
'https://example.com',
),
]);
}
}Тепер після запуску застосунку endpoint буде доступний за адресою:
GET /healththis.database.pingCheck('database')TypeOrmHealthIndicator перевіряє, чи може TypeORM виконати операцію через підключене джерело даних.
Рядок 'database' — це назва перевірки, яка з’явиться у відповіді.
Якщо підключення до бази даних недоступне, health check вважатиметься невдалим.
this.disk.checkStorage('storage', {
path: process.cwd(),
thresholdPercent: 0.9,
})У цьому прикладі перевіряється файлова система, на якій розташована поточна робоча директорія застосунку.
thresholdPercent: 0.9 означає, що перевірка вважається успішною, якщо використано не більше 90% доступного простору. Якщо диск заповнений більше ніж на 90%, перевірка завершиться помилкою.
Для контейнерного застосунку часто перевіряють кореневу файлову систему:
this.disk.checkStorage('storage', {
path: '/',
thresholdPercent: 0.9,
})Однак process.cwd() є зручнішим переносним варіантом для локального запуску.
this.http.pingCheck(
'external-api',
'https://example.com',
)HttpHealthIndicator надсилає HTTP-запит до вказаної адреси. Якщо сервіс недоступний або запит завершується помилкою, перевірка буде невдалою.
У реальному проєкті замініть адресу на критичний для вашого застосунку сервіс, наприклад платіжний шлюз або внутрішній API.
Якщо всі перевірки успішні, Terminus повертає відповідь зі статусом HTTP 200:
{
"status": "ok",
"info": {
"database": {
"status": "up"
},
"storage": {
"status": "up"
},
"external-api": {
"status": "up"
}
},
"error": {},
"details": {
"database": {
"status": "up"
},
"storage": {
"status": "up"
},
"external-api": {
"status": "up"
}
}
}Якщо хоча б одна критична перевірка не пройшла, статус буде error, а HTTP-відповідь матиме статус 503 Service Unavailable.
Приклад структури помилки:
{
"status": "error",
"info": {
"database": {
"status": "up"
}
},
"error": {
"external-api": {
"status": "down",
"message": "..."
}
},
"details": {
"database": {
"status": "up"
},
"external-api": {
"status": "down",
"message": "..."
}
}
}Це дозволяє інфраструктурі автоматично зрозуміти, що екземпляр застосунку не готовий обробляти запити.
Метод health.check() приймає масив функцій-перевірок:
return this.health.check([
() => this.database.pingCheck('database'),
() => this.disk.checkStorage('storage', options),
() => this.http.pingCheck('external-api', url),
]);Кожна функція повертає Promise. Terminus запускає перевірки та об’єднує їхні результати в одну відповідь.
Перевірки мають бути короткими. Health endpoint не повинен виконувати складні запити, завантажувати великі об’єми даних або запускати бізнес-операції.
Не кожна зовнішня система однаково важлива для доступності застосунку.
Наприклад:
база даних може бути критичною;
платіжний сервіс може бути критичним лише для окремих endpoint;
аналітичний сервіс може бути некритичним.
Якщо додати всі залежності до одного health check, тимчасова недоступність другорядного сервісу може зробити весь застосунок «нездоровим».
Тому набір перевірок потрібно визначати відповідно до призначення endpoint. Для критичних залежностей використовуйте перевірки, які впливають на загальний статус застосунку.
У складніших системах часто створюють два endpoint:
GET /health/live — процес застосунку працює;
GET /health/ready — застосунок готовий приймати трафік.
Перевірка liveness зазвичай не повинна залежати від бази даних або зовнішніх сервісів. Якщо база тимчасово недоступна, перезапуск контейнера не завжди є правильним рішенням.
Перевірка readiness, навпаки, може містити базу даних, диск і необхідні зовнішні API.
Приклад окремого readiness endpoint:
import { Controller, Get } from '@nestjs/common';
import {
HealthCheck,
HealthCheckService,
TypeOrmHealthIndicator,
} from '@nestjs/terminus';
@Controller('health')
export class HealthController {
constructor(
private readonly health: HealthCheckService,
private readonly database: TypeOrmHealthIndicator,
) {}
@Get('live')
live() {
return {
status: 'ok',
};
}
@Get('ready')
@HealthCheck()
ready() {
return this.health.check([
() => this.database.pingCheck('database'),
]);
}
}У цьому варіанті live лише показує, що Node.js-процес відповідає, а ready додатково перевіряє базу даних.
Health check не повинен надовго блокувати відповідь через повільний зовнішній сервіс. Для HTTP-перевірки можна передати параметри запиту, зокрема тайм-аут через Axios-конфігурацію:
this.http.pingCheck(
'external-api',
'https://example.com',
{
timeout: 3000,
},
)Точні параметри залежать від версії Terminus та Axios, тому їх потрібно узгоджувати з версіями встановлених пакетів.
Головна ідея — зовнішня перевірка має завершуватися за обмежений час. Інакше кілька повільних залежностей можуть створити додаткове навантаження на застосунок.
Нижче наведено мінімальний приклад модуля та контролера, який можна використати в новому NestJS-застосунку.
app.module.ts:
import { Module } from '@nestjs/common';
import { HttpModule } from '@nestjs/axios';
import { TypeOrmModule } from '@nestjs/typeorm';
import { TerminusModule } from '@nestjs/terminus';
import { HealthController } from './health.controller';
@Module({
imports: [
TerminusModule,
HttpModule,
TypeOrmModule.forRoot({
type: 'sqlite',
database: ':memory:',
autoLoadEntities: true,
synchronize: true,
}),
],
controllers: [HealthController],
})
export class AppModule {}health.controller.ts:
import { Controller, Get } from '@nestjs/common';
import {
DiskHealthIndicator,
HealthCheck,
HealthCheckService,
HttpHealthIndicator,
TypeOrmHealthIndicator,
} from '@nestjs/terminus';
@Controller('health')
export class HealthController {
constructor(
private readonly health: HealthCheckService,
private readonly database: TypeOrmHealthIndicator,
private readonly disk: DiskHealthIndicator,
private readonly http: HttpHealthIndicator,
) {}
@Get()
@HealthCheck()
check() {
return this.health.check([
() => this.database.pingCheck('database'),
() =>
this.disk.checkStorage('storage', {
path: process.cwd(),
thresholdPercent: 0.9,
}),
() =>
this.http.pingCheck(
'external-api',
'https://example.com',
{
timeout: 3000,
},
),
]);
}
}Після запуску:
npm run start:devперевірте endpoint:
curl http://localhost:3000/healthДля кожного indicator потрібні відповідні модулі:
TerminusModule — для базової функціональності Terminus;
HttpModule — для HttpHealthIndicator;
TypeOrmModule — для TypeOrmHealthIndicator.
Якщо залежність не імпортована, NestJS не зможе створити потрібний сервіс.
Шлях / зазвичай підходить для Linux-контейнера, але не завжди зручний для локального запуску на Windows. Для переносного прикладу використовуйте:
path: process.cwd()Не використовуйте health endpoint для:
повного сканування таблиць;
перевірки великої кількості записів;
виконання бізнес-логіки;
надсилання великих HTTP-запитів.
Health check має швидко відповідати на питання: чи доступна критична залежність.
Без обмеження часу повільний сервіс може затримати відповідь /health. Додавайте невеликий тайм-аут і враховуйте, що health check сам створює зовнішній HTTP-трафік.
Якщо додати до перевірки необов’язковий сервіс, його короткочасна недоступність може призвести до вилучення робочого екземпляра з балансувальника.
Включайте до readiness-перевірки лише ті залежності, без яких застосунок справді не може працювати.
@nestjs/terminus спрощує створення health endpoint у NestJS.
TypeOrmHealthIndicator перевіряє підключення до бази даних.
DiskHealthIndicator перевіряє доступний простір на диску.
HttpHealthIndicator перевіряє доступність зовнішніх HTTP-сервісів.
Декоратор @HealthCheck() інтегрує перевірки з HTTP-відповіддю NestJS.
Успішний health check повертає статус 200, а невдалий — 503.
Критичні та некритичні залежності варто розділяти.
Для складних систем корисно мати окремі liveness і readiness endpoint.