일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 |
- Til
- docker
- SQL
- Java
- 디자인 패턴
- 연습문제
- 알고리즘
- LV03
- LV.02
- Lv.0
- Redis
- LV02
- S3
- JPA
- 이것이 자바다
- 일정관리 프로젝트
- Join
- mysql
- 데이터 베이스
- 일정관리프로젝트
- 프로그래머스
- spring boot
- LV1
- 포트폴리오
- LV0
- GIT
- 배열
- LV01
- 코테
- CoffiesVol.02
- Today
- Total
코드 저장소.
Spring 서비스의 운영 모니터링 환경 구축기2: Prometheus, Grafana, Loki, Kafka/Redis Exporter 본문
Spring 서비스의 운영 모니터링 환경 구축기2: Prometheus, Grafana, Loki, Kafka/Redis Exporter
slown 2025. 6. 20. 22:00목차
1.기존 구조의 문제점
2.문제 분석
3.문제 해결
4.후기
1.기존 구조의 문제점
일정 관리 프로젝트를 라이트세일의 2GB서버에 띄우고 모니터링을 구축을 하기 위해서 도커 컴포즈를 사용해서 띄운 것은 아래와 같습니다.
- Spring Boot
- Nginx
- Redis
- Kafka
- ZooKeeper
- Grafana
- Promtail
- Prometheus
- Loki
- Kafka Exporter/ Redis Exporter
그리고 사진과 같이 모든 서비스를 하나의 서버에 동시에 띄우니 1.53GB까지 올라가더니 모니터링을 켜면 메모리 과부하로 인해서 서버가 다운이 되는 현상이 발생을 했습니다.
2.문제 분석
우선은 이러한 문제점에 대한 원인은 아래와 같습니다.
- 모니터링 스택이 가벼운 줄 알았으나, Loki,Grafana,Prometheus 자체가 상당한 메모리를 소모한다.
- Promtail 로그 경로가 Docker Volume으로 마운트된 상태에서 로컬 디스크 사용량도 지속적으로 증가.
- 결과적으로 로그가 계속 쌓이면서 지속적인 쓰기 작업을 유발하며 로그가 커질수록 디스크의 I/O 대역폭을 점유하여 전체 시스템 성능을 갉아먹는 병목현상이 발생.
3.문제 해결
3-1. 모니터링 서버를 분리.
모니터링 스택(Grafana,Prometheus,Loki)을 별도의 Lightsail 인스턴스로 분리
서비스 서버는 Promtail,Exporter만 남기기.
3-2.로그 관리 자동화(logrotate)
서비스 서버 내 로그가 계속 쌓이기 때문에 logrotate를 사용해서 일별 순환, 압축, 자동삭제를 설정을 하기로 했다.
우선은 해당 디렉토리에 파일을 생성
sudo nano /etc/logrotate.d/schedule-app

