새벽 4시 7분. 전화벨이 울렸다. '서버 죽었어요.' 로그인해서 확인하니 CPU 100%, 메모리 풀. 원인 파악에 2시간 걸렸다. 모니터링 시스템이 있었다면? 아마 30분 전에 알림을 받고 잠을 깨지 않았을 것이다. 이건 그 새벽 이후에 구축한 모니터링 시스템의 이야기다.

아키텍처 개요

모니터링 시스템의 데이터 흐름은 다음과 같습니다:

Spring Boot App (Actuator + Micrometer)
        ↓ /actuator/prometheus
    Prometheus (메트릭 수집 & 저장)
        ↓
    Grafana (시각화 & 알림)

Spring Boot 설정

의존성 추가

// build.gradle.kts
dependencies {
    implementation("org.springframework.boot:spring-boot-starter-actuator")
    implementation("io.micrometer:micrometer-registry-prometheus")
}

application.yml 설정

management:
  endpoints:
    web:
      exposure:
        include: health,info,prometheus,metrics
  endpoint:
    health:
      show-details: always
    prometheus:
      enabled: true
  metrics:
    tags:
      application: ${spring.application.name}
      environment: ${SPRING_PROFILES_ACTIVE:local}

여기서 중요한 포인트는 metrics.tags입니다. 환경별(dev, staging, prod)로 메트릭을 구분하기 위해 환경변수를 통해 태그를 주입합니다.

Standalone Tomcat 환경 설정

WAR로 배포하는 경우, Tomcat의 setenv.sh에서 환경변수를 설정합니다:

# $CATALINA_BASE/bin/setenv.sh
export SPRING_PROFILES_ACTIVE=production
export JAVA_OPTS="$JAVA_OPTS -Dspring.profiles.active=production"

이렇게 하면 Grafana에서 환경별로 메트릭을 필터링할 수 있습니다.

Prometheus 설정

# prometheus.yml
global:
  scrape_interval: 15s

scrape_configs:
  - job_name: 'spring-boot-app'
    metrics_path: '/actuator/prometheus'
    static_configs:
      - targets: ['app-server:8080']
    relabel_configs:
      - source_labels: [__address__]
        target_label: instance
        regex: '(.+):\d+'
        replacement: '${1}'

Grafana 대시보드 구성

Grafana 대시보드 ID 19004를 추천합니다. Spring Boot 3.x에 최적화된 대시보드입니다.

주요 모니터링 패널

환경별 필터링 쿼리

# Production 환경의 HTTP 요청 수
sum(rate(http_server_requests_seconds_count{environment="production"}[5m]))

커스텀 메트릭 추가

비즈니스 로직에 맞는 커스텀 메트릭을 추가할 수 있습니다:

@Component
public class OrderMetrics {
    private final Counter orderCounter;
    private final Timer orderProcessingTimer;
    
    public OrderMetrics(MeterRegistry registry) {
        this.orderCounter = Counter.builder("orders.created")
            .description("Number of orders created")
            .register(registry);
            
        this.orderProcessingTimer = Timer.builder("orders.processing.time")
            .description("Order processing time")
            .register(registry);
    }
    
    public void recordOrder() {
        orderCounter.increment();
    }
    
    public void recordProcessingTime(Runnable task) {
        orderProcessingTimer.record(task);
    }
}

알림 설정

Grafana에서 알림 규칙을 설정합니다:

트러블슈팅

메트릭이 수집되지 않는 경우

  1. Actuator 엔드포인트 노출 확인: /actuator 접근 가능 여부
  2. Prometheus 타겟 상태 확인: Prometheus UI의 Targets 메뉴
  3. 방화벽 설정 확인: Prometheus → App 서버 간 통신

환경 태그가 적용되지 않는 경우

WAR 배포 환경에서 환경변수가 제대로 전달되지 않을 수 있습니다. CATALINA_OPTS 대신 JAVA_OPTS를 사용하거나, catalina.properties에 직접 설정합니다.

실무 경험 공유: AWS CloudWatch 알림으로 DB 커넥션 이슈를 사전에 발견한 경험이 있습니다. 커넥션 풀 사용량이 평소보다 급격히 올라가는 것을 알림으로 확인하고 원인을 추적해보니, 트랜잭션에서 커넥션을 제대로 반환하지 않는 버그가 있었습니다. 모니터링 덕분에 장애가 나기 전에 수정할 수 있었고, 이후 모니터링의 중요성을 절감하게 되었습니다.

모니터링 시스템이 장애를 막아준 실제 사례

위에서 공유한 경험 이후로 팀에서는 모니터링 알림 규칙을 대폭 강화했습니다. 단순히 시스템 메트릭만 감시하는 것이 아니라, 배포 직후 30분간은 알림 임계값을 더 낮게 설정하는 "배포 감시 모드"를 도입했습니다. 이 덕분에 비슷한 유형의 문제를 3건 더 사전에 탐지할 수 있었습니다.

모니터링 시스템 구축은 선택이 아닌 필수입니다. 대시보드를 만드는 것 자체보다, 어떤 메트릭을 감시하고 어떤 임계값에서 알림을 받을지를 설계하는 것이 핵심입니다. 특히 DB 커넥션 풀, JVM 메모리, 에러율 이 세 가지는 반드시 알림을 설정해두시길 강력히 추천합니다.

Jaeseong
Jaeseong

10년차 풀스택 개발자. Spring Boot, Flutter, AI 등 실무 경험을 기록합니다.

GitHub →

💬 댓글