Terminus

장현욱(Artlogy)·2022년 11월 17일
0

Nest.js

목록 보기
15/18
post-thumbnail

Server Health Check

서비스를 운용하다 보면 트래픽이 늘어나거나 DB에 부하가 생기기도 한다.
장애는 언제 어디에서나 발생 할 수 있기때문에 서버의 건강(?)파악은 늘 체크 할 수 있어야한다.
Nest에는 Terminus라는 헬스 체크 패키지를 제공한다.

  • HttpHealthIndicator
  • TypeOrmHealthIndicator
  • MongooseHealthIndicator
  • SequelizeHealthIndicator
  • MicroserviceHealthIndicator
  • GRPCHealthIndicator
  • MemoryHealthIndicator
  • DiskHealthIndicator

패키지 설치

#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 {}

Http Check

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;
}

TypeOrm (DB) Check

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'),
    ]);
  }
}

Custom Check

terminus에서 제공하지 않는 상태표시기가 필요하다면, HealthIndicator를 상속받는 상태 표시기를 직접 만들 수 있다.

export declare abstract class HealthIndicator{
	prorected getStatus(key: string, isHealthy:boolean, data?:{
    	[key:string]:any;
    }): HealthIndicatorResult;
}
  • key : 상태를 나타냄
  • isHealthy: 상태 측정 결과
  • data : 결과를 포함시킬 문자열

구현

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으로 표시 된걸 볼 수있다.

0개의 댓글