서비스를 운용하다 보면 트래픽이 늘어나거나 DB에 부하가 생기기도 한다.
장애는 언제 어디에서나 발생 할 수 있기때문에 서버의 건강(?)파악은 늘 체크 할 수 있어야한다.
Nest에는 Terminus라는 헬스 체크 패키지를 제공한다.
#npm
$ npm i -s @nestjs/terminus
#yarn
$ yarn add @nestjs/terminus
상태확인은 특정 라우터의 앤드포인터로 요청을 보내고 응답을 확인 하는 방법을 사용 하기 때문에
HealthCheck
Controller를 만들어 줄 것이다.
$ nest g co health-check
import { TerminusModule } from '@nestjs/terminus';
import { HealthCheckController } from './health-check/health-check.controller';
...
@Module({
imports: [TerminusModule],
providers: [HealthCheckController],
...
})
export class AppModule {}
HttpHealthIndicator 동작과정에서 @nestjs/axios
를 필요로 한다.
#npm
$ npm i -s @nestjs/axios
#yarn
$ yarn add @nestjs/axios
axios에서 제공하는 HttpModule또한 필요하기때문에 등록한다.
import { HttpModule } from '@nestjs/axios';
import { TerminusModule } from '@nestjs/terminus';
import { HealthCheckController } from 'health-check.controller';
...
@Module({
imports: [TerminusModule, HttpModule],
providers: [HealthCheckController],
...
})
export class AppModule {}
import { Controller, Get } from '@nestjs/common';
import { HealthCheckService, HttpHealthIndicator, HealthCheck } from '@nestjs/terminus';
@Controller('health-check')
export class HealthCheckController {
constructor(
private health: HealthCheckService,
private http: HttpHealthIndicator,
) { }
@Get()
@HealthCheck()
check() {
return this.health.check([
() => this.http.pingCheck('nestjs-docs', 'https://docs.nestjs.com'),
]);
}
}
https://docs.nestjs.com
에 요청을 보내서 응답을 잘받으면 첫번째 인자인nestjs-docs
에 결과를 넣어주는 코드이다.
curl : http://localhost:8084/health-check
{
"status": "ok",
"info": {
"nestjs-docs": {
"status": "up"
}
},
"error": {},
"details": {
"nestjs-docs": {
"status": "up"
}
}
}
여기서 up
은 정상작동한다는 뜻이다.
응답객체는 다음과 같은 스키마를 가진다.
export interface HealthCheckResult {
// 헬스 체크를 수행한 전반적인 상태. 'error' | 'ok' | 'shutting_down' 값을 가짐
status: HealthCheckStatus;
// 상태가 "up" 일때의 상태 정보
info?: HealthIndicatorResult;
// 상태가 "down" 일때의 상태 정보
error?: HealthIndicatorResult;
// 모든 상태 표시기의 정보
details: HealthIndicatorResult;
}
TypeOrmHealthIndicator
는 단순히 DB가 잘 살아있는지 확인한다.
import { ApiGuard } from '@config/decorator/role.decorator';
import { Controller, Get } from '@nestjs/common';
import {
HealthCheck,
HealthCheckService,
HttpHealthIndicator,
TypeOrmHealthIndicator,
} from '@nestjs/terminus';
@Controller('health-check')
export class HealthCheckController {
constructor(
private readonly health: HealthCheckService,
private readonly http: HttpHealthIndicator,
private readonly db: TypeOrmHealthIndicator,
) {}
@Get()
@ApiGuard({ summary: 'Http Healthy Ping Check' })
@HealthCheck()
checkPing() {
return this.health.check([
() => this.http.pingCheck('nestjs-docs', 'https://docs.nestjs.com'),
() => this.db.pingCheck('database'),
]);
}
}
terminus에서 제공하지 않는 상태표시기가 필요하다면, HealthIndicator
를 상속받는 상태 표시기를 직접 만들 수 있다.
export declare abstract class HealthIndicator{
prorected getStatus(key: string, isHealthy:boolean, data?:{
[key:string]:any;
}): HealthIndicatorResult;
}
import { Injectable } from '@nestjs/common';
import { HealthIndicator, HealthIndicatorResult, HealthCheckError } from '@nestjs/terminus';
export interface Dog {
name: string;
type: string;
}
@Injectable()
export class DogHealthIndicator extends HealthIndicator {
private dogs: Dog[] = [
{ name: 'Fido', type: 'goodboy' },
{ name: 'Rex', type: 'badboy' },
];
async isHealthy(key: string): Promise<HealthIndicatorResult> {
//나쁜 개들 필터링
const badboys = this.dogs.filter(dog => dog.type === 'badboy');
// 나쁜개가 없으면 true 아닐 경우 false
const isHealthy = badboys.length === 0;
//key, 상태결과, 나쁜개의 수를 대입
const result = this.getStatus(key, isHealthy, { badboys: badboys.length });
//나쁜개가 없을 경우 결과 반환
if (isHealthy) {
return result;
}
//나쁜개가 있을경우 예외를 반환
throw new HealthCheckError('Dogcheck failed', result);
}
}
위는 예시로 작성한 커스텀 헬스 체크 로직이다.
이렇게 구현하고 나면 global이나 app 모듈에 provider로 등록해준다.
...
import { DogHealthIndicator } from './health-check/dog.health';
@Module({
...
providers: [DogHealthIndicator]
})
export class AppModule { }
import { Controller, Get } from '@nestjs/common';
import { HealthCheckService, HttpHealthIndicator, HealthCheck, TypeOrmHealthIndicator } from '@nestjs/terminus';
import { DogHealthIndicator } from './dog.health';
@Controller('health-check')
export class HealthCheckController {
constructor(
private health: HealthCheckService,
private http: HttpHealthIndicator,
private db: TypeOrmHealthIndicator,
private dogHealthIndicator: DogHealthIndicator,
) { }
@Get()
@HealthCheck()
check() {
return this.health.check([
() => this.http.pingCheck('nestjs-docs', 'https://docs.nestjs.com'),
() => this.db.pingCheck('database'),
() => this.dogHealthIndicator.isHealthy('dog'),
]);
}
}
$ curl http://localhost:8084/health-check
{
"status": "error",
"info": {
"nestjs-docs": {
"status": "up"
},
"database": {
"status": "up"
}
},
"error": {
"dog": {
"status": "down",
"badboys": 1
}
},
"details": {
"nestjs-docs": {
"status": "up"
},
"database": {
"status": "up"
},
"dog": {
"status": "down",
"badboys": 1
}
}
}
Rex라는 이름의 강아지가 벹보이 였기땜에 헬스체크 status가 down으로 표시 된걸 볼 수있다.