[우테코 6기 레벨3] Grafana, Loki, Prometheus - 로그와 메트릭 모니터링

새양·2024년 8월 26일
1

우테코 6기 일기장

목록 보기
16/16
post-thumbnail

목표

  • Spring Boot 의 API 요청 및 응답에 대한 정보와 로그를 확인

  • Spring BootJVM 정보를 확인

  • Spring Boot 가 동작하는 컴퓨터의 하드웨어 정보를 확인

로그와 매트릭

로그를 왜 남길까?

  • 특정 시점에 어떠한 상황을 기록
  • 이러한 기록을 토대로 미래에 생길 문제를 해결

매트릭은 왜 수집 할까?

  • 어떠한 정보가 지속적으로 어떤 값을 나타내고 있는지 기록
  • 이러한 기록을 바탕으로 하드웨어 자원을 변경하거나 프로그램에 변화를 고려

모니터링

선택

팀 회의를 통해 아래 3가지 중 1, 2번의 선택지가 있었습니다.

  1. Grafana Loki Prometheus
  2. Elastic Search Logstash Kibana
  3. CloudWatch

EC2 를 사용하는 특성 상 CloudWatch 도 사용 가능 했지만 당시 잘 몰랐기에 선택지에 없었습니다.

ELKElastic Search 는 인덱싱 방식이 난 단어를 모두 토큰화해서 하지만 Lokilabel 별로 인덱싱 해서 저장한다고 되어있었습니다.

저장 코스트가 다른 부분이 있었는데 소규모 프로젝트여서 큰문제는 없었습니다.

우아한 형제들 기술 블로그 중 ELK 에서 Loki 로의 전환하는 글도 있었고, 1번과 2번 사이에서 저희는 Spring Boot 의 기술 블로그 중 하나 BaeldungLoki 를 통해 로깅하는 방법을 발견하였습니다.

조금 더 빠르게 적용하여 해볼 수 있을 것이라 생각되어 바로 시도해보게 되었습니다.

역할

저희가 사용할 프로그램은 세부적으로 총 4가지 입니다.

  • 시각화 Grafana 로키와 프로메테우스에 정보를 API 요청하여 정보를 가져와 시각화 함
  • 로그 Loki 스프링이 로키 API를 호출하여 전달해준 로그를 저장해 둠
  • 매트릭 Prometheus 스프링과 노드 익스포터 API 요청 하여 정보를 일정 주기마다 얻어와서 저장해 둠
  • 하드웨어 매트릭 Node Exporter 하드웨어 정보를 주시하고 있다가 API 요청 받으면 정보를 응답해줌

세부적인 역할은 아래와 같습니다.

  • Grafana 는 단순 데이터를 시각화 및 알림 기능을 제공하지 단독으로는 데이터를 가지고 있지 않아 할 수 있는게 없습니다!
  • LokiPrometheus 는 각각 데이터를 저장해두고 있지만 Node Exporter 는 하드웨어 정보를 주시만 하고 있지 매트릭 정보이므로 정보를 저장해두지 않습니다!
  • Loki 는 로그 담당이고 Prometheus 는 매트릭 정보 담당 입니다.

요청 방향

누가 어디로 API 요청하는지 방향을 알고 있으면 흐름을 이해하기 더욱 수월합니다.

  1. Spring Boot -> Loki
  2. Prometheus -> Spring Boot Node Exporter
  3. Grafana -> Loki Prometheus

이 정도 알고 나면 직접 실행하고 적용해보며 이해하면 훨씬 빠르게 습득하실 수 있습니다.

설치

모니터링 도구는 역할이 서로 다른 여러 프로그램을 사용하게 되므로 구성 및 관리에 용이하도록 도커 컴포즈를 사용할 예정입니다.

로컬 실행

해당 Baeldung 에 있는 게시글을 통해 필요한 부분만 적용하여 빠르게 실행해 보았고, 이를 바탕으로 Grafana Loki Prometheus Node Exporter 총 4개의 컨테이너를 도커 컴포즈로 묶어 실행할 수 있는 로컬용 docker-compose-local.yamlresources 폴더에 두고 실행하였습니다.

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

이렇게 우선 로컬로 모든 컨테이너를 실행시켜 하나씩 웹 접속을 하여 어떻게 동작하는지 테스트 해보도록 하겠습니다.

그라파나 테스트

http://localhost:3000

도커 컴포즈에 로그인을 스킵하도록 설정해두어 바로 홈화면이 보이면 성공입니다.

로키 테스트

http://localhost:3100/ready

로키는 API 서비스만 제공하기 때문에 위 링크 접속 시 단순히 레디라고 보이면 성공입니다.

프로메테우스 테스트

http://localhost:9090

프로메테우스 화면이 보이는지만 확인해주세요.

노드 익스포터 테스트

http://localhost:9100

아래 화면이 보이시면 성공입니다!

스프링 부트 구성

기본적인 실행 및 확인이 끝났다면 그라파나에서 여러 데이터 소스를 통해 대시보드를 구성하도록 하겠습니다.

그라파나와 로키에 대해서는 도커 컴포즈의 entrypoint 로 구성 파일을 이미 작성해놓은 상태입니다.

