Docker Compose로 모니터링 환경 구축하기 (MySQL, Redis, Prometheus, Grafana)

dongwoo you·2025년 6월 4일
0

myboard-dev-log

목록 보기
6/8
post-thumbnail

이번 글에서는 Docker Compose를 활용해 MyBoard 애플리케이션의 모니터링 환경을 구축하는 과정을 단계별로 설명합니다.

MySQL과 Redis의 메트릭을 Prometheus로 수집하고 Grafana로 시각화하는 과정을 다루며, 설정 과정에서 발생한 주요 이슈와 해결법도 함께 정리했습니다.


1️⃣ MySQL·Redis 및 Exporter 설정

Prometheus는 직접 MySQL이나 Redis에서 메트릭을 가져오는 것이 아니라, Exporter를 통해 메트릭을 수집합니다.

Exporter는 각 서비스의 내부 상태를 Prometheus 형식으로 변환해 HTTP로 노출하는 역할을 합니다.

▶️ MySQL 설정 + Exporter

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

▶️ Redis 설정 + Exporter

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과 다르게 특정 계정 및 권한이 필요하지 않습니다.


2️⃣ Spring Boot 애플리케이션 설정

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
  • 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를 실행했었는데, 이 과정에서 오류는 아래에서 정리하겠습니다.


3️⃣ Prometheus·Grafana

모니터링을 담당하는 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

아직 springbootmysql 만 필요하지만, 추후 성능 개선을 위해 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 Exporter
    • redis-exporter:9121 → Redis Exporter

이 설정이 완료되면, Prometheus가 15초마다 지정된 엔드포인트로 HTTP 요청을 보내고,
수집된 시계열 데이터가 spring-boot, mysql, redis 세 가지 job으로 구분되어 저장됩니다.


4️⃣ Spring Boot Actuator & 보안 설정

Spring Boot Actuator

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 이미지 재빌드(--build) 문제

소스 코드 수정 후, 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 Health DOWN (Redis 연결 문제)

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.ymldocker-compose.yml에서 다음과 같이 환경 변수를 설정하여 컨테이너 간 통신이 정상적으로 이루어지게 하였습니다.

SPRING_DATA_REDIS_HOST: redis
SPRING_DATA_REDIS_PORT: 6379

이렇게 설정을 수정한 후 다시 실행했을 때, Health 상태가 정상적으로 UP으로 변경되는 것을 확인할 수 있었습니다.


이번 글에서는 Docker Compose를 활용한 Prometheus-Grafana 모니터링 환경 구축 과정과 발생했던 주요 트러블슈팅 사항을 정리하였습니다.

profile
꾸준함 빼면 시체

0개의 댓글