![[Spring] 놀멍 서비스 개발 일지 - 로그 시스템 구축하기1](https://img1.daumcdn.net/thumb/R750x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAx4QS%2FbtsL1svBegQ%2Fyx6yGFzsuOkbVa0WScgn4K%2Fimg.png)
이 글은 반려견 동반 가능 시설 공유 플랫폼 '놀멍'의 모니터링 서버를 구축하는 과정입니다.
놀멍 서비스의 1차 MVP를 완성하고, 이를 배포하여 운영할 계획을 세웠습니다.
서비스 운영에 있어 필요한 요소를 고민하던 중, 서버 모니터링의 필요성을 느꼈습니다.
놀멍 서비스 요구사항
장애나 사고 발생 시 원인을 신속하게 파악하고 대응할 수 있는 시스템이 필요했습니다. 또한, 프론트엔드 개발자와의 협업 과정에서, 프론트엔드의 요청으로 로그를 확인할 때마다 서버의 도커에 접속하는 것은 매우 비효율 적이었습니다. 그렇기에 서비스의 요구사항은 다음과 같았습니다.
- 사용자에게 만족을 제공하기 위해 시스템은 항상 안정적으로 운영되어야 하며, 이상 징후를 지속적으로 감시할 수 있어야 한다.
- 시스템 이상 발생 시 원인 파악을 위한 정보를 제공할 수 있어야 한다.
이를 위해 로그 시스템의 필요성을 느꼈고, 로그 시스템을 통해 사용자들이 서비스를 이용하는 동안 장애 발생을 최소화하고, 장애가 발생했을 때 신속하게 대응하여 서비스 중단 시간을 줄이고자 했습니다.
ELK vs Grafana Loki
로그 시스템은 크게 ELK Stack과 Grafana Loki Stack이 있습니다.
ELK: Elasticsearch, Logstash, Kibana + Filebeat
Grafana Loki: Promtail + Loki + Grafana
ELK 스택과 Grafana Loki 스택의 장단점은 아래와 같습니다.
ELK | Grafana Loki | |
장점 | - Elastisearch는 복잡한 쿼리와 빠른 검색 속도를 자랑하는 전문 검색 엔진 - Logstash는 다양한 형식의 데이터를 처리하고 변환하는 강력한 도구 - 대용량 데이터 처리에 적합하며, 쉽게 확장할 수 있음 |
- 설정이 간단하고 운영하기 쉬운 경량 로그 처리 솔루션 - 로그 데이터를 압축하여 저장하여 로그 관리 비용 절감 - 메타데이터만을 인덱싱 하기에 저장 공간을 효율적으로 사용 |
단점 | - Elasticsearch는 상당한 메모리와 CPU 자원이 필요 - 여러 구성 요소 간의 통합 및 설정이 다소 복잡할 수 있음 - 운영 비용이 상대적으로 높음 |
- 장기적인 로그 데이터 관리에 있어 다소 제한적일 수 있음 - 아직 발전 중인 기술로, 커뮤니티 지원이나 자료가 ELK 스택에 비해 제한 |
각각의 장단점을 고려한 결과, 저희 팀은 Grafana Loki Stack을 사용하기로 결정하였습니다.
선택 이유는 다음과 같습니다.
- Prometheus와 Grafana를 사용한 경험이 있어 기존 시스템과의 호환성을 보장하는 Loki Stack을 사용
- ELK Stack을 단일 서버에 모두 설치할 경우 4GB 이상의 메모리를 요구하는 반면, Loki Stack은 상대적으로 가벼움
- Elasticsearch는 운영 비용이 발생
인프라 설계
처음 구상한 시스템 아키텍처는 위와 같습니다.
운영 서버에 SpringBoot 애플리케이션뿐만 아니라, Prometheus, Promtail, Loki, Grafana를 Docker를 사용하여 운영하고자 했습니다.
Promtail: log 파일의 변경을 감지하고, 파일 변경시 Loki 서버로 데이터를 전송
Loki: Promtail로부터 받은 데이터를 저장
Grafana: Loki 서버에 LogQL을 통해 데이터를 읽어오고, 대시보드 설정에 따라 다양한 UI로 데이터를 시각화
하지만 이러한 인프라를 구축한 후, 서버가 갑자기 버벅거리거나 갑자기 종료되는 문제가 발생하였습니다.
서버가 갑자기 다운되면서 504 Gateway Time-out 에러가 발생하였습니다.
AWS의 CloudWatch를 사용하여 확인해본 결과, 서버가 재부팅되면서 메모리 사용량이 0에서 시작해 Docker 컨테이너와 다른 프로그램들이 가동됨에 따라 점진적으로 높아졌고, 결국 메모리 사용률이 90%에 육박하는 것을 확인할 수 있었습니다.
더욱 자세히 알아보기 위해 free -h 명령어를 통해 메모리 사용량을 확인하였습니다.
결과는 위와 같이 나타났습니다.
제가 생각한 문제의 원인은 저희 서버가 t3.micro 인스턴스를 사용하여 메모리 크기가 1GB인 상황에서 여러 툴을 Docker로 많이 띄운 것이었습니다. Docker를 사용해 운영하는 툴들이 많아짐에 따라 메모리 사용량이 증가하고, 이로 인해 Buffer와 Cache 영역이 줄어들게 됩니다. Buffer와 Cache 영역이 줄어든다는 것은 속도가 떨어져 성능 저하로 이어진다고 판단하였습니다.
buff: 버퍼를 사용하고 있는 메모리양으로 시스템 성능 향상을 위해 커널에서 사용하는 메모리 양
cache: I/O 관련 작업을 빠르게 진행하기 위해 커널에서 사용하는 메모리 양
아래는 모든 컨테이너를 중단시킨 경우의 서버 메모리 상태입니다.
해결책
처음에 생각한 해결 방법은 서버 성능을 높이는 것이었습니다. 서버의 메모리 크기를 scale up하면 문제가 자연스럽게 해결될 것이라고 생각했습니다. 물론 이러한 방식으로도 해결할 수 있지만, 운영 서버에 문제가 발생할 경우 모니터링 시스템이 동일한 서버에 위치해 있어 적절한 대응이 어려울 수 있다는 문제점이 있었습니다.
이러한 이유로 모니터링 서버와 운영 서버를 분리하는 것이 맞다고 판단하였고, 인프라를 아래와 같이 최종적으로 수정하였습니다.
하지만 이렇게 인프라를 구축했음에도 불구하고 여전히 운영 서버의 메모리 사용률은 76%에 달했습니다.
결국 서버를 scale up하여 t3.micro에서 t3.medium으로 메모리 용량을 1GiB에서 2GiB로 증가시켰습니다.
그 결과, 메모리 사용률은 45% ~ 46%로 안정적인 모습을 보이는 것을 확인할 수 있었습니다.
LogBack
Spring에서 제공하는 Logging 관련 라이브러리 중 logback을 사용하였습니다.
SpringBoot 환경에서 별도의 Dependency를 추가하지 않아도 사용할 수 있어서 Logback을 선택하였습니다.
1. Trace
- 가장 상세한 로그 레벨로, 애플리케이션의 실행 흐름과 디버깅 정보를 상세히 기록한다. 주로 디버깅 시에 사용된다.
2. DEBUG
- 디버깅 목적으로 사용되며, 개발 단계에서 상세한 정보를 기록한다.
- 애플리케이션의 내부 동작을 이해하고 문제를 분석하는 데 도움을 준다.
3. INFO
- 정보성 메시지를 기록한다.
- 애플리케이션의 주요 이벤트나 실행 상태에 대한 정보를 전달한다.
4. WARN
- 경고성 메시지를 기록한다.
- 예상치 못한 문제나 잠재적인 오류 상황을 알리는 메시지이다.
- 애플리케이션이 정상적으로 동작하지만 주의가 필요한 상황을 알려준다.
5. ERROR
- 오류 메시지를 기록한다.
- 심각한 문제 또는 예외 상황을 나타내며, 애플리케이션의 정상적인 동작에 영향을 미칠 수 있는 문제를 알린다.
resources 패키지 하위에 logback-spring.xml 파일을 다음과 같이 생성했습니다.
<?xml version="1.0" encoding="UTF-8"?>
<!-- 30초마다 로그에 변경이 있는지 체크하는것 -->
<configuration scan="true" scanPeriod="30 seconds">
<!-- springProfile 태그를 사용하여 profile 별 property 값 설정 -->
<springProfile name="prod">
<!-- local log file path -->
<property name="LOG_PATH" value="./app/logs"/>
</springProfile>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<!-- [시간][로그레벨][thread이름] logger이름 메시지 \n 의 형식으로 출력 -->
<pattern>%green([%d{yyyy-MM-dd HH:mm:ss.SSS}]) %highlight([%-5level]) %magenta([%thread]) %cyan(%logger) %msg%n</pattern>
</encoder>
</appender>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/application.log</file>
<encoder>
<pattern>[%d{yyyy-MM-dd HH:mm:ss:SSS}] [%-5level] [%thread] %logger %msg%n</pattern>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/nolmung-%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>2</maxHistory>
<totalSizeCap>15MB</totalSizeCap>
</rollingPolicy>
</appender>
<appender name="ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>error</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<file>${LOG_PATH}/err_application.log</file>
<encoder>
<pattern>[%d{yyyy-MM-dd HH:mm:ss:SSS}] [%-5level] [%thread] %logger %msg%n</pattern>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/nolmung-error-%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>5</maxHistory>
<totalSizeCap>15MB</totalSizeCap>
</rollingPolicy>
</appender>
<root level="INFO">
<!-- INFO단계 위로 모두 아래의 console 방식으로 출력하는것임 -->
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE"/>
<appender-ref ref="ERROR"/>
</root>
</configuration>
기본 로그 레벨을 INFO로 설정하였고, ERROR 로그만 기록하는 별도의 파일을 생성하였습니다.
보관 기간이나 로그 레벨은 상황에 따라 조정할 필요가 있을 것 같습니다.
로그 파일이 잘 생성된 것을 확인할 수 있습니다.
인프라 구축 과정은 다음 글에서 소개하겠습니다!
참고자료
불편했던 로그 시스템 전환기 with PLG
이번글에서는 Observability 도구로써 PortfoGram에 Loki,Promtail, Grafana를 사용한 경험에 대해 이야기해보겠습니다.
medium.com
LogBack을 통한 효율적인 로그 관리
로그 관리의 필요성을 느낀 필자는 Slf4j의 LogBack을 사용해서 문제를 해결한다..!
velog.io
'Spring' 카테고리의 다른 글
[Spring] 놀멍 서비스 개발 일지 - 로그 시스템 구축하기2 (2) | 2025.02.04 |
---|---|
[Spring] 놀멍 서비스 개발 일지 - 지도 화면 개발하기2(공간 인덱스 적용) (0) | 2025.01.07 |
[Spring] 놀멍 서비스 개발 일지 - 지도 화면 개발하기1 (0) | 2025.01.05 |
[Spring] Redis 테스트 환경 구축하기(Embedded Redis) (2) | 2024.12.31 |
[Spring] URL 이미지 리사이징 후, S3에 업로드 (3) | 2024.07.28 |
느리더라도 단단하게 성장하고자 합니다!
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!