kkokkio - 프로젝트/트러블슈팅

Prometheus & Loki 기반 모니터링 시스템 구축기

파란배개 2025. 6. 16. 07:28

TL;DR

AWS EC2 기반의 Spring Boot 애플리케이션에서 메트릭(Prometheus)과 로그(Loki)를 통합하여 모니터링하는 파이프라인을 구축했다. Terraform으로 인프라를 선언형으로 관리하고, Alertmanager + Slack 알림까지 연결해 운영 효율성을 높이는 방법까지 소개한다.


1. 우리의 목표와 현재 상황 이해하기

1.1 현재 로컬 환경

Docker Compose:

  • MySQL과 Redis: 애플리케이션에서 사용하는 데이터베이스와 캐시
  • Prometheus: 서버와 애플리케이션의 메트릭(예: CPU 사용량, 메모리 사용량) 수집
  • Grafana: Prometheus에서 가져온 메트릭과 나중에 추가할 로그 등 시각화
  • MySQL Exporter: MySQL 데이터베이스의 메트릭 노출
  • Node Exporter: EC2 인스턴스의 시스템 메트릭(CPU, 메모리 등) 노출

1.2 현재 배포 구조

(1차 스프린트 기준) 프로젝트의 현재 배포 구조:

  • EC2 인스턴스: Docker 컨테이너로 Spring Boot 백엔드(8080)와 Redis(6379) 실행, 사용자 정의 네트워크로 연결
  • RDS (MySQL): VPC 내부에서 EC2와 통신
  • Doppler: 환경 변수 주입 (DOPPLER_TOKEN 런타임 제공)
  • GitHub Actions: PR 머지 시 태그 생성, Docker 이미지 빌드 및 GHCR에 Push, EC2에서 이미지 Pull 및 컨테이너 실행

1.3 목표

로컬에서는 Docker Compose를 사용해 DB와 모니터링 시스템을 구축하고 Spring 서버는 수동으로 실행하여 개발하고 있었다. 즉, Prometheus와 Grafana를 통해 애플리케이션의 메트릭을 시각화하여 보고 있지만, 로그는 별도로 관리하고 있었다.

심지어, EC2에 배포한 이후에는 로그를 확인하기 위해 매번 SSH로 접속해야 했다. 매우 비효율적이라고 볼 수 있다..

<aside> 💡

메트릭과 로그를 통합하여 한눈에 볼 수 있다면 문제를 더 빠르게 파악할 수 있지 않을까?

</aside>

목표

  • 모니터링 도입: Prometheus로 메트릭, Loki로 로그 수집
  • AWS 최적화: 기존 EC2와 RDS 구조 활용
  • Terraform 관리: 인프라 코드화
  • 알림 시스템: Alertmanager로 문제 감지 시 Slack 알림

2. 도구 선택: 왜 Prometheus와 Loki인가?

2.1 Prometheus

  • 역할: 시계열 메트릭 수집 및 저장
  • 특징: Pull 방식으로 Exporter에서 데이터를 가져옴
  • 사용 사례: Spring Boot의 /actuator/prometheus나 Node Exporter로 메트릭 수집
  • 장점: 애플리케이션 코드 수정 최소화, 오픈소스
  • 추가 설명 - Prometheus는 메트릭 이름과 key-value 라벨로 구성된 시계열 데이터를 수집하고 저장한다. 이 데이터를 쿼리하기 위해 PromQL(Prometheus Query Language)이라는 전용 언어를 사용하며, 실시간 분석이 가능하다.

Prometheus 구성 요소:

  • Prometheus Server: Exporter로부터 데이터를 Pull.
  • Exporter: 각 대상 시스템의 메트릭 데이터를 제공.
  • TSDB: 내부 시계열 데이터 저장소.
  • Alertmanager: 조건 만족 시 알림.
  • Grafana: 시각화 도구.

이 시스템의 장점은 명확한 역할 분리와 독립성이다. Prometheus는 수집에만 집중하고 시각화나 알림은 다른 컴포넌트로 분리된다.

