ECS(EC2) 환경에서 Grafana Cloud로 모니터링 구축하기 (Prometheus + Loki)

2026. 8. 4. 12:53·Project/YAPP 27기

이 글은 Velog에서 이전한 글입니다. Velog 원문 보기

들어가며

서비스를 운영할 때는 장애 대응이 매우 중요하다.

  • 서버가 살아 있는지
  • 지금 병목이 어디인지
  • 에러가 언제부터 증가했는지

실시간으로 파악할 수 있어야 한다.

이 글에서는 ECS(EC2) 기반 서비스에 Grafana Cloud를 적용해 메트릭(Prometheus) + 로그(Loki) 모니터링을 구성하는 전체 과정을 정리한다.


1. 전체 아키텍처 개요

1-1. 적용 전 (As-Is) vs 적용 후 (To-Be)

기존 CloudWatch 의존도를 낮추고, Grafana Agent(Sidecar)가 데이터를 수집해 Grafana Cloud로 전송하는 구조로 변경한다.

  • 메트릭: Prometheus Remote Write 방식
  • 로그: Log 파일 Tail → Loki Push API 방식
  • 시각화: Grafana Cloud Dashboard (SaaS)

2. 서버 모니터링 시스템 후보 비교 (비용 최소화 기준)

2-1. 프로젝트 전제 조건

항목 내용
서버 규모 소규모 (Start-up 단계)
트래픽 초기 단계, 많지 않음
최우선 목표 비용 최소화 (Cost Efficiency)
확장 전략 필요 시 점진적 유료 플랜 도입

2-2. 모니터링 시스템 비교

데이터 소유권과 과금 모델에 따른 비교

구분 Grafana Cloud (선택) ELK Stack Datadog
유형 SaaS (Free Tier) Self-Hosted (OSS) SaaS (Enterprise)
수집 방식 Push (Agent가 전송) Pull/Push 복합 Agent 전송
인프라 비용 0원 (SaaS) 높음 (최소 3개 노드 권장) 0원 (SaaS)
라이선스 비용 무료 (일정 사용량까지) 무료 (운영 인건비 발생) 높음 (호스트/로그 과금)
장기 리스크 사용량 초과 시 요금 발생 운영 복잡도 증가 (OOM 등) 트래픽 증가 시 요금 폭탄

판단:

  • ELK: 배보다 배꼽이 더 크다. (모니터링을 위해 운영할 서버가 더 많음)
  • Datadog: 편하지만 비싸다.
  • Grafana Cloud: Free Tier로 충분히 커버 가능하며, 인프라 관리 소요가 없다.

3. 최종 선택한 모니터링 아키텍처

각 도구의 역할을 명확히 분리하여 사각지대를 없앴다.

도구 역할 관측 대상 (What) 핵심 질문 (Why)
Grafana 상태 모니터링 CPU, Memory, JVM, TPS, Latency "지금 서버 건강해? 느려지지 않았어?"
CloudWatch 인프라 관측 AWS EC2 Credit, ALB Error, ASG "AWS 땅(인프라) 자체는 튼튼해?"
Sentry 에러 트래킹 Exception Stacktrace "도대체 왜 에러가 난 거야? (원인)"

4. ECS Task에 Grafana Agent 추가 (Terraform)

4-1. Sidecar 패턴과 볼륨 공유

ECS에서는 Sidecar 컨테이너 패턴을 사용한다. 핵심은 로그 파일을 공유할 볼륨(Volume)이다.

4-2. Terraform 구성 (ecs_ec2.tf)

CI/CD 파이프라인과 별개로, 인프라 형상 관리(Terraform) 레벨에서 Sidecar와 설정을 주입한다.
특히 로그 종류(Console vs Transaction)를 분리하여 수집하도록 설정하는 것이 중요하다.