이제 아래 2가지를 해줄 것입니다.

  • Actuator 를 사용해 Endpoint /actuator/prmetheus API 서버를 엽니다.
    • 프로메테우스가 요청하여 JVM 등의 스프링 어플리케이션 정보를 가져갈 수 있도록 해줘야 합니다.
  • Logback 설정 파일을 구성하여 로그가 생길 때 마다 Loki 의 API 를 호출하여 로그를 전송합니다.

Actuator 설치

build.gradle 에 아래 의존성을 추가합니다.

implementation 'org.springframework.boot:spring-boot-starter-actuator'

Actuator API 테스트

코끼리를 새로고침 하였다면 Actuator 가 열렸는지 확인을 해봅시다!

SpringApplication 을 실행해세요.

이후 http://localhost:8080/actuator 에 접속 후 아래와 같이 보인다면 성공입니다!

Prometheus 라이브러리 설치

build.gradle 에 아래 의존성을 추가합니다.

implementation 'io.micrometer:micrometer-registry-prometheus'

Prometheus 구성 및 API 테스트

application.yaml 에 아래 내용을 추가합니다.

management:
  endpoints:
    web:
      exposure:
        include: prometheus

이후 SpringApplication 을 재시작 한 뒤 아래 링크로 접속했을 때 프로메테우스에 필요한 데이터 형식이 보일 경우 성공입니다.

http://localhost:8080/actuator/prometheus

이로써 스프링 상태 정보를 매트릭으로 제공하는 구성을 완료하였습니다.

Logback 설정

resources 폴더에 logback-spring.xml 을 만들어 아래 내용을 작성해 줍니다.

Logback 은 스프링 자체에 포함된 도구이기 떄문에 따로 설치해주지 않아도 됩니다!

중간 즈음 app=Pengcook 되어 있는 부분에 Pengcook 을 본인의 어플리케이션 이름으로 변경해주세요!! (공백이 없는 것이 테스트에 용이합니다.)

  • Console 에는 기존 스프링 부트 기본 logback 패턴을 사용하여 원래 보였던 것과 같이 출력되게 합니다.
  • Loki 에는 특정 URL 주소로 API 요청을 보낼 거고, 특정 label, 메세지는 loki4jlogback.JsonLayout 을 사용하는 것입니다.
    https://loki4j.github.io/loki-logback-appender/docs/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>

실행하여 로그 생성

  • 설정 파일 작성이 끝난 후 스프링을 실행하였을 때 아래와 같이 콘솔에도 그대로 잘 출력되면 성공입니다!

Loki 로그 확인

아래 내용 중 Pengcook 이라는 단어를 위에서 변경해준 어플리케이션 이름으로 변경하고 날짜도 수정한 뒤 ChatGPT 에게 아래 명령어로 loki 접속 URL을 생성해달라 하세요!

loki에 HTTP 접속하여 로그를 확인할건데
주소는 localhost:3100 이고
label은 app=Pengcook
날짜는 2024년 9월 전체로 해서
API URL 알려줘

그럼 아래와 같은 주소를 알려줄 것입니다!
바로 접속해서 사진과 같이 보이면 성공이고 뭔가 짧거나 json 중간에 result: [] 로 텅텅 빈 결과가 나온다면 다시 시도해주시길 바랍니다!

http://localhost:3100/loki/api/v1/query_range?query={app=%22Pengcook%22}&start=1725062400000000000&end=1727654399000000000

그라파나 대시보드

여기까지 잘 따라왔다면 SpringApplication 과 4개의 도커 컨테이너 Grafana Loki Prometheus Node Exporter 가 실행되고 있고, 로그와 매트릭 정보 데이터 구성까지 완료한 것입니다.

이제 데이터 소스는 완성이 되었으니 본격적으로 시각화를 할 수 있는 대시보드를 구성해보도록 하겠습니다.

검색

우선 아래 링크로 접속한 뒤 좌측에 Dashboard 이후 Create dashboard 를 클릭해 줍니다.

http://localhost:3000

이후 Import dashboard 를 클릭합니다.

공식 홈페이지에 있는 대시보드 템플릿을 보기 위하여 아래 링크 또는 그라파나에서 파란 텍스트를 클릭해줍니다.

https://grafana.com/grafana/dashboards/

저희는 아래 총 3가지 템플릿을 사용할 것입니다.

생성

위 3가지 템플릿의 URL을 보시면 17175 4701 1860 이라고 되어있습니다. 이 번호를 아래 처럼 기업 후 Load 버튼을 눌러주세요.

이후 아래와 같이 데이터 소스를 선택해주고 Import 버튼을 누르면 대시보드가 생성됩니다!

여기서 필요한 일부분은 수정해 줄 것입니다.

  • 대시보드 연결 데이터를 최근 5분이 아닌 최근 1일로 수정
  • 아래쪽 로그 관련 패널 2개 로키 label 수정

스프링 로그 대시보드 수정

아래 사진처럼 24시간으로 변경해줍니다.

