Spring Boot
의 API 요청 및 응답에 대한 정보와 로그를 확인
Spring Boot
의 JVM
정보를 확인
Spring Boot
가 동작하는 컴퓨터의 하드웨어 정보를 확인
팀 회의를 통해 아래 3가지 중 1, 2번의 선택지가 있었습니다.
Grafana
Loki
Prometheus
Elastic Search
Logstash
Kibana
CloudWatch
EC2
를 사용하는 특성 상 CloudWatch
도 사용 가능 했지만 당시 잘 몰랐기에 선택지에 없었습니다.
ELK
의 Elastic Search
는 인덱싱 방식이 난 단어를 모두 토큰화해서 하지만 Loki
는 label
별로 인덱싱 해서 저장한다고 되어있었습니다.
저장 코스트가 다른 부분이 있었는데 소규모 프로젝트여서 큰문제는 없었습니다.
우아한 형제들 기술 블로그 중 ELK
에서 Loki
로의 전환하는 글도 있었고, 1번과 2번 사이에서 저희는 Spring Boot
의 기술 블로그 중 하나 Baeldung
에 Loki
를 통해 로깅하는 방법을 발견하였습니다.
조금 더 빠르게 적용하여 해볼 수 있을 것이라 생각되어 바로 시도해보게 되었습니다.
저희가 사용할 프로그램은 세부적으로 총 4가지 입니다.
Grafana
로키와 프로메테우스에 정보를 API 요청하여 정보를 가져와 시각화 함Loki
스프링이 로키 API를 호출하여 전달해준 로그를 저장해 둠Prometheus
스프링과 노드 익스포터 API 요청 하여 정보를 일정 주기마다 얻어와서 저장해 둠Node Exporter
하드웨어 정보를 주시하고 있다가 API 요청 받으면 정보를 응답해줌세부적인 역할은 아래와 같습니다.
Grafana
는 단순 데이터를 시각화 및 알림 기능을 제공하지 단독으로는 데이터를 가지고 있지 않아 할 수 있는게 없습니다!Loki
와 Prometheus
는 각각 데이터를 저장해두고 있지만 Node Exporter
는 하드웨어 정보를 주시만 하고 있지 매트릭 정보이므로 정보를 저장해두지 않습니다!Loki
는 로그 담당이고 Prometheus
는 매트릭 정보 담당 입니다.누가 어디로 API 요청하는지 방향을 알고 있으면 흐름을 이해하기 더욱 수월합니다.
Spring Boot
-> Loki
Prometheus
-> Spring Boot
Node Exporter
Grafana
-> Loki
Prometheus
이 정도 알고 나면 직접 실행하고 적용해보며 이해하면 훨씬 빠르게 습득하실 수 있습니다.
모니터링 도구는 역할이 서로 다른 여러 프로그램을 사용하게 되므로 구성 및 관리에 용이하도록 도커 컴포즈를 사용할 예정입니다.
해당 Baeldung
에 있는 게시글을 통해 필요한 부분만 적용하여 빠르게 실행해 보았고, 이를 바탕으로 Grafana
Loki
Prometheus
Node Exporter
총 4개의 컨테이너를 도커 컴포즈로 묶어 실행할 수 있는 로컬용 docker-compose-local.yaml
을 resources
폴더에 두고 실행하였습니다.
docker compose -f docker-compose-local.yaml up -d
volumes:
grafana:
prom:
services:
loki:
image: grafana/loki
restart: unless-stopped
ports:
- 3100:3100
command: -config.file=/etc/loki/local-config.yaml
node:
image: prom/node-exporter
restart: unless-stopped
pid: host
ports:
- 9100:9100
command:
- '--path.rootfs=/host'
volumes:
- '/:/host:ro'
prom:
image: prom/prometheus
restart: unless-stopped
volumes:
- prom:/prometheus
ports:
- 9090:9090
links:
- node
entrypoint:
- sh
- -euc
- |
cat <<EOF > /prometheus/prometheus.yml
scrape_configs:
- job_name: 'app'
metrics_path: '/actuator/prometheus'
scrape_interval: 15s
static_configs:
- targets: ['host.docker.internal:8080']
- job_name: 'node'
scrape_interval: 15s
static_configs:
- targets: ['node:9100']
EOF
/bin/prometheus
grafana:
image: grafana/grafana
restart: unless-stopped
volumes:
- grafana:/var/lib/grafana
environment:
- GF_AUTH_ANONYMOUS_ENABLED=true
- GF_AUTH_ANONYMOUS_ORG_ROLE=Admin
ports:
- 3000:3000
links:
- loki
- prom
entrypoint:
- sh
- -euc
- |
mkdir -p /etc/grafana/provisioning/datasources
cat <<EOF > /etc/grafana/provisioning/datasources/ds.yaml
apiVersion: 1
datasources:
- name: Loki
type: loki
access: proxy
orgId: 1
url: http://loki:3100
basicAuth: false
isDefault: true
version: 1
editable: false
- name: Prometheus
type: prometheus
access: proxy
orgId: 1
url: http://prom:9090
basicAuth: false
isDefault: false
editable: false
EOF
/run.sh
이렇게 우선 로컬로 모든 컨테이너를 실행시켜 하나씩 웹 접속을 하여 어떻게 동작하는지 테스트 해보도록 하겠습니다.
도커 컴포즈에 로그인을 스킵하도록 설정해두어 바로 홈화면이 보이면 성공입니다.
로키는 API 서비스만 제공하기 때문에 위 링크 접속 시 단순히 레디라고 보이면 성공입니다.
프로메테우스 화면이 보이는지만 확인해주세요.
아래 화면이 보이시면 성공입니다!
기본적인 실행 및 확인이 끝났다면 그라파나에서 여러 데이터 소스를 통해 대시보드를 구성하도록 하겠습니다.
그라파나와 로키에 대해서는 도커 컴포즈의 entrypoint
로 구성 파일을 이미 작성해놓은 상태입니다.
이제 아래 2가지를 해줄 것입니다.
Actuator
를 사용해 Endpoint
/actuator/prmetheus
API 서버를 엽니다.Logback
설정 파일을 구성하여 로그가 생길 때 마다 Loki
의 API 를 호출하여 로그를 전송합니다.build.gradle
에 아래 의존성을 추가합니다.
implementation 'org.springframework.boot:spring-boot-starter-actuator'
코끼리를 새로고침 하였다면 Actuator
가 열렸는지 확인을 해봅시다!
SpringApplication
을 실행해세요.
이후 http://localhost:8080/actuator 에 접속 후 아래와 같이 보인다면 성공입니다!
build.gradle
에 아래 의존성을 추가합니다.
implementation 'io.micrometer:micrometer-registry-prometheus'
application.yaml
에 아래 내용을 추가합니다.
management:
endpoints:
web:
exposure:
include: prometheus
이후 SpringApplication
을 재시작 한 뒤 아래 링크로 접속했을 때 프로메테우스에 필요한 데이터 형식이 보일 경우 성공입니다.
http://localhost:8080/actuator/prometheus
이로써 스프링 상태 정보를 매트릭으로 제공하는 구성을 완료하였습니다.
resources
폴더에 logback-spring.xml
을 만들어 아래 내용을 작성해 줍니다.
Logback
은 스프링 자체에 포함된 도구이기 떄문에 따로 설치해주지 않아도 됩니다!
중간 즈음 app=Pengcook
되어 있는 부분에 Pengcook
을 본인의 어플리케이션 이름으로 변경해주세요!! (공백이 없는 것이 테스트에 용이합니다.)
Console
에는 기존 스프링 부트 기본 logback
패턴을 사용하여 원래 보였던 것과 같이 출력되게 합니다.Loki
에는 특정 URL
주소로 API 요청을 보낼 거고, 특정 label
, 메세지는 loki4j
의 logback.JsonLayout
을 사용하는 것입니다.<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<!-- <springProfile name="local">-->
<property name="LOKI_URL" value="http://localhost:3100/loki/api/v1/push"/>
<!-- </springProfile>-->
<!-- <springProfile name="dev">-->
<!-- <property name="LOKI_URL" value="http://loki:3100/loki/api/v1/push"/>-->
<!-- </springProfile>-->
<appender name="LOKI" class="com.github.loki4j.logback.Loki4jAppender">
<http>
<url>${LOKI_URL}</url>
</http>
<format>
<label>
<pattern>app=Pengcook,host=${HOSTNAME},level=%level</pattern>
<readMarkers>true</readMarkers>
</label>
<message class="com.github.loki4j.logback.JsonLayout"/>
</format>
</appender>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="LOKI"/>
<appender-ref ref="CONSOLE"/>
</root>
</configuration>
아래 내용 중 Pengcook
이라는 단어를 위에서 변경해준 어플리케이션 이름으로 변경하고 날짜도 수정한 뒤 ChatGPT
에게 아래 명령어로 loki 접속 URL을 생성해달라 하세요!
loki에 HTTP 접속하여 로그를 확인할건데
주소는 localhost:3100 이고
label은 app=Pengcook
날짜는 2024년 9월 전체로 해서
API URL 알려줘
그럼 아래와 같은 주소를 알려줄 것입니다!
바로 접속해서 사진과 같이 보이면 성공이고 뭔가 짧거나 json 중간에 result: []
로 텅텅 빈 결과가 나온다면 다시 시도해주시길 바랍니다!
여기까지 잘 따라왔다면 SpringApplication
과 4개의 도커 컨테이너 Grafana
Loki
Prometheus
Node Exporter
가 실행되고 있고, 로그와 매트릭 정보 데이터 구성까지 완료한 것입니다.
이제 데이터 소스는 완성이 되었으니 본격적으로 시각화를 할 수 있는 대시보드를 구성해보도록 하겠습니다.
우선 아래 링크로 접속한 뒤 좌측에 Dashboard
이후 Create dashboard
를 클릭해 줍니다.
이후 Import dashboard
를 클릭합니다.
공식 홈페이지에 있는 대시보드 템플릿을 보기 위하여 아래 링크 또는 그라파나에서 파란 텍스트를 클릭해줍니다.
저희는 아래 총 3가지 템플릿을 사용할 것입니다.
위 3가지 템플릿의 URL을 보시면 17175
4701
1860
이라고 되어있습니다. 이 번호를 아래 처럼 기업 후 Load
버튼을 눌러주세요.
이후 아래와 같이 데이터 소스를 선택해주고 Import
버튼을 누르면 대시보드가 생성됩니다!
여기서 필요한 일부분은 수정해 줄 것입니다.
label
수정아래 사진처럼 24시간으로 변경해줍니다.
저장
이후에 모든 수정을 마치고 나면 상단쪽 저장 아이콘 을 눌러 저장하게 될 것이므로 만약 중간에 브라우저가 종료될 경우 여기부터 다시 수정을 해주셔야 합니다!
이제 아래 사진 처럼 패널 수정 화면으로 들어가 줍니다.
이후 기존의 쿼리 내용을 다 지웁니다.
label
을 app
과 서비스명
으로 선택해줍니다!
서비스명은 이전
logback-spring.xml
에서 설정하였습니다.
이대로 보긴 힘들어서 약간의 변환 작업을 해줄 것입니다!
Extract Fields
를 선택해줍니다.
아래와 같이 변환 데이터와 테이블 표시로 변경해줍니다.
로그 형식 시각화 패널인데 시간 데이터가 인식이 되지 않아 표시가 제대로 안되고 있습니다.
Spring
에서 생성해준 timestamp_ms
라는 필드를 그라파나에서 볼 수 있는 시간 값으로 변환을 해줍시다.
이후 필드를 지정해주면 아래와 같이 다시 데이터가 보이게 됩니다.
테이블 뷰
로그 뷰가 아닌 테이블 형식으로 보고 싶다면 아래 처럼 설정하시면 됩니다!
보기 방식은 개인 혹은 팀 취향입니다!
다 되었다면 우측 상단의 Apply
버튼을 눌러 저장해줍시다.
이제 다음 패널의 쿼리만 수정해주면 됩니다.
여기서는 Rate
와 Sum by
쿼리만 남겨주고 나머지는 전부 지워줍니다.
이후 app
을 서비스명
으로 지정해준 뒤 Sumb by
에 label
을 level
로 지정해주고 쿼리를 실행해주면 다음과 같이 미리보기가 될 것이고, 저장까지 눌러줍니다.
다음과 같이 체크를 모두 해주고 적당히 커밋 메세지처럼 작성해준 뒤 저장해줍니다!
남은 2개의 대시보드를 따로 수정할 것 없이 바로 Import
하면 됩니다.
이렇게 모든 대시 보드 구성이 완료되었습니다.
이렇게 모니터링을 구성하는 것 말고도 추가로 할 수 있는 작업이 굉장히 많은 것 같습니다.
여기서 모든 것을 다룰 순 없지만 프로젝트를 하면서 생각했던 것들을 소개하고 마치도록 하겠습니다.
사용자 요청과 응답에 대해 parameter
나 body
를 로깅해놓으면 미래에 문제 상황이 생겼을 때 대처하기가 굉장히 수월해 집니다.
또한 미래에는 지금과 다른 데이터베이스 상태를 가지고 있을 테이니 현재에 상황에 맞도록 잘 기록해두면 더욱 도움이 되겠죠?
이를 하기 위해 보통 아래 선택지가 있습니다.
Interceptor
에 요청 로그 추가logbook
라이브러리 사용Interceptor
에서 요청을 로그하는 것이 학습적인 방면에서는 좋지만 logbook
이라는 강력한 라이브러리를 사용하는 것을 더욱 추천드립니다.
사용자의 요청과 응답에 대해 json
형식 메세지를 만든 뒤 TRACE
레벨로 로깅을 해주니 이를 그라파나에서 적절히 패널을 만들어 사용할 수 있습니다.
그라파나 자체에는 알림 기능을 제공하고 있고 webhook
slack
discord
정말 많은 플랫폼과 연동할 수 있습니다.
팀 프로젝트를 할 당시에는 evaluation
부분에서 어려움을 겪어 실패하였지만 아래와 같은 상황에 알림이 가도록 하면 굉장히 서버 관리자 입장에서 유용할 것이라 생각이 들었습니다.
ERROR
레벨 로그가 일정 횟수 이상 급격히 발생한 경우추후 비동기식 로깅을 사용하거나 여러 사용자의 요청에 대해 로깅을 하는 상황이 올 때 로그가 뒤섞인는 상황이 오기도 합니다.
이러한 상황 속 특정 사용자의 요청을 분별하기 위해 MDC
를 사용할 수 있습니다.
nginx
의 request_id
를 사용하는 등의 방법으로 Spring
측에서 특정 고유한 값을 실행 중인 스레드에 메타 정보를 넣어 로그에 출력할 수 있습니다.
user_id
또는 session_id
가 대표적인 예가 됩니다!
우분투에서 top
명령어 또는 윈도우에서 작업관리자로 하드웨어 매트릭을 보곤 했었는데 서버가 돌아가는 환경의 상태 정보를 시각화하여 볼 수 있다는 것이 참 매력적이었습니다.
서비스를 만들 때 항상 사용자 측면에서 편리한지 생각하는 것 처럼
열심히 많은 모니터링 대시보드도 결국 사용자 기준에서 보면 불편할 수 있을 것 같았습니다.
팀원에게 보기 편한지 직접 사용해보라하여 유저 테스트를 한 뒤 개선해 나가는 것도 큰 도움이 되었습니다.