홈

© 2025 Ki Chang. All rights reserved.

본 블로그의 콘텐츠는 CC BY-NC-SA 4.0 라이선스를 따릅니다.

소개개인정보처리방침이용약관

© 2025 Ki Chang. All rights reserved.

콘텐츠: CC BY-NC-SA 4.0

소개|개인정보처리방침|이용약관

작은 서비스에도 모니터링이 필요한 이유 - NestJS + Prometheus + Grafana Cloud 구축기

정기창·2025년 12월 28일

시작은 단순한 질문이었다

"Coolify에 배포한 백엔드가 갑자기 느려졌는데, CPU 때문인지 메모리 때문인지 어떻게 알 수 있지?"

개인 블로그 플랫폼을 운영하면서 마주친 질문이었다. 트래픽이 많지 않은 작은 서비스다. 모니터링 시스템을 구축하는 게 과한 건 아닐까? 그런 생각이 들었다.

하지만 곰곰이 생각해보니, 오히려 작은 서비스이기 때문에 모니터링이 필요했다.

작은 서비스에 모니터링이 필요한 이유

리소스가 제한된 환경에서는 작은 변화도 큰 영향을 미친다. Coolify에서 CPU 0.5코어, 메모리 512MB로 운영하는 서비스라면, 메모리 누수 100MB는 전체 용량의 20%에 해당한다.

모니터링 없이는 다음 질문들에 답할 수 없다:

  • 현재 할당한 리소스가 적절한가?
  • 어느 시점에 병목이 발생하는가?
  • 트래픽이 늘어나면 어떤 부분을 먼저 확장해야 하는가?

이런 판단을 위해서는 데이터가 필요하다. 그래서 모니터링 시스템을 구축하기로 했다.

Prometheus + Grafana: APM의 시작점

Prometheus와 Grafana 조합을 선택했다. 처음에는 이 조합이 완전한 APM(Application Performance Monitoring)이라고 생각했는데, 정확히 말하면 그렇지 않다.

Observability(관측 가능성)는 세 가지 축으로 구성된다:

  1. Metrics - 숫자로 표현되는 측정값 (CPU, 메모리, 요청 수)
  2. Logs - 이벤트 기록
  3. 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를 만들었다:

  1. IP 화이트리스트 - 특정 IP만 접근 허용 (CIDR 표기법 지원)
  2. 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가 직접 엔드포인트를 스크래핑하는 방식이다.

설정 과정:

  1. Grafana Cloud 계정 생성 (일본 리전 선택 - 한국에서 가장 가까움)
  2. Connections → Add new connection → Metrics Endpoint
  3. 스크래핑 URL 입력: https://api.example.com/metrics/prometheus
  4. Basic Auth 자격증명 설정
  5. 스크래핑 간격: 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 소요 시간

이제 "메모리가 왜 늘어났지?"라는 질문에 데이터로 답할 수 있다.

배운 점

이번 작업을 통해 몇 가지를 배웠다:

  1. 모니터링은 서비스 규모와 무관하게 필요하다 - 오히려 제한된 리소스에서 더 중요할 수 있다.
  2. 완벽한 시스템보다 시작이 중요하다 - Metrics만으로 시작해도 충분하다. Logs, Traces는 필요할 때 추가하면 된다.
  3. 무료 티어로 충분히 시작할 수 있다 - Grafana Cloud 무료 티어는 개인 프로젝트에 넉넉하다.
  4. 보안은 처음부터 고려해야 한다 - 메트릭 엔드포인트도 보호가 필요하다.

다음 단계로는 커스텀 메트릭(API 응답 시간, 에러율 등)을 추가하고, 알림 규칙을 설정할 계획이다. 하지만 그건 실제로 필요해질 때 하면 된다.

일단은, 데이터를 볼 수 있게 된 것만으로 충분하다.

NestJSPrometheusGrafana CloudMonitoringObservabilityBackendNode.js