Health checks (Terminus)
terminusλ μ μμ μΈ μ’
λ£μ λ°μνκΈ°μν νν¬λ₯Ό μ 곡νλ©° λͺ¨λ HTTP μμ© νλ‘κ·Έλ¨μ λν μ μ ν Kubernetes readiness/liveness νμΈμ μμ±νλλ‘ μ§μν©λλ€. @nestjs/terminus λͺ¨λμ ν°λ―Έλ λΌμ΄λΈλ¬λ¦¬λ₯Ό Nest μμ½ μμ€ν
κ³Ό ν΅ν©ν©λλ€.
Getting started
@nestjs/terminus
λ₯Ό μμνλ €λ©΄ νμν μμ‘΄μ±μ μ€μΉν΄μΌν©λλ€.
$ npm install --save @nestjs/terminus @godaddy/terminus
Setting up a health check
μν νμΈμ μν νμκΈ°μ μμ½μ λνλ
λλ€. μν νμκΈ°λ μλΉμ€ μνμ κ΄κ³μμ΄ μλΉμ€ κ²μ¬λ₯Ό μ€νν©λλ€. ν λΉλ λͺ¨λ μν νμκΈ°κ° μλλμ΄ μ€νμ€μΈ κ²½μ° μν νμΈμ κΈμ μ μ
λλ€. λ§μ μμ© νλ‘κ·Έλ¨μ΄ μ μ¬ν μν νμκΈ°λ₯Ό νμλ‘ νκΈ° λλ¬Έμ @nestjs/terminusλ λ€μκ³Ό κ°μ 미리 μ μλ μν νμκΈ° μ§ν©μ μ 곡ν©λλ€.
MicroserviceHealthIndicator
DNS Health Check
첫λ²μ§Έ μν νμΈμ μμνλ 첫λ²μ§Έ λ¨κ³λ μν νμκΈ°λ₯Ό μλ ν¬μΈνΈμ μ°κ²°νλ μλΉμ€λ₯Ό μ€μ νλ κ²μ
λλ€.
@@filename(terminus-options.service)
import {
TerminusEndpoint,
TerminusOptionsFactory,
DNSHealthIndicator,
TerminusModuleOptions
} from '@nestjs/terminus';
import { Injectable } from '@nestjs/common';
@Injectable()
export class TerminusOptionsService implements TerminusOptionsFactory {
constructor(
private readonly dns: DNSHealthIndicator,
) {}
createTerminusOptions(): TerminusModuleOptions {
const healthEndpoint: TerminusEndpoint = {
url: '/health',
healthIndicators: [
async () => this.dns.pingCheck('google', 'https://google.com'),
],
};
return {
endpoints: [healthEndpoint],
};
}
}
@@switch
import { Injectable, Dependencies } from '@nestjs/common';
import { DNSHealthIndicator } from '@nestjs/terminus';
@Injectable()
@Dependencies(DNSHealthIndicator)
export class TerminusOptionsService {
constructor(dns) {
this.dns = dns;
}
createTerminusOptions() {
const healthEndpoint = {
url: '/health',
healthIndicators: [
async () => this.dns.pingCheck('google', 'https://google.com'),
],
};
return {
endpoints: [healthEndpoint],
};
}
}
μΌλ¨ TerminusOptionsService
λ₯Ό μ€μ νλ©΄, TerminusModule
μ λ£¨νΈ ApplicationModule
λ‘ κ°μ Έμ¬ μ μμ΅λλ€. TerminusOptionsService
λ μ€μ μ μ 곡νλ©°,μ΄ μ€μ μ TerminusModule
μ μν΄ μ¬μ©λ©λλ€.
@@filename(app.module)
import { Module } from '@nestjs/common';
import { TerminusModule } from '@nestjs/terminus';
import { TerminusOptionsService } from './terminus-options.service';
@Module({
imports: [
TerminusModule.forRootAsync({
useClass: TerminusOptionsService,
}),
],
})
export class ApplicationModule { }
info ννΈ μ¬λ°λ₯΄κ² μνλλ©΄ Nestλ GET μμ²μ ν΅ν΄ μ μ λ κ²½λ‘μ λλ¬ ν μμλ μ μ λ μν μ κ²μ λ
ΈμΆν©λλ€. μλ₯Ό λ€μ΄ curl -X GET 'http://localhost:3000/health'
Custom health indicator
κ²½μ°μ λ°λΌ @nestjs/terminus
μμ μ 곡νλ μ¬μ μ μλ μν νμκΈ°κ° λͺ¨λ μν νμΈ μꡬ μ¬νμ λ€λ£¨μ§ μμ΅λλ€. μ΄ κ²½μ° νμμ λ°λΌ μ¬μ©μ μ μ μν νμκΈ°λ₯Ό μ€μ ν μ μμ΅λλ€.
λ§μΆ€ν μν νμΈμ λνλ΄λ μλΉμ€λ₯Ό λ§λ€μ΄ μμνκ² μ΅λλ€. μν νμΈμ΄ μ΄λ»κ² ꡬμ±λμ΄ μλμ§μ λν κΈ°λ³Έ μ§μμ μ»κΈ° μν΄ DogHealthIndicator
μμ λ₯Ό λ§λ€ κ²μ
λλ€. λͺ¨λ Dog
μ€λΈμ νΈμ goodboy
μ νμ΄ μλ κ²½μ°μ΄ Health νμκΈ°μ μνλ "up"μ΄μ΄μΌ ν©λλ€. κ·Έλ μ§ μμΌλ©΄ μ€λ₯κ° λ°μνκ³ Health νμκΈ°λ "down"μΌλ‘ νμλ©λλ€.
@@filename(dog.health)
import { Injectable } from '@nestjs/common';
import { HealthCheckError } from '@godaddy/terminus';
import { HealthIndicatorResult } from '@nestjs/terminus';
export interface Dog {
name: string;
type: string;
}
@Injectable()
export class DogHealthIndicator extends HealthIndicator {
private readonly 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');
const isHealthy = badboys.length === 0;
const result = this.getStatus(key, isHealthy, { badboys: badboys.length });
if (isHealthy) {
return result;
}
throw new HealthCheckError('Dogcheck failed', result);
}
}
@@switch
import { Injectable } from '@nestjs/common';
import { HealthCheckError } from '@godaddy/terminus';
@Injectable()
export class DogHealthIndicator extends HealthIndicator {
dogs = [
{ name: 'Fido', type: 'goodboy' },
{ name: 'Rex', type: 'badboy' },
];
async isHealthy(key) {
const badboys = this.dogs.filter(dog => dog.type === 'badboy');
const isHealthy = badboys.length === 0;
const result = this.getStatus(key, isHealthy, { badboys: badboys.length });
if (isHealthy) {
return result;
}
throw new HealthCheckError('Dogcheck failed', result);
}
}
λ€μμΌλ‘ ν΄μΌ ν μΌμ μν νμκΈ°λ₯Ό 곡κΈμλ‘ λ±λ‘νλ κ²μ
λλ€.
@@filename(app.module)
import { Module } from '@nestjs/common';
import { TerminusModule } from '@nestjs/terminus';
import { TerminusOptions } from './terminus-options.service';
import { DogHealthIndicator } from './dog.health.ts';
@Module({
imports: [
TerminusModule.forRootAsync({
imports: [ApplicationModule],
useClass: TerminusOptionsService,
}),
],
providers: [DogHealthIndicator],
exports: [DogHealthIndicator],
})
export class ApplicationModule { }
info ννΈ μ€μ μμ© νλ‘κ·Έλ¨μμ DogHealthIndicator
λ λ³λμ λͺ¨λ (μ: DogsModule
)λ‘ μ 곡λμ΄μΌνλ©°, κ·Έλ° λ€μ ApplicationModule
μμ κ°μ Έμ΅λλ€. κ·Έλ¬λ DogHealthIndicator
λ₯Ό DogModule
μ exports
λ°°μ΄μ μΆκ°νκ³ TerminusModule.forRootAsync()
νλΌλ―Έν° κ°μ²΄μ imports
λ°°μ΄μ DogModule
μ μΆκ°ν΄μΌν©λλ€.
λ§μ§λ§μΌλ‘ ν΄μΌ ν μΌμ νμν μν μ κ² μλ ν¬μΈνΈμ μ¬μ© κ°λ₯ν μν νμκΈ°λ₯Ό μΆκ°νλ κ²μ
λλ€. μ΄λ₯Ό μν΄ μ°λ¦¬λ TerminusOptionsService
λ‘ λμκ°μ/health
μλ ν¬μΈνΈλ₯Ό ꡬνν©λλ€.
@@filename(terminus-options.service)
import {
TerminusEndpoint,
TerminusOptionsFactory,
DNSHealthIndicator,
TerminusModuleOptions
} from '@nestjs/terminus';
import { Injectable } from '@nestjs/common';
@Injectable()
export class TerminusOptionsService implements TerminusOptionsFactory {
constructor(
private readonly dogHealthIndicator: DogHealthIndicator
) {}
createTerminusOptions(): TerminusModuleOptions {
const healthEndpoint: TerminusEndpoint = {
url: '/health',
healthIndicators: [
async () => this.dogHealthIndicator.isHealthy('dog'),
],
};
return {
endpoints: [healthEndpoint],
};
}
}
@@switch
import { DogHealthIndicator } from '../dog/dog.health';
import { Injectable, Dependencies } from '@nestjs/common';
@Injectable()
@Dependencies(DogHealthIndicator)
export class TerminusOptionsService {
constructor(dogHealthIndicator) {
this.dogHealthIndicator = dogHealthIndicator;
}
createTerminusOptions() {
const healthEndpoint = {
url: '/health',
healthIndicators: [
async () => this.dogHealthIndicator.isHealthy('dog'),
],
};
return {
endpoints: [healthEndpoint],
};
}
}
λͺ¨λ κ²μ΄ μ¬λ°λ₯΄κ² μλ£ λμλ€λ©΄/health
μλ ν¬μΈνΈλ 503
μλ΅ μ½λμ λ€μ λ°μ΄ν°λ‘ μλ΅ν΄μΌ ν©λλ€.
{
"status": "error",
"error": {
"dog": {
"status": "down",
"badboys": 1
}
}
}
@nestjs/terminus
μ μ₯μμμ μ€μ μμ λ₯Ό λ³Ό μ μμ΅λλ€.
Custom Logger
Terminus
λͺ¨λμ μν νμΈ μμ² λμ λͺ¨λ μ€λ₯λ₯Ό μλμΌλ‘ κΈ°λ‘ν©λλ€. κΈ°λ³Έμ μΌλ‘ μ μ μ μΌλ‘ μ μ λ Nest λ‘κ±°λ₯Ό μ¬μ©ν©λλ€. κΈλ‘λ² λ‘κ±°μ λν μμΈν λ΄μ©μ λ‘κ±° μ±ν°μμ νμΈν μ μμ΅λλ€. κ²½μ°μ λ°λΌTerminus
μ λ‘κ·Έλ₯Ό λͺ
μμ μΌλ‘ μ²λ¦¬νλ €κ³ ν©λλ€. μ΄ κ²½μ° TerminusModule.forRoot[Async]
ν¨μλ 컀μ€ν
λ‘κ±°λ₯Όμν μ΅μ
μ μ 곡ν©λλ€.
TerminusModule.forRootAsync({
logger: (message: string, error: Error) => console.error(message, error),
endpoints: [
...
]
})
λ‘κ±° μ΅μ
μ null
λ‘ μ€μ νμ¬ λ‘κ±°λ₯Ό λΉνμ±ν ν μλ μμ΅λλ€.
TerminusModule.forRootAsync({
logger: null,
endpoints: [
...
]
})