ํฌ์ค? ๋ผ์์จ์ ๋ฒ ์ด๋ฒ !
ํ๋ก์ ํธ๋ฅผ ์งํํ๋ฉด์, ์๋ฒ์ ์ํ๋ฅผ ํ์ธํ๋ ํ๋ก์ฐ๋ฅผ ๋ง๋ค์ด์ผ ํ๋ค.
์์คํฌ๋ฆฝํธ๋ฅผ crontab์ ์ด์ฉํด์ slack์ ์๋ฒ ์ํ ๋ฉ์์ง๋ฅผ ๋ณด๋ด๋ ๋ฐฉ๋ฒ์ ๋ง๋ค์์ง๋ง,
์ด๊ฑธ๋ก๋ ๋ถ์กฑํ๋ค.
๋ด๊ฐ ์ํ๋ ๊ฒ์ ์ผ์ข ์ ์๋ฒ์ค์ด์๋ค.
์๋ฒ ํ๋์์ DB, Main์๋ฒ๋ฅผ ๊ณ์ํด์ ํ์ธํ๋ ๋ก์ง์ ์ํ๋ค.
๋ก์ปฌ์์๋ ์คํ์์ผ ์๋ฒ ์ํ๋ฅผ ๊ณ์ ํ์ธํ๊ณ , ๋ค๋ฅธ ์๋ฒ์์ ์ฃผ๊ธฐ์ ์ผ๋ก ์๊ทธ๋๋ง์ ์ฒ๋ฆฌํ์ผ๋ฉด ์ข๊ฒ ๋ค๋ ์๊ฐ์ ํ๋ค.
๊ทธ๋์ ์ด๋ป๊ฒ ์ฒ๋ฆฌํ์๊น?
ํฌ๊ฒ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ถ๊ฐ, ๋ชจ๋ ๊ตฌํ, ํ๋ก๋ฐ์ด๋ ๊ตฌํ์ผ๋ก ๋๋์ด๋ณด์๋ค.
์ด ๋์์ ๊ตฌํํ๊ธฐ ์ํด์, 3๊ฐ์ง ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ถ๊ฐํ๋ค.
npm install @nestjs/terminus
npm install @nestjs/schedule
npm install @nestjs/axios
์ด๋ ๊ฒ terminus(ํฌ์ค์ฒดํฌ๊ธฐ๋ฅ), schedule(crontab๊ธฐ๋ฅ), axios(์ธ๋ถ ip์์ฒญ) ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ๋ฃ์ด์ฃผ๊ณ ์์ํ๋ค.
๊ฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ค์ด ์ด๋ป๊ฒ ์ฌ์ฉ๋์๋์ง ํ๋์ฉ ์ฝ๋๋ฅผ ๋ง๋ค๋ฉด์ ์ ๋ฆฌํ๋ค.
const logger = new LoggerService('healthcheck');
@Module({
imports: [TerminusModule, HttpModule, ScheduleModule.forRoot()],
providers: [
HealthCheckScheduler,
HealthCheckService,
TypeOrmHealthIndicator,
HealthCheckExecutor,
{
provide: 'TERMINUS_ERROR_LOGGER',
useValue: Logger.error.bind(logger),
},
{
provide: 'TERMINUS_LOGGER',
useValue: logger,
},
],
controllers: [],
})
export class HealthModule {}
HealthModule์ ๋ฐ๋ก ๋ง๋ค์๋ค.
ํฌ์ค์ฒดํฌ๊ธฐ๋ฅ์ ์ํ TerminusModule, axios๋ฅผ ์ํ HttpModule, ์ค์ผ์ค๋ง์ ์ํ ScheduleModule.forRoot()๋ฅผ importํ๋ค.
providers๋ก๋
@Injectable()
export class HealthCheckScheduler {
private readonly dbLogger = new LoggerService('DB');
private readonly main = new LoggerService('MAIN');
constructor(
private health: HealthCheckService,
private db: TypeOrmHealthIndicator,
private http: HttpHealthIndicator,
) {}
@Cron('0 * * * * *')
async checkDB() {
try {
await this.health.check([
async () => await this.db.pingCheck('database'),
]);
this.dbLogger.log(`${new Date()} : ON`);
} catch (error) {
this.dbLogger.error(`${new Date()} : OFF`, error.stack);
}
}
@Cron('0 * * * * *')
async checkMain() {
try {
await this.health.check([
async () =>
await this.http.pingCheck('gomterview-main', process.env.BE_URL),
]);
this.main.log(`${new Date()} : ON`);
} catch (error) {
this.main.error(`${new Date()} : OFF`, error.stack);
}
}
}
์ด๋ฐ์์ผ๋ก ํ๋ก๋ฐ์ด๋๋ฅผ ๊ตฌํํ๋ค. ํ๋์ฉ ์ ๋ฆฌ๋ฅผ ํด๋ณด์.
TypeOrmHealthIndicator
HttpHealthIndicator
HealthCheckService
@Cron
: ํฌ๋ก ํญ๊ณผ ๊ฐ์ ๋ฐฉ์์ผ๋ก ์ฃผ๊ธฐ์๊ฐ์ ๋ฑ๋กํ ์ ์๋ค.
health.check([cb()])
์ฆ, ํด๋น ๋ก์ง๋ค์ Main์๋ฒ์, DB์๋ฒ์ ํ ์ฒดํฌ๋ฅผ ํ๊ณ ์ํฉ์ ๋ฐ๋ผ ์ปค์คํ ๋ก๊ทธ๋ฅผ ์ถ๋ ฅ์ํค๋ ๊ฒ์ ์ฃผ๊ธฐํ ์ํจ ๊ฒ์ด๋ค.
์ด๋ฐ์์ผ๋ก ์ฒ๋ฆฌํ์ ๋, ๋งค ๋ถ๋ง๋ค ์ํ๋ฅผ ๋ก๊น ์ฒ๋ฆฌํด์ฃผ์๋ค.
์ด๋ฅผ ํตํด ์ํ๋ ๋ก๊ทธ์๋ฒ๋ฅผ ๋ง๋ค์๋ค.
ํ์ฌ ํ๋ก์ ํธ๋ฅผ ์งํํ๋ฉด์ ๋ฐฑ์๋๋ 3๊ฐ์ ์๋ฒ๋ฅผ ์ด๋ค(์จ๋ด์ผ ์ ๋ค ํ๋ฆฌํฐ์ด ec2 ์ธ์คํด์ค์ด๋ค)
์ด ์ํฉ์์ Dev๊ฐ ํ ์ผ์ด ํ์ฌ๋ ๋ณ๋ก ์๋ค(ํ๋ก ํธ 3๋ช , ๋ฐฑ 2๋ช ์ด ์ ๋ถ์ธ ํ์ด๋ค)
Dev๊ฐ ์กฐ๊ธ ๋ ๊ฐ๋ฐ์๋ฅผ ์ํ ์๋ฒ๊ฐ ๋์์ผ๋ฉด ์ข๊ฒ ๋ค๋ ์ทจ์ง๋ก ๋ก๊ทธ๋ฅผ ์ถ๊ฐํ๋ค.
์ด์ ์ฐ๋ฆฌ์ Dev ์๋ฒ๋ ๋ชจ๋ ํ๋ก ํธ/๋ฐฑ์์ ๊ณ์ํด์ ๋ก๊น ํ๋ฉฐ ์ฒ๋ฆฌํ๋ ์๋ฒ๊ฐ ๋์๋ค. (์ด์ ์ผ Dev ์๋ฒ๋ ์ข ์ธ๋ชจ๊ฐ ์๊ฒผ๋ค )
์ฆ ๋ฉ์ธ๊ณผ Dev๋ ๊ฐ์ ๊นํ๋ธ ๋ฆฌํฌ์งํ ๋ฆฌ๋ฅผ ์ฐ์ง๋ง, Main์ Health๊ฐ ์์ด, Dev๋ Health๋ฅผ ๊ฐ์ง๊ณ ๋ฐฐํฌํ๋ ์์ผ๋ก ์งํํ๋ค.
Main์์๋ ๊ฐ์ LoggerService๊ฐ ์กด์ฌํ๋๋ฐ, ์ด๊ฑธ ์จ๋จน์ด๋ณธ์ ์ด ์๋ค.
๊ทธ๋์ ์ด๊ฑธ ๊ฐ์ง๊ณ ๋ ๊ฐ์ง ๋ก๊ทธ๋ฅผ ์ถ๊ฐํ๋ค.
async function bootstrap() {
initializeTransactionalContext();
const app = await NestFactory.create(AppModule, {
abortOnError: true,
});
const expressApp = app.getHttpAdapter().getInstance();
const logger = new LoggerService('traffic');
...
expressApp.use((req, res, next) => {
logger.info(req.url);
next();
});
await app.listen(8080, '0.0.0.0');
}
์ด๋ ๊ฒ ๋ก๊ฑฐ๋ฅผ ํ๋ ๋ฌ์์, ์ด๋ค api๊ฐ ์์ฒญ์ด ๋ค์ด์ค๋์ง ๋ก๊ทธ์ฒ๋ฆฌํ๊ฒ ํ๋ค(์ด ๋ logger๋ ์ถ๋ ฅ๋ฟ๋ง ์๋๋ผ, ๋ก์ปฌ์ ํ์ผ์ ๋ง๋ค๊ณ ์์ฑํ๋ ๊ธฐ๋ฅ๊น์ง ๋ด๊ฒจ์๋ค)
๋ํ, ์์ธ์ฒ๋ฆฌ์์ ์ฐ๋ฆฌ๊ฐ ๋ง๋ ์ปค์คํ ์์ธ๊ฐ ์ด๋์์ ํฐ์ง๋์ง ํ์ธํ๊ฒ ํ๋ค.
class HttpCustomException extends HttpException {
constructor(message: string, errorCode: string, status: number) {
super({ message: message, errorCode: errorCode }, status);
errorLogger.error(errorCode, super.stack);
}
}
์ด๋ฐ์์ผ๋ก ์ด๋ค ์๋ํฌ์ธํธ์์ ์ด๋ค ์๋ฌ์ฝ๋๊ฐ ๋์๋์ง, ๊ทธ๋ฆฌ๊ณ ์ด ๋ stack๊น์ง ๊ฐ์ด ์ถ๋ ฅํ๊ฒ ํ๋ค.
๋จ์ํ์ง๋ง, ์๋์น ์์ ์ผ์ด์ค๋ ํฐ์ง๋ฉด ์๋๋ ์์ธ์ผ์ด์ค๋ฅผ ๋ก๊น ํ๋๋ฐ ์ฉ์ดํ ๊ฒ ๊ฐ๋ค๋ ์๊ฐ์ด ๋ค์๋ค. (์ ๋ถ๋ค 500์ผ๋ก ๋ง๋ค์ด ์จ๊ฒจ๋๊ธด ํ์ง๋ง, errorCode๋ฅผ ํตํด ๊ฐ๋ฐ์๋ง ๋ณผ ์ ์๊ฒ ์์ ํด๋์๋ค)
์ด๋ฅผ ํตํด ๋ก๊ทธ๋ฅผ ๋ฐ์์ํค๊ณ , ์ด๋ฅผ ์ฐ๋ฆฌ๊ฐ ์๋์ ๊ฐ์ ๋ก๊ทธ๋ฅผ ๋ฉ์ธ์์ ์ฒ๋ฆฌํ๊ฒ ํ๋ค.
๋ฌผ๋ก ์ธ๋ถ ํ๋ก๊ทธ๋จ๋ฑ์ ํ์ฉํ๋ฉด ๋ ์์ํ๊ฒ ๋ก๊น ์ ํ ์ ์๋ ๊ฒ์ ์์ง๋ง, ์์ง์ ๋ถ์คํธ์บ ํ ๊ธฐ๊ฐ์ด์ด์ ์ต๋ํ ๋ง์ ๊ฒ์ ํ์ตํด์ ๋ง๋ค์ด๋ณด๋ฉด์ํ๊ณ ์๋ค.
๋ฉ์ธ๊ณผ ๋ฐ๋ธ์์ ๊ฐ์ ์๊ธฐ๊ฐ ๋ณด์ฌ์ค์ผ ํ ๊ฒ๋ค์ด ์๋ค๊ณ ๋๋๋ค.
๋ฉ์ธ์์๋ ๋ด๊ฐ ์ด๋ค ์์ฒญ์ ๋ฐ์๊ณ , ์ด๋ค ์๋ฌ๊ฐ ์๊ณ , ์ด๋ค ์ด์ ์ธ์ง ์๋ ค์ค์ผ ํ๋ฉฐ,
dev์๋ฒ์์๋ ๋ค๋ฅธ ์๋ฒ๋ค์ ์ํ๋ฅผ ๋ณด์ฌ์ค์ผ ํ๋ค๊ณ ์๊ฐํ๋ค.
์ด๋ฅผ ์ํ ๋ก๊ทธ๋ฅผ ๋ฌ๊ณ ๋๋, ํ๋ก ํธ์์ '๋ญ๊ฐ ์ด์ํด์! ๋ญ๊ฐ ์๋ผ์!'๋ฅผ ํ๋ ๊ฒ ๋ณด๋จ, ๋ ํจ์จ์ ์ธ ๋ฐฉ๋ฒ์ด ๋์๋ค.
๊ฐ์์ค์์๋, ๋ฉํ ๋ง์ค์์๋ ๋ก๊น ์ ๋ํด์๋ ๋ง์ ๋ก๊ทธ๋ฅผ ๋ง๋ค์ด๋๊ณ , ๊ทธ ์ค์์ ํ์ํ ๋ถ๋ถ์ ์ฐพ์๊ฐ๋ ๊ฒ์ด ์ค์ํ๋ค๊ณ ํ๋ค.
์ด๋ฅผ ์ํ ์์์ผ๋ก ํ๋์ฉ ์ถ๊ฐํ๊ฑฐ๋ ๋นผ๋ ์์ผ๋ก ์งํํ๋ฉด ๋ ๊ฒ ๊ฐ๋ค.
๋ค๋ค ๋๋ฒ๊น ์ํ๋ ๊ทธ๋ ๊น์ง ๋ก๊ทธ๋ฅผ ๋ณด์!!
(์ฌ์ค ํฐ๋ฏธ๋๋ก ๋ก๊ทธ ๋ณด๋๊น ์ง์ง the love๋ค..ํํ)
๊ทธ๋ผ...twenty thousand...๐ฅ