2.2 Loki

  • 역할: 로그 데이터 수집 및 저장
  • 특징: Push 방식, Promtail이 로그를 전송
  • 사용 사례: 애플리케이션 로그를 Grafana에서 시각화
  • 장점
    • 비용 효율성: 로그 전체를 인덱싱하지 않고 메타데이터만 저장해 저장 비용을 줄일 수 있다.
    • Grafana와 통합: Prometheus와 함께 Grafana에서 메트릭과 로그를 한 번에 볼 수 있있다.
  • ADR - 대안으로 AWS CloudWatch도 고민했지만, 소규모 프로젝트에서는 간단할 수 있어도 장기적으로 로그와 메트릭이 많아지면 비용이 커질 수 있기도 하고, 이미 Grafana + Prometheus 시스템이 도입된 상태이므로 Loki를 적용하기로 결정했다.항목 Loki CloudWatch 단순 Logback 파일
    비용 모델 메타데이터만 인덱싱 + S3 저장 → 저렴 저장·쿼리 건수 과금 가장 저렴하지만 검색 불가
    Grafana 통합 기본 플러그인 필요 불가
    쿼리 LogQL (PromQL 유사) CloudWatch Insight grep
    결정 트리거 메트릭·로그를 하나의 Grafana로 보고 싶다 조직 표준·보안 규정 로컬 한정 디버그

2.3 Promtail

  • 역할: 로그 파일을 읽어 Loki로 전송
  • 특징: 로그 파일 감시(Tailing), 라벨링으로 쿼리 효율성 향상
  • 사용 사례: Spring Boot 로그 파일 전송

2.4 Grafana

  • 역할: Prometheus 메트릭과 Loki 로그 시각화
  • 특징: PromQL/LogQL로 쿼리, 대시보드 제공
  • 사용 사례: 통합 모니터링 대시보드

통합 표

도구 데이터 수집 방식 쿼리 언어 주요 역할

Prometheus 메트릭 Pull PromQL 메트릭 수집/저장
Loki 로그 Push LogQL 로그 수집/저장
Promtail 로그 전송 Push - 로그를 Loki로 전달
Grafana 시각화 - PromQL/LogQL 데이터 통합 시각화

3. 시스템 설계

현재 단일 EC2 인스턴스에서 애플리케이션과 Redis가 실행 중이므로, 모니터링 시스템도 동일한 EC2에 통합 설치하고자 한다. 단, 성능과 안정성을 고려해 나중에 모니터링 서버를 분리할 수 있는 구조로 설계한다.

단일 EC2 인스턴스:

  • Docker 컨테이너: Spring Boot, Redis, Node Exporter, Promtail, Prometheus, Loki, Grafana
  • 사용자 정의 네트워크 활용

확장 구조: 모니터링 서버 분리

  • 분리 이유
    • 서비스 문제의 원인을 파악할 수 있어야 한다. 만약 배포 서버가 장애를 일으켰다면 모니터링 시스템이 같은 서버에 있으면 확인 자체가 불가능하다.
    • 부하 분리. 메트릭 수집도 리소스를 사용하므로 모니터링 시스템이 별도로 존재하면 애플리케이션 성능에 영향을 덜 미친다.
  • 두 개의 서버(EC2)
    1. 배포 서버: Spring Boot 애플리케이션 실행, 메트릭/로그 제공
    2. 모니터링 서버: Prometheus, Loki, Grafana로 데이터 수집 및 시각화

4. 모니터링 시스템 구축하기

4.0 Exporter 설정: Spring Boot + Actuator

Spring 애플리케이션에서 Prometheus metric을 노출하기 위해 spring-boot-actuator와 micrometer-registry-prometheus를 적용한다.

  • build.gradle
  • implementation 'org.springframework.boot:spring-boot-starter-actuator' implementation 'io.micrometer:micrometer-registry-prometheus'

Spring Boot가 실행되면 /actuator/prometheus endpoint를 통해 내부 metric을 노출한다.