저장
이후에 모든 수정을 마치고 나면 상단쪽 저장 아이콘 을 눌러 저장하게 될 것이므로 만약 중간에 브라우저가 종료될 경우 여기부터 다시 수정을 해주셔야 합니다!

이제 아래 사진 처럼 패널 수정 화면으로 들어가 줍니다.

이후 기존의 쿼리 내용을 다 지웁니다.

labelapp서비스명 으로 선택해줍니다!

서비스명은 이전 logback-spring.xml 에서 설정하였습니다.

이대로 보긴 힘들어서 약간의 변환 작업을 해줄 것입니다!

Extract Fields 를 선택해줍니다.

아래와 같이 변환 데이터와 테이블 표시로 변경해줍니다.

로그 형식 시각화 패널인데 시간 데이터가 인식이 되지 않아 표시가 제대로 안되고 있습니다.

Spring 에서 생성해준 timestamp_ms 라는 필드를 그라파나에서 볼 수 있는 시간 값으로 변환을 해줍시다.

이후 필드를 지정해주면 아래와 같이 다시 데이터가 보이게 됩니다.

테이블 뷰
로그 뷰가 아닌 테이블 형식으로 보고 싶다면 아래 처럼 설정하시면 됩니다!
보기 방식은 개인 혹은 팀 취향입니다!

다 되었다면 우측 상단의 Apply 버튼을 눌러 저장해줍시다.

이제 다음 패널의 쿼리만 수정해주면 됩니다.

여기서는 RateSum by 쿼리만 남겨주고 나머지는 전부 지워줍니다.

이후 app서비스명 으로 지정해준 뒤 Sumb bylabellevel로 지정해주고 쿼리를 실행해주면 다음과 같이 미리보기가 될 것이고, 저장까지 눌러줍니다.

스프링 대시보드 저장

다음과 같이 체크를 모두 해주고 적당히 커밋 메세지처럼 작성해준 뒤 저장해줍니다!

나머지 대시보드

남은 2개의 대시보드를 따로 수정할 것 없이 바로 Import 하면 됩니다.

이렇게 모든 대시 보드 구성이 완료되었습니다.

추가 작업

이렇게 모니터링을 구성하는 것 말고도 추가로 할 수 있는 작업이 굉장히 많은 것 같습니다.
여기서 모든 것을 다룰 순 없지만 프로젝트를 하면서 생각했던 것들을 소개하고 마치도록 하겠습니다.

사용자 요청 응답 로그

사용자 요청과 응답에 대해 parameterbody 를 로깅해놓으면 미래에 문제 상황이 생겼을 때 대처하기가 굉장히 수월해 집니다.

또한 미래에는 지금과 다른 데이터베이스 상태를 가지고 있을 테이니 현재에 상황에 맞도록 잘 기록해두면 더욱 도움이 되겠죠?

이를 하기 위해 보통 아래 선택지가 있습니다.

Interceptor 에서 요청을 로그하는 것이 학습적인 방면에서는 좋지만 logbook 이라는 강력한 라이브러리를 사용하는 것을 더욱 추천드립니다.
사용자의 요청과 응답에 대해 json 형식 메세지를 만든 뒤 TRACE 레벨로 로깅을 해주니 이를 그라파나에서 적절히 패널을 만들어 사용할 수 있습니다.

알림 기능

그라파나 자체에는 알림 기능을 제공하고 있고 webhook slack discord 정말 많은 플랫폼과 연동할 수 있습니다.

팀 프로젝트를 할 당시에는 evaluation 부분에서 어려움을 겪어 실패하였지만 아래와 같은 상황에 알림이 가도록 하면 굉장히 서버 관리자 입장에서 유용할 것이라 생각이 들었습니다.

  • ERROR 레벨 로그가 일정 횟수 이상 급격히 발생한 경우
  • 하드웨어 사용률이 일정 퍼센트 이상 갑자기 올라갈 경우 (CPU, RAM, Network, Disk 등)

MDC

추후 비동기식 로깅을 사용하거나 여러 사용자의 요청에 대해 로깅을 하는 상황이 올 때 로그가 뒤섞인는 상황이 오기도 합니다.

이러한 상황 속 특정 사용자의 요청을 분별하기 위해 MDC 를 사용할 수 있습니다.

nginxrequest_id 를 사용하는 등의 방법으로 Spring 측에서 특정 고유한 값을 실행 중인 스레드에 메타 정보를 넣어 로그에 출력할 수 있습니다.

user_id 또는 session_id 가 대표적인 예가 됩니다!

마치며

우분투에서 top 명령어 또는 윈도우에서 작업관리자로 하드웨어 매트릭을 보곤 했었는데 서버가 돌아가는 환경의 상태 정보를 시각화하여 볼 수 있다는 것이 참 매력적이었습니다.

서비스를 만들 때 항상 사용자 측면에서 편리한지 생각하는 것 처럼
열심히 많은 모니터링 대시보드도 결국 사용자 기준에서 보면 불편할 수 있을 것 같았습니다.
팀원에게 보기 편한지 직접 사용해보라하여 유저 테스트를 한 뒤 개선해 나가는 것도 큰 도움이 되었습니다.

profile
안녕, 세상!

0개의 댓글