이번 글에서는 Docker Compose를 활용해 MyBoard 애플리케이션의 모니터링 환경을 구축하는 과정을 단계별로 설명합니다.
MySQL과 Redis의 메트릭을 Prometheus로 수집하고 Grafana로 시각화하는 과정을 다루며, 설정 과정에서 발생한 주요 이슈와 해결법도 함께 정리했습니다.
Prometheus는 직접 MySQL이나 Redis에서 메트릭을 가져오는 것이 아니라, Exporter를 통해 메트릭을 수집합니다.
Exporter는 각 서비스의 내부 상태를 Prometheus 형식으로 변환해 HTTP로 노출하는 역할을 합니다.
docker-compose.yml
의 MySQL 부분은 다음과 같습니다
services:
mysqldb:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: myboard
TZ: Asia/Seoul
volumes:
- ./mysql-data:/var/lib/mysql
- ./init-exporter.sql:/docker-entrypoint-initdb.d/init-exporter.sql:ro
ports: ["3306:3306"]
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-uroot", "-proot"]
interval: 5s; timeout: 2s; retries: 5
mysql-exporter:
image: prom/mysqld-exporter:latest
platform: linux/amd64
command:
- "--config.my-cnf=/etc/mysql_exporter.cnf"
- "--web.listen-address=:9104"
volumes:
- ./etc/mysql_exporter.cnf:/etc/mysql_exporter.cnf:ro
ports: ["9104:9104"]
depends_on:
- mysqldb
Prometheus가 메트릭을 스크랩하려면 서비스별 Exporter가 필요합니다.
Exporter는 해당 서비스의 내부 메트릭을 Prometheus 형식으로 변환해 HTTP로 노출합니다.
MySQL Exporter를 사용하려면 별도 계정을 생성하고 적절한 권한을 부여해야 합니다.
아래 SQL 스크립트를 /docker-entrypoint-initdb.d/
경로에 바인딩하면,
컨테이너가 최초 실행될 때만 이 SQL이 자동으로 실행됩니다.
init-exporter.sql
(빈 볼륨 첫 기동 시 실행) CREATE USER 'exporter'@'%' IDENTIFIED BY 'export_pwd';
GRANT PROCESS, REPLICATION CLIENT, SELECT ON *.* TO 'exporter'@'%';
위에서 생성한 exporter
전용 계정에 대한 정보를 일치하게 mysql-exporter
실행시 필요한 .cnf
파일에 동일하게 값을 넣어줍니다.
etc/mysql_exporter.cnf
[client]
user=exporter
password=export_pwd
host=mysqldb
services:
redis:
image: redis:latest
ports: ["6379:6379"]
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s; timeout: 2s; retries: 5
redis-exporter:
image: oliver006/redis_exporter:latest
command: ["--redis.addr=redis:6379"]
ports: ["9121:9121"]
depends_on:
- redis
redis exporter는 mysql과 다르게 특정 계정 및 권한이 필요하지 않습니다.
MySQL과 Redis가 정상적으로 시작된 이후에만 Spring Boot 애플리케이션을 실행하도록, 컨테이너의 상태를 체크하는 depends_on
조건을 추가했습니다.
services:
springboot:
build:
context: .
dockerfile: Dockerfile
image: my-board:latest
environment:
SPRING_PROFILES_ACTIVE: docker
SPRING_DATASOURCE_URL: jdbc:mysql://mysqldb:3306/myboard?serverTimezone=Asia/Seoul
SPRING_DATASOURCE_USERNAME:
SPRING_DATASOURCE_PASSWORD:
SPRING_DATA_REDIS_HOST: redis
SPRING_DATA_REDIS_PORT: 6379
ports: ["8080:8080"]
depends_on:
mysqldb:
condition: service_healthy
redis:
condition: service_healthy
FROM openjdk:21-jdk
COPY build/libs/my-board-0.0.1-SNAPSHOT.jar app.jar
ENTRYPOINT ["java", "-jar", "app.jar"]
코드 수정 후 재빌드를 하지 않고 docker compose up -d --build
를 실행했었는데, 이 과정에서 오류는 아래에서 정리하겠습니다.
모니터링을 담당하는 Prometheus와 Grafana가 다른 서비스보다 나중에 시작되도록 depends_on
옵션을 설정하고, 메트릭 수집을 위한 각 컨테이너를 연결합니다.
이때 docker-compose.yml
에서 정의한 컨테이너 이름과 동일해야합니다.
services:
prometheus:
image: prom/prometheus:latest
volumes:
- ./prometheus:/etc/prometheus
ports: ["9090:9090"]
depends_on:
- springboot
- mysql-exporter
- redis-exporter
grafana:
image: grafana/grafana:latest
ports: ["3000:3000"]
depends_on:
- prometheus
아직 springboot 와 mysql 만 필요하지만, 추후 성능 개선을 위해 redis 를 도입할 수 있으니 redis-exporter
를 넣어 함께 데이터를 수집하겠습니다.
http://localhost:9090/targets
접속시 나타나는 Prometheus UI입니다.
위와 같은 UI를 띄우기 위한 설정 방법을 정리하겠습니다.
Prometheus가 15초 간격으로 각 엔드포인트를 스크랩하도록 설정합니다.
Prometheus 설정 (prometheus.yml
)
global:
scrape_interval: 15s
scrape_configs:
- job_name: 'spring-boot'
metrics_path: '/actuator/prometheus'
static_configs:
- targets: ['springboot:8080']
- job_name: 'mysql'
static_configs:
- targets: ['mysql-exporter:9104']
- job_name: 'redis'
static_configs:
- targets: ['redis-exporter:9121']
job_name
Prometheus UI에 표시될 서비스 그룹의 이름입니다.
이 예제에서는 spring-boot
, mysql
, redis
3개의 job이 생성되며,
UI의 Status → Targets 화면에서 각각의 이름으로 확인할 수 있습니다.
metrics_path
메트릭을 수집할 엔드포인트 경로입니다.
대부분의 애플리케이션은 기본 /metrics
를 사용하지만,
Spring Boot Actuator를 쓸 때는 /actuator/prometheus
로 변경해야 합니다.
static_configs.targets
실제 메트릭을 가져올 호스트와 포트입니다.
springboot:8080
→ Spring Boot 컨테이너(Actuator)mysql-exporter:9104
→ MySQL Exporterredis-exporter:9121
→ Redis Exporter이 설정이 완료되면, Prometheus가 15초마다 지정된 엔드포인트로 HTTP 요청을 보내고,
수집된 시계열 데이터가 spring-boot, mysql, redis 세 가지 job으로 구분되어 저장됩니다.
Prometheus로 메트릭을 수집하고 Grafana에서 시각화하려면, Spring Boot의 Actuator 엔드포인트를 외부에 노출하고 시큐리티 설정에서 /actuator/**
경로를 모두 허용해야 합니다.
application-docker.yml
에 아래와 같이 설정하면, /actuator/health
, /actuator/info
, /actuator/prometheus
세 개의 엔드포인트가 열립니다.
# application-docker.yml
management:
endpoints:
web:
exposure:
include: health,info,prometheus
endpoint:
health:
show-details: always
이제 /actuator/health
호출 시 데이터베이스(db
), 디스크 용량(diskSpace
), 네트워크 핑(ping
), Redis 연결(redis
), SSL 설정(ssl
) 등 상세한 상태 정보를 확인할 수 있습니다.
localhost:8080/actuator/health
접속시 나오는 화면입니다.
가시적으로 Spring boot 서버 health 체크를 할 수 있습니다.
위 사진에서 확인할 수 있듯이, 데이터베이스와 애플리케이션의 전반적인 상태는 정상(UP
)으로 표시되었으나, Redis 연결 상태는 DOWN
으로 나타났습니다.
이 원인에 대해서는 하단의 트러블슈팅 항목에서 자세히 다루겠습니다.
Spring Security를 사용하는 환경에서는 Actuator 경로를 인증 없이 접근할 수 있도록 설정해줘야 합니다. 이는 SecurityFilterChain
에서 다음과 같이 처리할 수 있습니다.
@Bean
public SecurityFilterChain apiSecurityChain(HttpSecurity http) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable)
.sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(authz -> authz
.requestMatchers(ACTUATOR_PATTERN).permitAll()
.requestMatchers(SWAGGER_PATTERNS).permitAll()
.requestMatchers(STATIC_RESOURCES_PATTERNS).permitAll()
.requestMatchers(PERMIT_ALL_PATTERNS).permitAll()
.requestMatchers(PUBLIC_ENDPOINTS).permitAll()
.requestMatchers(AUTH_ENDPOINTS).permitAll()
.anyRequest().authenticated()
)
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
위 설정은 /actuator/**
경로를 인증 없이 허용하며, 나머지 API 경로에 대해서는 인증된 사용자만 접근할 수 있도록 구성합니다.
소스 코드 수정 후, Docker Compose를 통해 컨테이너를 재기동했지만 변경 사항이 반영되지 않는 경우가 있었습니다.
이는 Dockerfile에서 사용하는 .jar
파일이 갱신되지 않고 캐시된 이전 파일을 사용하기 때문입니다.
기본 Dockerfile은 다음과 같습니다.
FROM openjdk:21-jdk
COPY build/libs/my-board-0.0.1-SNAPSHOT.jar app.jar
ENTRYPOINT ["java", "-jar", "app.jar"]
docker compose up -d --build
명령어는 Dockerfile 자체나 .jar
파일이 변경될 때만 새로운 이미지를 빌드합니다.
따라서 코드 변경 후 다음과 같은 절차를 반드시 수행해야 합니다.
1. ./gradlew clean build -x test
명령어로 최신 .jar
파일을 생성합니다.
2. 이후 docker compose up -d --build
로 이미지를 재빌드하고 컨테이너를 재시작합니다.
Prometheus가 Spring Boot 애플리케이션의 메트릭은 정상적으로 수집하였지만, /actuator/health
엔드포인트에서 상태가 DOWN
으로 나타났습니다.
Spring Actuator을 통해 로그를 분석해본 결과, Redis 연결 실패 (RedisConnectionFailureException
)가 원인이었습니다.
이는 애플리케이션 내부에서 Redis 호스트 정보를 localhost
로 하드코딩하여, 컨테이너 내부 네트워크에서 Redis를 인식하지 못한 것이 문제였습니다.
아래처럼 설정을 변경하여 해결하였습니다.
// 수정 전
@Value("localhost") private String redisHost;
@Value("6379") private int redisPort;
// 수정 후
@Value("${spring.data.redis.host}") private String redisHost;
@Value("${spring.data.redis.port}") private int redisPort;
이후 application-docker.yml
과 docker-compose.yml
에서 다음과 같이 환경 변수를 설정하여 컨테이너 간 통신이 정상적으로 이루어지게 하였습니다.
SPRING_DATA_REDIS_HOST: redis
SPRING_DATA_REDIS_PORT: 6379
이렇게 설정을 수정한 후 다시 실행했을 때, Health 상태가 정상적으로 UP
으로 변경되는 것을 확인할 수 있었습니다.
이번 글에서는 Docker Compose를 활용한 Prometheus-Grafana 모니터링 환경 구축 과정과 발생했던 주요 트러블슈팅 사항을 정리하였습니다.