이 endpoint는 Prometheus 서버가 metric을 pull하는 entry point가 된다.

4.1 로컬 환경 구성

Docker Compose를 활용하여 로컬 환경에서 Prometheus, Loki, Grafana, Promtail 등을 구성했다. Spring Boot 애플리케이션의 로그를 파일로 저장하고, Promtail이 이를 수집하여 Loki로 전송하도록 설정했다.

애플리케이션의 로그는 Spring의 logback을 사용해서 파일을 생성했다.

  • resources/logback-spring.xml 추가 → 환경별로 설정
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <include resource="org/springframework/boot/logging/logback/base.xml"/>
    <springProperty name="LOG_LEVEL_ROOT"
                    source="logging.level.root"
                    defaultValue="INFO"/>

    <springProfile name="local">
        <property name="LOG_DIR" value="logs"/>

        <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
            <encoder>
                <pattern>
                    %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
                </pattern>
            </encoder>
        </appender>

        <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <file>${LOG_DIR}/app.log</file>
            <encoder>
                <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
            </encoder>
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                <fileNamePattern>${LOG_DIR}/app-%d{yyyy-MM-dd}.log</fileNamePattern>
                <maxHistory>7</maxHistory>
            </rollingPolicy>
        </appender>

        <root level="${LOG_LEVEL_ROOT}">
            <appender-ref ref="CONSOLE"/>
            <appender-ref ref="FILE"/>
        </root>
    </springProfile>

    <springProfile name="test">
        <root level="OFF"/>
    </springProfile>

    <springProfile name="dev">
        <property name="LOG_DIR" value="logs"/>
        <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
            <encoder>
                <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
            </encoder>
        </appender>
        <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <file>${LOG_DIR}/app.log</file>
            <encoder>
                <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
            </encoder>
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                <fileNamePattern>${LOG_DIR}/app-%d{yyyy-MM-dd}.log</fileNamePattern>
                <maxHistory>7</maxHistory>
            </rollingPolicy>
        </appender>

        <root level="${LOG_LEVEL_ROOT}">
            <appender-ref ref="CONSOLE"/>
            <appender-ref ref="FILE"/>
        </root>
    </springProfile>

    <springProfile name="prod">
        <property name="LOG_DIR" value="logs"/>
        <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
            <encoder>
                <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
            </encoder>
        </appender>
        <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <file>${LOG_DIR}/general.log</file>
            <encoder>
                <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
            </encoder>
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                <fileNamePattern>${LOG_DIR}/general-%d{yyyy-MM-dd}.loghistory</fileNamePattern>
                <maxHistory>7</maxHistory>
            </rollingPolicy>
        </appender>

        <root level="${LOG_LEVEL_ROOT}">
            <appender-ref ref="CONSOLE"/>
            <appender-ref ref="FILE"/>
        </root>
    </springProfile>

</configuration>
  • docker-compose.yaml 에 모니터링 container 추가
services:
  prometheus:
    image: prom/prometheus:latest
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
    depends_on:
      - mysql-exporter
    networks:
      - monitoring
      
  loki:
    container_name: loki
    image: grafana/loki:latest
    restart: always
    ports:
      - "3100:3100"
    command: -config.file=/etc/loki/local-config.yaml
    volumes:
      - ./loki-config.yaml:/etc/loki/local-config.yaml
      - ./tmp/loki:/loki
    networks:
      - monitoring

  promtail:
    container_name: promtail
    image: grafana/promtail:latest
    restart: always
    volumes:
      - ./logs:/app/logs
      - ./promtail-config.yml:/etc/promtail/config.yml
    command: -config.file=/etc/promtail/config.yml
    depends_on:
      - loki
    networks:
      - monitoring
      
  grafana:
    image: grafana/grafana:latest
    ports:
      - "3000:3000"
    volumes:
      - grafana_data:/var/lib/grafana
    environment:
      - GF_SECURITY_ADMIN_USER=${DB_USERNAME}
      - GF_SECURITY_ADMIN_PASSWORD=${DB_PASSWORD}
    depends_on:
      - prometheus
    networks:
      - monitoring

  mysql-exporter:
    container_name: mysql-exporter
    image: prom/mysqld-exporter:latest
    ports:
      - "9104:9104"
    command:
      - "--mysqld.username=${DB_USERNAME}:${DB_PASSWORD}"
      - "--mysqld.address=db:3306"
    depends_on:
      - db
    networks:
      - monitoring

  node-exporter:
    image: prom/node-exporter:latest
    ports:
      - "9100:9100"
    volumes:
      - /proc:/host/proc:ro
      - /sys:/host/sys:ro
      - /:/rootfs:ro
    command:
      - '--path.procfs=/host/proc'
      - '--path.sysfs=/host/sys'
      - '--collector.filesystem.ignored-mount-points=^/(sys|proc|dev|host|etc)($$|/)'
    networks:
      - monitoring

    
