작은 서비스에도 모니터링이 필요한 이유 - 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 응답 시간, 에러율 등)을 추가하고, 알림 규칙을 설정할 계획이다. 하지만 그건 실제로 필요해질 때 하면 된다.
일단은, 데이터를 볼 수 있게 된 것만으로 충분하다.
관련 글
NestJS Cron으로 Grafana Cloud 서버 메트릭을 Slack에 자동 보고하기
NestJS 스케줄러와 Grafana Cloud Prometheus API를 연결해 매일 아침 서버 상태를 Slack으로 자동 보고하는 시스템을 구축한 과정을 정리했습니다. 이미 갖춰진 인프라를 활용해 최소한의 코드로 일일 리포트를 만드는 방법입니다.
Prometheus 메트릭 최적화: 카디널리티 폭발과 공격 트래픽 필터링
Grafana에서 CPU 스파이크를 추적하다 발견한 Prometheus 메트릭 문제들 - 공격 트래픽으로 인한 카디널리티 폭발, 중복 메트릭, 서비스 구분 부재. 해결 방법과 교훈을 공유합니다.
NestJS 일일 리포트에 GA4, GSC 데이터 통합하기
기존 Grafana Prometheus 기반 서버 리포트에 Google Analytics 4와 Search Console 데이터를 추가하여, 서버 상태부터 사용자 행동, 검색 성과까지 한눈에 파악할 수 있는 통합 일일 리포트를 구축한 과정을 정리했습니다.