• 티스토리 홈
  • 프로필사진
    알쓸개잡
  • 태그
  • 블로그 관리
  • 글 작성
알쓸개잡
  • 프로필사진
    알쓸개잡
    • 분류 전체보기 (99) N
      • 스프링부트 (58) N
      • AWS (5)
      • 쿠버네티스 (7)
      • 자바 (19)
      • 인프라 (1)
      • ETC (9)
  • 리얼포스 Realforce R3S 키보드 R3SD13 토프레 무점접키보드, 기본, 기본, 텐키리스
    반응형
# Home
# 공지사항
#
# 태그
# 검색결과
# 방명록
  • Spring Boot Actuator - 6. actuator + prometheus + grafana
    2025년 10월 13일
    • 알쓸개잡
    • 작성자
    • 2025.10.13.:48

    운영 중인 애플리케이션이 잘 동작하고 있는지, 장애 조짐은 없는지 어떻게 알 수 있을까? 단순히 로그만 보는 것으로는 분명한 한계가 있다. 애플리케이션 내부 상태 (JVM 메모리, 스레드, HTTP 요청 처리량등)를 실시간으로 모니터링하고 이를 기반으로 알림 설정하며 직관적인 대시보드로 시각화하는 것이 필요하다.

    Spring Boot는 이런 요구를 충족시키기 위해서 Actuator라는 강력한 모듈을 제공한다. Actuator는 애플리케이션의 헬스 상태와 다양한 메트릭을 엔트포인트로 노출해 주지만 장기간 데이터 저장이나 시각화 기능은 기본적으로 제공하지 않는다.

    이러한 데이터 보관과 시각화를 위해서 잘 알려진 것이 Prometheus와 Grafana 조합이다. Prometheus는 Actuator에서 노출하는 메트릭을 주기적으로 수집하고 저장하는 역할을 한다. Grafana는 Prometheus에 저장된 메트릭을 기반으로 시각화 하는 역할을 한다.

    이번 포스팅에서는 Spring Boot Actuator + Prometheus + Grafana를 로컬 환경에 구성하여 애플리케이션 메트릭 데이터를 시각화 하는 방법을 정리해 보고자 한다.

     

    Dependency

    애플리케이션에 대한 메트릭 정보를 Prometheus에 맞게 전달하기 위해서는 다음의 의존성이 필요하다.

    implementation 'io.micrometer:micrometer-registry-prometheus'
    <dependency>
        <groupId>io.micrometer</groupId>
        <artifactId>micrometer-registry-prometheus</artifactId>
    </dependency>

    Spring Boot 의존성 관리에 micrometer이 포함되어 있어 Spring Boot 의존성 관리를 사용하고 있다면 별도의 버전 정보는 필요하지 않다. 이 의존성을 추가함으로써 prometheus에 대한 엔드포인트 자동 구성 실행이 활성화된다. (prometheus 엔드포인트가 추가된다는 의미는 아니다.)

     

    actuator prometheus 엔드포인트 노출 설정

    spring-boot-actuator-autoconfigure 모듈의 org.springframework.boot.actuate.autoconfigure.metrics.export.prometheus 패키지에 보면 prometheus 엔드포인트 구성을 위한 자동 구성 클래스와 Properties 클래스가 있다.

    PrometheusMetricsExportAutoConfiguration 클래스가 prometheus를 위한 자동구성 클래스인데 지정된 어노테이션을 간단히 살펴보자.

    @AutoConfiguration(
        before = { CompositeMeterRegistryAutoConfiguration.class, 
                   SimpleMetricsExportAutoConfiguration.class },
        after = MetricsAutoConfiguration.class
    )
    @ConditionalOnBean(Clock.class)
    @ConditionalOnClass(PrometheusMeterRegistry.class)
    @ConditionalOnEnabledMetricsExport("prometheus")
    @EnableConfigurationProperties(PrometheusProperties.class)
    public class PrometheusMetricsExportAutoConfiguration { 
        ...
        ...
        @Configuration(proxyBeanMethods = false)
        @ConditionalOnAvailableEndpoint(PrometheusScrapeEndpoint.class)
        static class PrometheusScrapeEndpointConfiguration {
    
            @Bean
            @ConditionalOnMissingBean
            PrometheusScrapeEndpoint prometheusEndpoint(PrometheusRegistry prometheusRegistry,
                                                        PrometheusConfig prometheusConfig) {
                return new PrometheusScrapeEndpoint(
                    prometheusRegistry, prometheusConfig.prometheusProperties());
            }
        }
        ...
        ...
    }

    우선 ConditionalOnEnabledMetricsExport("prometheus")는 Conditional 어노테이션으로 value는 "prometheus"다.

    management.<value>.metrics.export.enabled 설정과 management.defaults.metrics.export.enabled 설정을 확인하여 true인 경우 자동 구성 조건이 된다. management.<value>.metrics.export.enabled와 management.defaults.emtrics.export.enabled 디폴트는 true라서 엔드포인트를 활성화할 생각이라면 굳이 설정은 하지 않아도 무방하다.

     

    두 번째로 살펴볼 어노테이션은 PrometheusScrapeEndpointConfiguration 클래스의 ConditionalOnAvailableEndpoint 어노테이션인데 간단히 말해서 해당 어노테이션은 management.endpoint.prometheus.access 설정과 management.endpoint.prometheus.enabled 설정 조건에 따라서 PrometheusScrapeEndpointConfiguration 클래스가 동작한다. management.endpoint.prometheus.access 설정이 우선이고 해당 설정이 없다면 management.endpoint.prometheus.enabled가 동작하지만 자동 완성으로 보면 enabled 설정은 deprecated 느낌이다.

    그냥 prometheus 엔드포인트를 노출하고자 한다면 management.endpoint.prometheus.access 설정을 하면 되겠다.

    management.endpoint.prometheus.access 설정 값은 none(노출 안 함), read_only(읽기 전용), unrestricted(제한 없음)이다.

    코드를 직접 따라가 보고자 한다면 아래와 같은 흐름으로 클래스를 따라가 보기 바란다.

    @ConditionalOnAvailableEndpoint(PrometheusScrapeEndpoint.class)
        -> OnAvailableEndpointCondition
            -> PropertiesEndpointAccessResolver::accessFor()
                -> PropertiesEndpointAccessResolver::resolveAccess()

     

    이제 prometheus 엔드포인트를 노출하기 위한 application.yml 설정은 다음과 같다.

    management:
      server:
        port: 8081
        
      endpoints:
        web:
          exposure:
            include: health, prometheus
          base-path: /security-actuator
          
      endpoint:
        prometheus:
          access: read_only
          
    app:
      security-password: ${SECURITY_PASSWORD}

    prometheus 엔드포인트에도 basic auth 인증 설정을 다음과 같이 지정하였다.

    @Configuration
    public class ActuatorSecurityConfig {
    
        private static final String ACTUATOR_ROLE = "ACTUATOR";
        @Value( "${app.security-password}" )
        private String actuatorPassword;
    
        @Value("${management.endpoints.web.base-path:/actuator}")
        private String actuatorBasePath;
    
        /*
        /metrics, metrics/**, /env, /env/** 에 대한 인증 및 인가 적용
        /health, /info 엔드포인트는 기본 허용
        그 외 나머지는 연결 거부
         */
        @Bean
        public SecurityFilterChain securityFilterChain( HttpSecurity http ) throws Exception {
            http.securityMatcher( actuatorBasePath + "/**" )
                    .authorizeHttpRequests( authorize -> authorize
                            .requestMatchers( actuatorBasePath, actuatorBasePath + "/health", actuatorBasePath + "/info" ).permitAll()
                            .requestMatchers( actuatorBasePath + "/metrics", actuatorBasePath + "/metrics/**",
                                    actuatorBasePath + "/env", actuatorBasePath + "/env/**",
                                    actuatorBasePath + "/maintenance", actuatorBasePath + "/maintenance/**",
                                    actuatorBasePath + "prometheus", actuatorBasePath + "/prometheus/**" )
                            .hasRole( ACTUATOR_ROLE )
                            .anyRequest().denyAll() )
                    .httpBasic( withDefaults() )
                    .csrf( AbstractHttpConfigurer::disable );
    
            return http.build();
        }
    
        /*
        인증 처리에 필요한 비밀번호 Encoder 정의. 여기서는 bcrypt를 사용.
        빈을 생성하지 않은 경우 UserDetailsService 빈 생성시 password 에 prefix 지정을 해줘야 함.
         */
        @Bean
        public PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder( 10 );
        }
    
        /*
        UserDetailsService 빈이 spring security 처리 과정에서 적용된다.
        내부적으로 AuthenticationManager -> AuthenticationProvider를 통해서 인증이 일어난다.
        보통은 DB에 저장된 값 혹은 k8s 환경의 경우 secret에 저장된 값을 적용하지만 편의상 환경변수에 정의된 값을 사용하겠다.
        여기서 username은 basic auth의 id가 그대로 설정된다. 중요한 것은 actuatorPassword 정보다.
        actuatorPassword와 basic auth 헤더의 password 정보가 일치해야 한다.
         */
        @Bean
        public UserDetailsService userDetailsService() {
            return username -> User.withUsername( username )
                    .password( passwordEncoder().encode( actuatorPassword ) )
                    .roles( ACTUATOR_ROLE )
                    .build();
        }
    }

    엔드포인트 보안은 다음 포스팅을 참고하기 바란다.

    2025.09.23 - [스프링부트] - Spring Boot Actuator - 3. Actuator 보안

     

    prometheus 엔드포인트 호출 URL은 다음과 같다.

    http://localhost:8081/security-actuator/prometheus

    basic 인증에 필요한 id:password는 admin:actuator-password

     ~/ curl -v -u admin:actuator-password http://localhost:8081/security-actuator/prometheus
    * Host localhost:8081 was resolved.
    * IPv6: ::1
    * IPv4: 127.0.0.1
    *   Trying [::1]:8081...
    * Connected to localhost (::1) port 8081
    * Server auth using Basic with user 'admin'
    > GET /security-actuator/prometheus HTTP/1.1
    > Host: localhost:8081
    > Authorization: Basic YWRtaW46YWN0dWF0b3ItcGFzc3dvcmQ=
    > User-Agent: curl/8.7.1
    > Accept: */*
    >
    * Request completely sent off
    < HTTP/1.1 200
    < X-Content-Type-Options: nosniff
    < X-XSS-Protection: 0
    < Cache-Control: no-cache, no-store, max-age=0, must-revalidate
    < Pragma: no-cache
    < Expires: 0
    < X-Frame-Options: DENY
    < Content-Type: text/plain;version=0.0.4;charset=utf-8
    < Content-Length: 28036
    < Date: Mon, 13 Oct 2025 05:12:34 GMT
    <
    # HELP application_ready_time_seconds Time taken for the application to be ready to service requests
    # TYPE application_ready_time_seconds gauge
    application_ready_time_seconds{main_application_class="org.example.spring.actuator.example.SpringActuatorExampleApplication"} 1.654
    # HELP application_started_time_seconds Time taken to start the application
    # TYPE application_started_time_seconds gauge
    application_started_time_seconds{main_application_class="org.example.spring.actuator.example.SpringActuatorExampleApplication"} 1.649
    # HELP disk_free_bytes Usable space for path
    # TYPE disk_free_bytes gauge
    disk_free_bytes{path="/Users/nohacking/workspace/java-project/blog/spring-actuator-example/."} 6.07994417152E11
    # HELP disk_total_bytes Total space for path
    # TYPE disk_total_bytes gauge
    disk_total_bytes{path="/Users/nohacking/workspace/java-project/blog/spring-actuator-example/."} 9.9466258432E11
    # HELP executor_active_threads The approximate number of threads that are actively executing tasks
    # TYPE executor_active_threads gauge
    executor_active_threads{name="applicationTaskExecutor"} 0.0
    # HELP executor_completed_tasks_total The approximate total number of tasks that have completed execution
    # TYPE executor_completed_tasks_total counter
    executor_completed_tasks_total{name="applicationTaskExecutor"} 0.0
    # HELP executor_pool_core_threads The core number of threads for the pool
    # TYPE executor_pool_core_threads gauge
    executor_pool_core_threads{name="applicationTaskExecutor"} 8.0
    # HELP executor_pool_max_threads The maximum allowed number of threads in the pool
    # TYPE executor_pool_max_threads gauge
    executor_pool_max_threads{name="applicationTaskExecutor"} 2.147483647E9
    # HELP executor_pool_size_threads The current number of threads in the pool
    # TYPE executor_pool_size_threads gauge
    executor_pool_size_threads{name="applicationTaskExecutor"} 0.0
    # HELP executor_queue_remaining_tasks The number of additional elements that this queue can ideally accept without blocking
    # TYPE executor_queue_remaining_tasks gauge
    executor_queue_remaining_tasks{name="applicationTaskExecutor"} 2.147483647E9
    # HELP executor_queued_tasks The approximate number of tasks that are queued for execution
    # TYPE executor_queued_tasks gauge
    executor_queued_tasks{name="applicationTaskExecutor"} 0.0
    # HELP jvm_info JVM version info
    # TYPE jvm_info gauge
    jvm_info{runtime="OpenJDK Runtime Environment",vendor="Eclipse Adoptium",version="17.0.14+7"} 1
    ...
    ...
    # TYPE spring_security_http_secured_requests_active_seconds summary
    spring_security_http_secured_requests_active_seconds_count 1
    spring_security_http_secured_requests_active_seconds_sum 0.001568084
    # HELP spring_security_http_secured_requests_active_seconds_max
    # TYPE spring_security_http_secured_requests_active_seconds_max gauge
    spring_security_http_secured_requests_active_seconds_max 0.001570417
    # HELP spring_security_http_secured_requests_seconds
    # TYPE spring_security_http_secured_requests_seconds summary
    spring_security_http_secured_requests_seconds_count{error="none"} 3
    spring_security_http_secured_requests_seconds_sum{error="none"} 0.033711875
    # HELP spring_security_http_secured_requests_seconds_max
    # TYPE spring_security_http_secured_requests_seconds_max gauge
    spring_security_http_secured_requests_seconds_max{error="none"} 0.026670375
    # HELP system_cpu_count The number of processors available to the Java virtual machine
    # TYPE system_cpu_count gauge
    system_cpu_count 10.0
    # HELP system_cpu_usage The "recent cpu usage" of the system the application is running in
    # TYPE system_cpu_usage gauge
    system_cpu_usage 0.10296991012114107
    # HELP system_load_average_1m The sum of the number of runnable entities queued to available processors and the number of runnable entities running on the available processors averaged over a period of time
    # TYPE system_load_average_1m gauge
    system_load_average_1m 2.31787109375
    # HELP tomcat_sessions_active_current_sessions
    # TYPE tomcat_sessions_active_current_sessions gauge
    tomcat_sessions_active_current_sessions 0.0
    # HELP tomcat_sessions_active_max_sessions
    # TYPE tomcat_sessions_active_max_sessions gauge
    tomcat_sessions_active_max_sessions 0.0
    # HELP tomcat_sessions_alive_max_seconds
    # TYPE tomcat_sessions_alive_max_seconds gauge
    tomcat_sessions_alive_max_seconds 0.0
    # HELP tomcat_sessions_created_sessions_total
    # TYPE tomcat_sessions_created_sessions_total counter
    tomcat_sessions_created_sessions_total 0.0
    # HELP tomcat_sessions_expired_sessions_total
    # TYPE tomcat_sessions_expired_sessions_total counter
    tomcat_sessions_expired_sessions_total 0.0
    # HELP tomcat_sessions_rejected_sessions_total
    # TYPE tomcat_sessions_rejected_sessions_total counter
    tomcat_sessions_rejected_sessions_total 0.0
    * Connection #0 to host localhost left intact

    prometheus 엔드포인트를 호출하면 위와 같이 각종 metric 정보를 응답한다.

     

    Prometheus & Grafana 설치

    Prometheus는 사운드클라우드에서 처음 개발된 오픈소스 시스템 모니터링 및 알림 도구 모음이다. Prometheus는 메트릭을 시계열 데이터로 수집 및 저장한다. 즉 메트릭 정보는 기록된 타임스탬프와 함께 저장되며 선택적으로 레이블이라고 하는 키-값 쌍이 함께 저장된다.

    Prometheus의 아키텍트는 다음과 같다.

    prometheus architecture
    출처: https://prometheus.io/docs/introduction/overview/#architecture

     

    Grafana는 오픈소스 기반의 데이터 시각화 및 대시보드 도구이다. Prometheus, Elasticsearch, Redis 등 다양한 데이터 소스와 연동할 수 있으며 시간에 따라 변화하는 시계열 데이터를 차트, 게이지, 테이블 등으로 시작화하는데 특화되어 있다.

     

    Prometheus가 애플리케이션으로부터 메트릭을 수집하고 저장하는 역할을 한다면 Grafana는 Prometheus가 저장한 데이터를 쿼리하고 시각화하는 역할을 한다.

     

    Prometheus와 Grafana에 대해서 자세한 내용을 파악하려면 다음 document를 참고하면 좋겠다.

    https://prometheus.io/docs/introduction/overview/

    https://grafana.com/docs/

     

    이제 로컬에 Prometheus와 Grafana를 설치해 보자. docker compose를 이용하여 설치해 보겠다.

    docker-compose.yml

    services:
      prometheus:
        image: prom/prometheus:latest
        container_name: prometheus
        volumes:
          - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
        ports:
          - "9090:9090"
        networks: [ demo ]
    
      grafana:
        image: grafana/grafana:latest
        container_name: grafana
        ports:
          - "3000:3000"
        environment:
          - GF_SECURITY_ADMIN_PASSWORD=admin
        networks: [ demo ]
    
    networks:
      demo: {}

     

    prometheus에 적용할 설정 파일은 docker-compose.yml 파일과 동일한 경로에 있어야 한다.

    grafana의 초기 admin 패스워드는 'admin'으로 지정하였다. 처음 grafana 접속 시 비밀번호 변경을 할 수 있다.

    다음은 prometheus.yml 설정이다.

    prometheus.yml

    global:
      scrape_interval: 10s
    
    scrape_configs:
      - job_name: 'spring-boot'
        metrics_path: /security-actuator/prometheus
        scheme: http
        static_configs:
          - targets: ['host.docker.internal:8081']
        basic_auth:
          username: 'admin'
          password: 'actuator-password'

    prometheus.yml 설정에는 메트릭 정보를 가져올 소스를 설정한다.

    애플리케이션의 /security-actuator/prometheus 엔드포인트로부터 메트릭 정보를 가져오기 위한 설정이다.

    prometheus 메트릭 정보를 가져오기 위해서는 basic 인증을 받아야 하므로 인증을 위한 정보도 설정하였다.

     

    위와 같이 작성 후 docker-compose.yml 파일이 있는 경로에서 다음 명령을 실행하여 prometheus, grafana를 설치하고 실행해 본다.

    docker compose up -d

     

    Grafana UI

    http://localhost:3000으로 Grafana UI에 접속한다.

    Connections > Data sources에서 Prometheus를 선택한다.

    grafana prometheus 연결

    Prometheus 설정 섹션에서 

    Connection 부분에 http://prometheus:9090으로 설정한다.

    docker-compose.yml 파일을 보면 demo이름의 동일 네트워크에 prometheus와 grafana가 있고 grafana 서버에서는 prometheus 서비스 이름으로 접속이 가능하므로 호스트 이름을 prometheus로 지정하였다.

     

    Connection >> Data sources 생성 후 해당 Data source에서 Build a dashboard를 통해서 대시보드를 직접 생성할 수 있다.

    grafana dashboard 설정
    직접 생성한 대시보드

     

    또한 https://grafana.com/grafana/dashboards/ 에서 제공하는 대시보드를 import 하여 사용할 수 있다.

    grafana UI에서 Home > Dashboards > New > Import 메뉴를 통해 Import 할 수 있다.

    dashboard import

     

    https://grafana.com/grafana/dashboards/ 에서 원하는 대시보드를 선택하여 'Copy ID to clipboard' 혹은 'Download JSON'을 선택하여 dashboard Import 설정 화면에서 클립보드에 복사한 ID를 입력하거나 다운로드한 JSON파일을 그대로 적용하여 dashboard를 import 할 수 있다.

    다음은 Spring Boot 2.1 System Monitor를 적용한 대시보드다.

    import를 이용한 dashboard

     

    끝.

    저작자표시 비영리 변경금지 (새창열림)

    '스프링부트' 카테고리의 다른 글

    Spring Boot Actuator - 5. 사용자 정의 Endpoint 만들기  (0) 2025.09.27
    Spring Boot Actuator - 4. Endpoint 커스텀  (0) 2025.09.24
    Spring Boot Actuator - 3. Actuator 보안  (0) 2025.09.23
    Spring Boot Actuator - 2. 주요 Endpoint  (0) 2025.09.21
    Spring Boot Actuator - 1. 시작하기  (0) 2025.09.17
    다음글
    다음 글이 없습니다.
    이전글
    이전 글이 없습니다.
    댓글
조회된 결과가 없습니다.
스킨 업데이트 안내
현재 이용하고 계신 스킨의 버전보다 더 높은 최신 버전이 감지 되었습니다. 최신버전 스킨 파일을 다운로드 받을 수 있는 페이지로 이동하시겠습니까?
("아니오" 를 선택할 시 30일 동안 최신 버전이 감지되어도 모달 창이 표시되지 않습니다.)
목차
표시할 목차가 없습니다.
    • 안녕하세요
    • 감사해요
    • 잘있어요

    티스토리툴바