작은 서비스에도 모니터링이 필요한 이유 - NestJS + Prometheus + Grafana Cloud 구축기
시작은 단순한 질문이었다
"Coolify에 배포한 백엔드가 갑자기 느려졌는데, CPU 때문인지 메모리 때문인지 어떻게 알 수 있지?"
개인 블로그 플랫폼을 운영하면서 마주친 질문이었다. 트래픽이 많지 않은 작은 서비스다. 모니터링 시스템을 구축하는 게 과한 건 아닐까? 그런 생각이 들었다.
하지만 곰곰이 생각해보니, 오히려 작은 서비스이기 때문에 모니터링이 필요했다.
작은 서비스에 모니터링이 필요한 이유
리소스가 제한된 환경에서는 작은 변화도 큰 영향을 미친다. Coolify에서 CPU 0.5코어, 메모리 512MB로 운영하는 서비스라면, 메모리 누수 100MB는 전체 용량의 20%에 해당한다.
모니터링 없이는 다음 질문들에 답할 수 없다:
- 현재 할당한 리소스가 적절한가?
- 어느 시점에 병목이 발생하는가?
- 트래픽이 늘어나면 어떤 부분을 먼저 확장해야 하는가?
이런 판단을 위해서는 데이터가 필요하다. 그래서 모니터링 시스템을 구축하기로 했다.
Prometheus + Grafana: APM의 시작점
Prometheus와 Grafana 조합을 선택했다. 처음에는 이 조합이 완전한 APM(Application Performance Monitoring)이라고 생각했는데, 정확히 말하면 그렇지 않다.
Observability(관측 가능성)는 세 가지 축으로 구성된다:
- Metrics - 숫자로 표현되는 측정값 (CPU, 메모리, 요청 수)
- Logs - 이벤트 기록
- Traces - 요청의 흐름 추적
Prometheus + Grafana는 이 중 Metrics만 담당한다. 완전한 APM이 되려면 분산 추적, 코드 레벨 프로파일링, 에러 추적 등이 추가로 필요하다.
하지만 작은 서비스의 시작점으로는 충분하다. 나머지는 필요할 때 추가하면 된다.
NestJS에 Prometheus 메트릭 추가하기
NestJS 백엔드에 prom-client 라이브러리를 설치하고, PrometheusService를 만들었다.
// prometheus.service.ts
import { Injectable, OnModuleInit } from '@nestjs/common';
import * as client from 'prom-client';
@Injectable()
export class PrometheusService implements OnModuleInit {
private readonly register: client.Registry;
constructor() {
this.register = new client.Registry();
client.collectDefaultMetrics({ register: this.register });
}
async getMetrics(): Promise<string> {
return this.register.metrics();
}
getContentType(): string {
return this.register.contentType;
}
}
/metrics/prometheus 엔드포인트를 추가하면, Prometheus 형식의 메트릭을 수집할 수 있다. 기본적으로 Node.js 런타임 메트릭(메모리 사용량, 이벤트 루프 지연, GC 통계 등)이 자동으로 수집된다.
메트릭 엔드포인트 보호하기
메트릭 엔드포인트를 그대로 공개하면 보안 문제가 된다. 시스템 내부 정보가 노출되기 때문이다.
두 가지 인증 방식을 지원하는 Guard를 만들었다:
- IP 화이트리스트 - 특정 IP만 접근 허용 (CIDR 표기법 지원)
- Basic Auth - 사용자명/비밀번호 인증
// metrics-auth.guard.ts
@Injectable()
export class MetricsAuthGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
// 개발 환경에서는 인증 없이 접근 가능
if (this.isDevelopment) {
return true;
}
const request = context.switchToHttp().getRequest<Request>();
const clientIp = this.getClientIp(request);
// IP 화이트리스트 체크
if (this.isIpAllowed(clientIp)) {
return true;
}
// Basic Auth 체크
if (this.isBasicAuthValid(request)) {
return true;
}
throw new UnauthorizedException('Metrics endpoint requires authentication');
}
}
Grafana Cloud와 연동할 때는 Basic Auth를 사용했다. IP 화이트리스트는 Grafana Cloud의 IP가 고정되지 않아 사용하기 어렵다.
Grafana Cloud 연동
Grafana Cloud의 무료 티어는 개인 프로젝트에 충분하다:
- 메트릭 10,000개
- 로그 50GB/월
- 트레이스 50GB/월
- 14일 보존
연동 방식은 Metrics Endpoint Integration을 선택했다. 별도의 에이전트 없이, Grafana Cloud가 직접 엔드포인트를 스크래핑하는 방식이다.
설정 과정:
- Grafana Cloud 계정 생성 (일본 리전 선택 - 한국에서 가장 가까움)
- Connections → Add new connection → Metrics Endpoint
- 스크래핑 URL 입력:
https://api.example.com/metrics/prometheus - Basic Auth 자격증명 설정
- 스크래핑 간격: 60초
Coolify 환경변수에 METRICS_USERNAME과 METRICS_PASSWORD를 설정하고, 재배포하면 연동이 완료된다.
스크래핑 간격에 대한 고민
스크래핑 간격을 얼마로 설정할지 고민했다. 너무 짧으면 불필요한 부하가 생기고, 너무 길면 순간적인 스파이크를 놓칠 수 있다.
결론은 60초가 적절하다는 것이었다:
- 트렌드 파악에는 충분함
- Grafana Cloud 무료 티어의 메트릭 한도 고려
- 작은 서비스에서 10초 단위 정밀도는 과함
나중에 트래픽이 늘어나면 15-30초로 줄이는 것을 고려하면 된다.
실제로 확인할 수 있게 된 것들
연동 후 Grafana Cloud의 Explore에서 다음 메트릭들을 확인할 수 있게 되었다:
process_resident_memory_bytes- 실제 메모리 사용량nodejs_eventloop_lag_seconds- 이벤트 루프 지연nodejs_heap_size_used_bytes- 힙 메모리 사용량nodejs_gc_duration_seconds- GC 소요 시간
이제 "메모리가 왜 늘어났지?"라는 질문에 데이터로 답할 수 있다.
배운 점
이번 작업을 통해 몇 가지를 배웠다:
- 모니터링은 서비스 규모와 무관하게 필요하다 - 오히려 제한된 리소스에서 더 중요할 수 있다.
- 완벽한 시스템보다 시작이 중요하다 - Metrics만으로 시작해도 충분하다. Logs, Traces는 필요할 때 추가하면 된다.
- 무료 티어로 충분히 시작할 수 있다 - Grafana Cloud 무료 티어는 개인 프로젝트에 넉넉하다.
- 보안은 처음부터 고려해야 한다 - 메트릭 엔드포인트도 보호가 필요하다.
다음 단계로는 커스텀 메트릭(API 응답 시간, 에러율 등)을 추가하고, 알림 규칙을 설정할 계획이다. 하지만 그건 실제로 필요해질 때 하면 된다.
일단은, 데이터를 볼 수 있게 된 것만으로 충분하다.
관련 글
개인 블로그에 AI 검색 달기 (2) - MongoDB Atlas Vector Search 구현
MongoDB Atlas Vector Search 인덱스 설정부터 NestJS에서 하이브리드 검색을 구현하는 과정. $vectorSearch의 null 필터 제한사항과 RRF 알고리즘, 유사도 임계값 튜닝까지.
k6와 실시간 Pool 모니터링으로 시스템 한계점 찾기
k6로 시스템 한계점을 찾는 Breakpoint 테스트와 NestJS Connection Pool 실시간 모니터링 시스템을 구현한 경험. 최적 RPS를 찾기까지의 과정을 정리했습니다.
PM2 vs Coolify: 상황에 맞는 Node.js 배포 전략 선택하기
Node.js 배포 도구인 PM2와 Coolify의 차이점을 분석하고, 프로젝트 특성에 따른 선택 기준을 제시합니다.