logrotate를 사용해 일별 순환, 압축, 자동 삭제 설정
Permission 문제(Permission denied, insecure directory) 해결
- 로그 파일 소유권을 ubuntu:ubuntu로 변경
- logrotate 설정에 su ubuntu ubuntu 추가
3-3.JVM 성능 튜닝
추가적으로 서버의 메모리를 줄이기 위해서 선택한 방법은 Jib으로 빌드를 할때 JVM을 튜닝을 하는 방식을 생각했습니다. 이유는 다음과 같습니다.
- JVM은 기본 설정만으로도 수백 MB 이상의 메모리를 사용하는 경우가 많습니다.
- Spring Boot 기반 서비스는 클래스 로딩, 프록시 생성, Bean 초기화 등으로 인해 Metaspace와 Heap을 빠르게 점유하게 됩니다.
이번 프로젝트에서는 제한된 2GB 메모리 환경에서 안정적으로 운영하기 위해 다음과 같은 JVM 튜닝을 적용하였다.
jvmFlags = ['-Dserver.port=8082',
'-Dspring.profiles.active=prod',
'-Xms256m',
'-Xmx512m',
'-XX:+UseG1GC',
'-XX:MaxMetaspaceSize=512m',
'-XX:MaxDirectMemorySize=64m',
'-XX:+UseContainerSupport',
'-Duser.timezone=Asia/Seoul']
이렇게 튜닝을 하게된 다음에 그라파나를 사용해서 튜닝전후를 비교를 해봤습니다.
대시보드의 구성은 왼쪽위부터 CPU 사용률,Thread 수, GC지연시간순으로 대시보드를 구성을 했습니다. 그래프의 설명은 아래와 같습니다.
CPU 사용률
- 튜닝 전:
- CPU 사용률이 1~3%에서 잦은 스파이크 발생.
- “모니터링, Kafka, Redis, Loki, Grafana, Prometheus 등 모든 컨테이너가
한 서버에 몰려 CPU 자원을 서로 뺏으며,
GC 및 서비스 자체의 CPU 소모까지 더해져
일정하지 않은 불안정한 패턴이 반복되었습니다.”
- 튜닝 후:
- JVM 옵션 튜닝(-Xmx, -Xms, G1GC 등)과 모니터링 인프라 분리 이후 CPU 사용률이 0.5~1.5% 사이로 낮아지고
스파이크 없이 일정하게 유지됨. - 불필요한 컨테이너 리소스 경쟁이 줄고, JVM이 컨테이너 환경을 정확히 인식(UseContainerSupport 적용)하면서
CPU 사용량이 안정화되었습니다.
- JVM 옵션 튜닝(-Xmx, -Xms, G1GC 등)과 모니터링 인프라 분리 이후 CPU 사용률이 0.5~1.5% 사이로 낮아지고
Thread 수
- 튜닝 전:
- Thread가 75개에서 급격히 60개 이하로 떨어지는 구간이 있습니다. 이 때는 JVM이 리소스 한계에 다다라 불필요한 Thread가 강제 종료되거나 서비스가 일시적으로 다운된 것으로 추정됩니다.
- 튜닝 후:
- Thread 수가 73~75개에서 고정적으로 유지. 불필요한 생성/종료, 강제 다운이 없어 서비스의 정상 상태가 지속적으로 유지됩니다.
GC Pause Time
- 튜닝 전:
- GC Pause Time이 0.1초 이상으로 급등하는 구간이 반복적으로 등장.
이 때 서비스 전체가 Stop-the-world에 걸려 사용자 요청이 중단되거나, 경우에 따라 서버가 다운될 위험도 있었음.
- GC Pause Time이 0.1초 이상으로 급등하는 구간이 반복적으로 등장.
- 튜닝 후:
- Heap/Metaspace 제한, G1GC 적용 등으로 GC Pause Time이 0.01초 이하로 줄고, 전체적으로 일관된 값 유지했습니다. 더 이상 스파이크가 나타나지 않았고 서비스의 지연/다운 현상이 사라졌습니다.
결과적으로 모니터링서버이관 및 JVM 튜닝결과 아래와 같이 낮출수가 있었습니다.
전체 시스템 영향 및 장애 개선
- 튜닝 전에는
- 여러 서비스/모니터링 스택이 한 서버를 공유하며 메모리, CPU, 디스크 I/O 등 모든 리소스 한계에 부딪혔고,
GC Pause, Thread 급감 등 지표의 불안정함이 곧 서비스 다운/지연으로 이어졌습니다.
- 여러 서비스/모니터링 스택이 한 서버를 공유하며 메모리, CPU, 디스크 I/O 등 모든 리소스 한계에 부딪혔고,
- 튜닝 후에는
- 불필요한 컨테이너 분리, JVM/GC/Heap 옵션 최적화, 리소스 사용량 제한 및 효율화로 모든 핵심 지표가 “일정하고 안정적”으로 바뀌었고, 실운영에서 서비스 장애나 다운타임 없이 지속적으로 안정 운영이 가능해졌다.
4.후기
이번 프로젝트를 진행하면서, 단순히 서비스 기능만 구현하는 것과 실제 운영환경에서의 시스템 안정화가 얼마나 다른 차원의 문제인지 직접 경험할 수 있었습니다. 초기에는 한정된 서버(2GB)에 모든 서비스(Spring Boot, Kafka, Redis, Nginx 등)와 모니터링 스택(Grafana, Prometheus, Loki, Exporter 등)을 한꺼번에 띄우는 구조로 빠르게 서비스를 구축했습니다.
처음엔 아무런 문제가 없어 보였지만, 실제 운영환경에서 로그와 메트릭이 쌓이면서 메모리 과부하와 GC Pause Time의 스파이크 현상, 서버 다운(OutOfMemoryError)과 같은 예기치 못한 장애가 반복적으로 발생했습니다. 이 과정에서 단순히 JVM 옵션을 바꿔보거나 코드를 수정하는 것만으로는 문제가 근본적으로 해결되지 않는다는 사실을 깨달았습니다. 그래서 실제 운영 데이터를 기반으로 문제 원인을 예상하고
- 모니터링 서버를 별도 인스턴스로 분리
- 로그 관리를 logrotate로 자동화
- JVM Heap/Metaspace/GC/DirectMemory 옵션을 운영환경에 맞게 튜닝
“구조적 개선”과 “정량적 성능 최적화”를 병행했습니다.
특히 Grafana 대시보드와 프로메테우스 메트릭을 활용하여
- 튜닝 전에는 CPU/GC/Thread 등 모든 지표에서 불규칙한 스파이크와 비정상적인 감소 현상이 빈번하게 발생했지만,
- 튜닝 및 구조 개선 후에는 모든 지표가 일정하게 유지되고 서비스가 다운 없이 안정적으로 운영되는 것을 수치와 그래프로 직접 확인할 수 있었습니다.
이 경험을 통해 “운영 모니터링-문제 진단-아키텍처 개선-성능 튜닝-효과 검증” 이라는 실제 DevOps/운영개선 사이클 전체를
직접 체득할 수 있었고, 단순 개발자에서 한 단계 성장하는 계기가 되었다고 생각합니다.
이번 프로젝트에서 얻은 교훈은,
- “장애를 두려워하지 않고 데이터로 원인을 분석한다”
- “단순 기능 개발이 아닌, 실제 운영 안정성을 고민한다”
- “서비스 품질을 수치로 증명하는 습관을 갖는다”
는 것이었습니다.
이 다음으로 진행할 작업은 다음과 같습니다.
- 서비스서버를 안정적으로 운영을 하기 위해서 GraalVM을 적용해서 서비스서버의 용량을 더 줄이기.
- 성능 측정 도구를 사용해서 부하 테스트하기.
- 장애발생시 알림 연동
'포폴 > 일정관리앱' 카테고리의 다른 글
Spring 서비스의 운영 모니터링 환경 구축기1: Prometheus, Grafana, Loki, Kafka/Redis Exporter (0) | 2025.06.17 |
---|---|
Kafka + Redis + MySQL 환경을 Testcontainers로 통합 테스트하기 (0) | 2025.06.10 |
프로젝트 배포2 - GithubAction 을 활용한 CI/CD구축하기. (0) | 2025.06.09 |
Jib을 활용한 Docker 이미지 자동화 빌드 경험기 (0) | 2025.05.27 |
일정알림기능4-아웃 박스 패턴을 적용하기(트랜잭션 일관성과 장애 복원력 강화) (0) | 2025.05.24 |