이 글은 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 모니터링의 장점을 모두 누릴 수 있다.
구축 성과 요약
- 비용 0원: Grafana Cloud Free Tier 범위 내에서 해결.
- 완전한 가시성:
TPS(비즈니스)↔JVM(애플리케이션)↔EC2 Credit(인프라)↔Logs(디버깅)로 이어지는 풀스택 관측성 확보. - 코드 레벨 분리: Terraform과 Logback 설정을 통해 로그 수집 파이프라인을 정교하게 제어.
이제 "서버 죽었나요?"라는 질문에 당황하지 않고, 대시보드 링크를 던져줄 수 있게 되었다.