networks:
  monitoring:
    driver: bridge

4.2 AWS Terraform 인프라 설정

기존 EC2와 RDS가 Terraform으로 관리되고 있으므로, 모니터링 관련 설정만 추가한다.

  • terraform 예시
# ec2.tf
resource "aws_instance" "monitor" {
  ami           = "ami-0c55b159cbfafe1f0" # Amazon Linux 2
  instance_type = "t2.micro"
  subnet_id                   = var.subnet_id
  associate_public_ip_address = true
  vpc_security_group_ids = [aws_security_group.monitor_sg.id]
  key_name               = var.key_name
  user_data              = file("scripts/monitoring_setup.sh")
  tags = {
    Name = "monitoring-${terraform.workspace}"
  }
}

# security_group.tf
resource "aws_security_group" "monitor_sg" {
  name   = "monitoring-sg"
  vpc_id = var.vpc_id

  ingress {
    from_port   = 9090
    to_port     = 9090
    protocol    = "tcp"
    cidr_blocks = ["<YOUR_PUBLIC_IP>/32"]  # Grafana/Prometheus 조회용
  }

  # Loki & Alertmanager 내부용 → VPC CIDR
  ingress {
    from_port   = 3100
    to_port     = 3100
    protocol    = "tcp"
    cidr_blocks = ["10.0.0.0/16"]
  }
  ingress {
    from_port   = 9093
    to_port     = 9093
    protocol    = "tcp"
    cidr_blocks = ["10.0.0.0/16"]
  }
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

적용 방법

> terraform init
> terraform apply -var="environment=dev" # 개발 환경
> terraform apply -var="environment=prod" # 운영 환경

4.3 EC2 배포 서버 설정

배포 서버는 Spring Boot와 메트릭/로그 도구를 실행한다.

- promtail-config.yml

server:
  http_listen_port: 9080 # Promtail 의 HTTP 서버가 요청을 수신할 포트
  grpc_listen_port: 0

positions:
  # 동기화 작업을 이루기 위해 promtail이 읽은 마지막 로그 정보를 저장하는 곳
  filename: /tmp/positions.yaml

clients:
  - url: http://loki:3100/loki/api/v1/push

scrape_configs:
  - job_name: spring-logs
    static_configs:
      - targets:
          - localhost
        labels:
          job: spring
          __path__: /logs/*.log

    pipeline_stages:
      - multiline:
          firstline: '^\[ls\] \[' # 로그의 시작을 나타내는 정규식
          separator: '\] \[le\]' # 멀티라인 로그의 구분자
server:
  http_listen_port: 9080
clients:
  - url: http://<Monitoring-EC2-Private-IP>:3100/loki/api/v1/push
scrape_configs:
  - job_name: docker
    docker_sd_configs:
      - host: unix:///var/run/docker.sock
    relabel_configs:
      - source_labels: [__meta_docker_container_name]
        regex: "backend"
        target_label: app
      - source_labels: [__meta_docker_container_log_stream]
        target_label: stream

컨테이너 STDOUT 로그를 바로 Tail, 라벨 app=backend 로 구분.

- prometheus.yml

global:
  scrape_interval: 15s

scrape_configs:
  - job_name: "app"
    metrics_path: "/api/actuator/prometheus"
    static_configs:
      - targets:
          - "${APP_HOST}:${APP_PORT}"
        labels:
          instance: "app"
          
  - job_name: "mysql_exporter"
    metrics_path: "/metrics"
    static_configs:
      - targets: ["mysql-exporter:9104"]
      
  - job_name: "node_exporter"
    scrape_interval: 5s
    static_configs:
      - targets: ["node-exporter:9100"]

- loki-config.yaml

auth_enabled: false # 인증을 사용하지 않도록 설정

server:
  http_listen_port: 3100 # Loki 서버가 수신할 HTTP 포트 설정
  
storage_config:
  filesystem:
    directory: /tmp/loki/chunks # 파일 시스템에 저장할 로그 청크 디렉터리 경로 설정

- app_setup.sh

#!/bin/bash
yum update -y
yum install docker -y
service docker start
usermod -aG docker ec2-user

# Docker 네트워크 생성
docker network create common

# Spring Boot (기존 배포)
docker run -d --name backend --network common -p 8080:8080 \
  -v /var/log/backend:/logs \
  -e DOPPLER_TOKEN=${DOPPLER_TOKEN} \
  ghcr.io/team04-kkokkio/backend:latest

# Redis (기존 배포)
docker run -d --name redis --network common -p 6379:6379 redis:latest

# Node Exporter
docker run -d --name node-exporter --network common -p 9100:9100 \
  prom/node-exporter

# Promtail
docker run -d --name promtail --network common \
  -v /var/log/backend:/logs \
  -v /etc/promtail/promtail-config.yml:/etc/promtail/config.yml \
  grafana/promtail:latest -config.file=/etc/promtail/config.yml

# Prometheus
docker run -d --name prometheus --network common -p 9090:9090 \
  -v /etc/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml \
  prom/prometheus:latest --config.file=/etc/prometheus/prometheus.yml

# Loki
docker run -d --name loki --network common -p 3100:3100 \
  -v /etc/loki/loki-config.yaml:/etc/loki/local-config.yaml \
  grafana/loki:latest -config.file=/etc/loki/local-config.yaml

# Grafana
docker run -d --name grafana --network common -p 3000:3000 \
  -v grafana-data:/var/lib/grafana \
  grafana/grafana:latest

- deploy-prd.yml 에 추가

# ... (기존 단계 생략)
    - name: "4. AWS SSM – 재배포 스크립트 실행"
      uses: aws-actions/aws-ssm-run-command@v1
      with:
        documentName: "AWS-RunShellScript"
        instanceIds: ${{ secrets.APP_EC2_ID }}
        parameters:
          commands: |
            set -e

            # 1) backend 재기동
            docker stop backend || true
            docker rm backend || true
            docker pull ghcr.io/team04-kkokkio/backend:latest
            docker run -d --name backend --network common \
              -p 8080:8080 --restart always \
              --env-file <(doppler secrets download --no-file --format docker) \
              ghcr.io/team04-kkokkio/backend:latest

            # 2) node-exporter (idempotent)
            docker inspect node-exporter >/dev/null 2>&1 || \
              docker run -d --name node-exporter --net=host \
              --restart always prom/node-exporter

            # 3) promtail (로그 → Loki)
            docker inspect promtail >/dev/null 2>&1 || \
              docker run -d --name promtail --restart always \
              -v /var/lib/docker/containers:/containers:ro \
              -v /etc/promtail/promtail-docker-config.yml:/etc/promtail/config.yml \
              grafana/promtail:latest -config.file=/etc/promtail/config.yml

4.4 Grafana 대시보드 설정

Grafana에서 Prometheus와 Loki를 데이터 소스로 추가하고, 다양한 템플릿을 활용하여 대시보드를 구성했다. 이를 통해 메트릭과 로그를 한 화면에서 시각화할 수 있었다. 

  • 💡 템플릿(https://grafana.com/grafana/dashboards/)
    • 14430: Spring Boot Statistics
    • 7362: MySQL Overview
    • 1860: Node Exporter Full
    • 13639: Loki 로그 뷰어
      • Query 예시: {app="backend", stream="stdout"} |= "ERROR" | logfmt | line_format "{{.msg}} [{{.method}} {{.status}}]" </aside>
  1. Grafana 접속: http://<ec2-public-ip>:3000
  2. PrometheusLoki를 데이터 소스로 추가
    1. Prometheus: http://localhost:9090
     

3. 대시보드에서 템플릿(예: 1860 for Node Exporter, 13639 for Loki)을 사용해 시각화

5. 알림 시스템 확장: Alertmanager 추가

Prometheus에 Alertmanager를 연동해 문제를 감지하면 알림을 보내는 기능을 추가할 수 있다. 또는, Loki의 Ruler 기능을 활용하여 로그 기반의 알림을 설정할 수도 있다. 예를 들어, 특정 키워드가 로그에 반복적으로 나타나면 Alertmanager를 통해 알림을 전송하도록 구성할 수 있다.

  • monitoring_setup.sh에 추가
# Alertmanager
docker run -d --name alertmanager --network common -p 9093:9093 \
  -v /etc/alertmanager/alertmanager.yml:/etc/alertmanager/alertmanager.yml \
  prom/alertmanager:latest --config.file=/etc/alertmanager/alertmanager.yml
  • alertmanager.yml
global:
  slack_api_url: "<your-slack-webhook-url>"
route:
  group_wait: 30s
  receiver: 'slack'
receivers:
  - name: 'slack'
    slack_configs:
      - channel: '#monitoring'
        api_url: '${SLACK_WEBHOOK_URL}'
        send_resolved: true
  • prometheus.yml에 알림 규칙 추가
alerting:
  alertmanagers:
    - static_configs:
        - targets: ["localhost:9093"]
rule_files:
  - "/etc/prometheus/rules.yml"
  • rules.yaml
groups:
- name: kkokkio
  rules:
  - alert: HighCPUUsage
    expr: 100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 80
    for: 2m
    labels:
      severity: warning
    annotations:
      summary: "High CPU usage on {{ $labels.instance }}"

6. 결론

처음에는 단순히 로그를 보기 위한 불편함을 해소하고자 시작한 작업이었지만, 결과적으로 메트릭과 로그를 통합하여 모니터링하는 체계를 구축하게 되었다. Terraform을 통한 인프라 관리를 하고, 더 나아가 Alertmanager와 Slack의 연동을 추가하면 운영 효율성까지 크게 향상시킬 수 있는 발판이 되었다.


Q1. Prometheus와 Loki를 같은 인스턴스에 배포하는 것이 괜찮을까? A. 초기 단계에서는 리소스 사용량이 적기 때문에 같은 인스턴스에 배포해도 무방하다. 하지만 트래픽이 증가하거나 고가용성이 요구되는 경우에는 별도의 인스턴스로 분리하는 것이 좋다.

Q2. Promtail에서 라벨링은 왜 중요한가? A. LogQL 쿼리는 라벨 기반으로 작동하기 때문에, 적절한 라벨링은 쿼리 효율성과 유지보수성을 높이는 데 중요하다. 예를 들어, 애플리케이션 이름, 환경, 로그 수준 등을 라벨로 지정하면 특정 로그를 빠르게 필터링할 수 있다.

References


https://velog.io/@onejaejae/NestJS에서-Grafana와-Loki로-모니터링-시스템-구축하기

https://wlsdn3004.tistory.com/48

https://dongker.tistory.com/entry/DevOps-Prometheus와-MySQL-Exporter로-Grafana-MySQL-DB-모니터링-시스템-구축하기

https://creampuffy.tistory.com/213

https://dy-coding.tistory.com/entry/Prometheus-Grafana를-이용한-EC2-모니터링-환경-구축

https://velog.io/@khyup0629/AWS-CloudWatch-요금-정보