# ECS Task Definition 설정 예시 (Terraform)
resource "aws_ecs_task_definition" "app" {
  container_definitions = jsonencode([
    {
      name      = "app-container"
      image     = "my-app:latest"
      mountPoints = [
        {
          sourceVolume  = "log-volume"
          containerPath = "/var/log/app"
        }
      ]
      # ... (생략)
    },
    {
      name      = "grafana-agent"
      image     = "grafana/agent:latest"
      # ... (Secret 주입 생략)
      environment = [
        {
          name  = "AGENT_CONFIG_CONTENT"
          value = <<EOF
metrics:
  global:
    scrape_interval: 15s
  configs:
    - name: default
      scrape_configs:
        - job_name: app
          metrics_path: /actuator/prometheus
          static_configs:
            - targets: ['localhost:8080']

logs:
  configs:
    - name: default
      clients:
        - url: $${GRAFANA_CLOUD_LOKI_URL} # Terraform 변수 처리
          # ... (Auth 설정 생략)
      positions:
        filename: /tmp/positions.yaml
      scrape_configs:
        # 1. 일반 시스템 로그 (Console) -> log_type: console 라벨 부여
        - job_name: app-console
          static_configs:
            - targets: ['localhost']
              labels:
                job: app
                log_type: console
                __path__: /var/log/app/console/*.log

        # 2. 트랜잭션/API 로그 (Transaction) -> log_type: transaction 라벨 부여
        - job_name: app-transaction
          static_configs:
            - targets: ['localhost']
              labels:
                job: app
                log_type: transaction
                __path__: /var/log/app/api/*.log
EOF
        }
      ]
      mountPoints = [
        {
          sourceVolume  = "log-volume"
          containerPath = "/var/log/app"
          readOnly      = true
        }
      ]
    }
  ])
  # ...
}

5. 애플리케이션 로그 분리 전략 (Logback)

Loki에서 효율적으로 쿼리하고 비용을 아끼기 위해, 일반 로그와 트랜잭션(API) 로그를 파일 레벨에서 분리한다.

5-1. Logback 흐름도

5-2. logback.xml 설정 (핵심)

additivity="false"를 사용하여 로그가 중복으로 쌓이는 것을 방지하고, Root Logger와 API Logger의 저장 경로를 명확히 나눈다.

<configuration>
    <include resource="logback-appender.xml"/>

    <root level="INFO">
        <appender-ref ref="CLOUD_WATCH_CONSOLE"/>
        <appender-ref ref="CONSOLE_FILE"/> </root>

    <logger name="com.nomoney.support.logging.ApiLoggingFilter" level="INFO" additivity="false">
        <appender-ref ref="CLOUD_WATCH_TRANSACTION"/>
        <appender-ref ref="API_FILE"/>     </logger>
</configuration>

6. Grafana 대시보드 구성 (템플릿 ID 활용)

Grafana의 강력한 점은 커뮤니티 대시보드를 ID 입력만으로 즉시 사용할 수 있다는 것이다.
우리 환경(Spring Boot + ECS)에 최적화된 대시보드를 구성했다.

6-1. 추천 대시보드 목록

대시보드 이름 ID 용도 주요 지표
Spring Boot 3.x Statistics 19004 메인 관제 TPS(Throughput), Latency, Error Rate
JVM (Micrometer) 4701 리소스 분석 Heap Memory, GC Count, CPU Usage
Loki Logs 13639 로그 검색 Error Log 검색, Stacktrace 확인
AWS EC2 617 인프라 상태 CPU Utilization, Network In/Out, Disk I/O
AWS ECS Cluster 551 클러스터 관리 CPU/Memory Reservation, Running Tasks
AWS Application Load Balancer 650 네트워크 입구 Target Response Time, HTTP 4xx/5xx, Request Count

6-2. 대시보드 활용 예시

1. Spring Boot Overview (19004)
"지금 트래픽이 얼마나 들어오지?"를 확인할 때 가장 먼저 보는 화면이다.

2. JVM Monitoring (4701)
메모리 누수(Memory Leak)나 GC로 인한 성능 저하가 의심될 때 확인한다.


7. 고급: 깔끔한 로그 뷰를 위한 LogQL 쿼리

JSON 로그가 그대로 보이면 가독성이 떨어진다. 또한 불필요한 Health Check 로그(/ping) 등을 제거하고 싶을 때 아래 쿼리를 사용한다.

Loki 쿼리 예시:

{job="app", log_type=~"$log_type"}    # 1. 변수로 로그 타입 선택 (console/transaction)
  |= "$search"                         # 2. 검색어 필터링
  | json                               # 3. JSON 파싱 (필드 추출)
  | url !~ "/ping|/actuator/prometheus" # 4. 불필요한 URL 제거
  | __error__=""                       # 5. 파싱 에러(배너 등) 무시
  | line_format "{{.timestamp}} [{{.level}}] {{.msg}}" # 6. 깔끔한 텍스트로 재조립

이 쿼리를 적용하면 JSON의 복잡한 괄호들이 사라지고, 우리가 익숙한 [INFO] 메시지 형태의 깔끔한 로그만 볼 수 있다.


8. 결론

ECS 환경에서도 Grafana Agent (Sidecar) 패턴을 이용하면, 인프라 변경을 최소화하면서 SaaS 모니터링의 장점을 모두 누릴 수 있다.

구축 성과 요약

  1. 비용 0원: Grafana Cloud Free Tier 범위 내에서 해결.
  2. 완전한 가시성: TPS(비즈니스) ↔ JVM(애플리케이션) ↔ EC2 Credit(인프라) ↔ Logs(디버깅) 로 이어지는 풀스택 관측성 확보.
  3. 코드 레벨 분리: Terraform과 Logback 설정을 통해 로그 수집 파이프라인을 정교하게 제어.

이제 "서버 죽었나요?"라는 질문에 당황하지 않고, 대시보드 링크를 던져줄 수 있게 되었다.

'Project > YAPP 27기' 카테고리의 다른 글

[트러블슈팅] JPA 다중 Fetch Join과 List 사용 시 발생하는 데이터 중복 (Cartesian Product)  (0) 2026.08.04
Terraform 상태 관리를 로컬에서 S3 + S3 Native State Locking으로 이전한 이유와 대안 비교  (0) 2026.08.03
Terraform으로 구축한 초기 서비스 인프라: 비용 최적화를 위한 단일 AZ 아키텍처 설계 과정  (0) 2026.08.03
'Project/YAPP 27기' 카테고리의 다른 글
  • [트러블슈팅] JPA 다중 Fetch Join과 List 사용 시 발생하는 데이터 중복 (Cartesian Product)
  • Terraform 상태 관리를 로컬에서 S3 + S3 Native State Locking으로 이전한 이유와 대안 비교
  • Terraform으로 구축한 초기 서비스 인프라: 비용 최적화를 위한 단일 AZ 아키텍처 설계 과정
pp8817
pp8817
공부한 내용, 개발 관련 지식, 트러블 슈팅 등을 기록합니다. 이전 블로그: https://velog.io/@pp8817/posts
  • pp8817
    끄적이는 개발 log
    pp8817
  • 전체
    오늘
    어제
    • 분류 전체보기 (273)
      • Project (71)
        • 척척학사 (29)
        • Book (23)
        • Saynow (3)
        • YAPP 27기 (4)
        • 나의 작은 프로젝트 (10)
        • Landit (2)
      • Backend (88)
        • Spring (13)
        • Spring MVC (13)
        • Spring Security (4)
        • JPA (26)
        • Database (19)
        • HTTP·Web (13)
        • Architecture (0)
      • Language·CS (59)
        • Java·Kotlin (5)
        • CS Interview (17)
        • Backend Interview (5)
        • Concepts (32)
      • Algorithm (25)
        • Algorithm (24)
        • 소마 알고리즘 스터디 (1)
      • Infra (8)
      • Troubleshooting (11)
      • Retrospective (6)
      • Etc (5)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    jpa
    Project
    Spring
    척척학사
    Python
    HTTP WEB 기본 지식
    나의 작은 프로젝트
    트러블슈팅
    interview
    Spring MVC
    object
    CS Interview
    http
    개념 정리!
    게시판
    BOJ
    java
    Book
    OS
    Algorithm
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.6
pp8817
ECS(EC2) 환경에서 Grafana Cloud로 모니터링 구축하기 (Prometheus + Loki)
상단으로

티스토리툴바