사내 모니터링 선택부터 아키텍처 설계·구현까지 직접하기
초기 서비스에서 모니터링을 어떻게 시작할 것인가
서비스를 처음 운영하기 시작하면 모니터링에 대한 첫 번째 질문이 생깁니다. 외부 SaaS 솔루션을 도입할 것인가, 아니면 오픈소스 기반으로 직접 구축할 것인가.
이 질문은 “어느 쪽이 더 좋은가”로 답할 수 없습니다. 팀 규모, 운영 역량, 비용 구조, 데이터 보관 요구사항에 따라 정답이 달라집니다.
| 기준 | 외부 SaaS | 자체 구축 |
|---|---|---|
| 초기 도입 속도 | 빠름 (1~2시간) | 느림 (수일~수주) |
| 운영 부담 | 낮음 | 높음 |
| 비용 예측성 | 트래픽 비례 증가 | 인프라 고정 비용 |
| 데이터 보관 기간 | 솔루션 제한에 의존 | 자유 설정 가능 |
| 장애 시 관측 경로 | SaaS 장애 시 통째로 끊김 | 자체 HA 설계 가능 |
| 커스터마이징 | 제한적 | 자유도 높음 |
저희 상황은 이랬습니다. 소규모 팀에서 운영 전담 인력 없이 개발과 운영을 병행하고 있었습니다.
서비스를 빅뱅 방식으로 재오픈하면서, 사내 모니터링 환경을 구축하기 전까지는 CloudWatch를 임시로 사용했습니다.
AWS 위에서 서비스를 운영하고 있었고, CloudWatch는 추가 인프라 없이 바로 쓸 수 있었습니다.
ECS 컨테이너에 awslogs 드라이버를 지정하면 로그가 CloudWatch Logs로 즉시 흘러갔고, 비용은 수집량 비례였지만 초기 트래픽에서는 가장 저렴했습니다.
모니터링 시스템을 본격적으로 설계하기 전에, 서비스가 정상적으로 동작하는지부터 확인하는 게 먼저였기 때문입니다.
이후 사내 모니터링 환경 구축을 본격적으로 고민하기 시작했습니다.
여기에 비용 제약이 동시에 있었습니다. 트래픽 증가 시 모니터링 비용의 상한을 예측할 수 있어야 했습니다. 동일 조건으로 비교했을 때 SaaS 기반은 월 약 $1,257, 자체 구축은 월 약 $303으로 4배 이상 차이가 났습니다.
대안 비교
후보 1: 상용 SaaS 중심
장점은 초기 진입 장벽이 낮다는 점입니다. 구성 속도가 빠르고 운영 포인트가 상대적으로 적습니다.
단점은 장기 비용과 운영 제어입니다. 트래픽과 보관 기간이 늘면 월 비용 변동폭이 커질 수 있습니다. 보관 기간을 원하는 대로 설정하지 못하거나, 세밀한 제어에 추가 비용이 붙는 경우도 있습니다. 또한 SaaS 자체에 장애가 나면 관측 경로가 통째로 끊기는 위험이 있습니다. 모니터링 시스템의 가용성을 외부 서비스에 의존하게 되는 셈입니다.
후보 2: 자체 구축 LGTM Stack
LGTM은 Grafana Labs의 관측 스택 약자로, Loki·Grafana·Tempo·Mimir를 묶어 부르는 이름입니다.
장점은 제어권입니다. 메트릭/로그/트레이스 저장 정책, 보관 기간, 경로 설계를 서비스 특성에 맞출 수 있습니다.
단점은 운영 복잡도 증가입니다. 배포, 스토리지 정책, 알림 규칙, 런북 관리가 필요해집니다.
비용 비교(동일 기준)
| 항목 | 자체 구축 LGTM | SaaS 비교안 |
|---|---|---|
| 월 비용(동일 가정) | 약 $303/월 |
약 $1,257/월 |
비용만으로 결정을 내리지는 않았습니다. 다만 장기 운영에서 4배 이상 차이가 나는 구조는 무시하기 어려웠습니다.
더 중요한 건 스케일링 시 비용 곡선이 다르다는 점입니다. LGTM은 인프라 고정 비용이므로 트래픽이 늘어도 비용이 거의 변하지 않습니다. SaaS 비교안은 vCPU 수에 비례하여 과금되므로, 서비스를 스케일아웃해서 vCPU가 늘어나면 모니터링 비용도 선형으로 증가합니다.
예를 들어 현재 기준 약 4배 차이가, 서비스 규모가 4배로 커지면 10배 이상으로 벌어집니다. 자체 구축 방식은 모니터링 인프라가 고정되어 있어서 애플리케이션을 스케일아웃해도 모니터링 비용이 거의 그대로입니다.
최종 선택: 자체 구축 LGTM Stack + HA + 외부 API 헬스체크 분리
세 가지를 묶어서 설계했습니다.
LGTM은 Grafana Labs가 제공하는 관측 스택의 약자입니다.
- Loki: 로그 저장과 조회
- Grafana: 시각화, 대시보드, 알림 라우팅
- Tempo: 분산 트레이스 저장과 조회
- Mimir: 메트릭 장기 저장 (Prometheus 호환)
이 중 Mimir는 도입하지 않았습니다.
Mimir는 Prometheus의 장기 저장소 역할을 하는 컴포넌트입니다. 수십 대 규모의 Prometheus 인스턴스에서 수집되는 메트릭을 통합하고, 수개월~수년 단위의 장기 보관이 필요한 환경에서 사용합니다.
현재 메트릭 수집 규모에서는 Prometheus 단독으로 충분했습니다. Mimir를 추가하면 Ingester, Compactor, Store Gateway 같은 구성 요소와 설정이 함께 따라오는데, 이 수준의 확장은 지금 단계에서는 불필요한 오버엔지니어링이라고 판단했습니다.
메트릭 보관 기간이나 규모를 넘어서는 시점이 오면 그때 Mimir를 도입해도 늦지 않습니다.
따라서 실제 구성은 Loki + Grafana + Tempo + Prometheus 조합이고, 모든 컴포넌트를 AWS 인프라(EC2, ECS, Lambda) 위에 직접 구축해서 운영하고 있습니다.
- 자체 구축 LGTM Stack (Prometheus 기반)
- HA 구성(memberlist 기반)
- 외부 API 전용 헬스체크 경로
이 조합을 선택한 이유는 “탐지 속도”와 “관측 연속성”을 동시에 충족했기 때문입니다.
아키텍처 구조
1) 애플리케이션 로그 수집 파이프라인
애플리케이션은 stdout에 로그를 남기고, 그 로그가 CloudWatch Logs에 먼저 저장됩니다. 그 이후에 파이프라인을 통해 Loki로 전달되는 구조로 설계했습니다.
전환 전략은 “원본은 그대로 두고, 복사본을 새 인프라로 보내는” 방식입니다.
flowchart LR
A[ECS App stdout] --> B[CloudWatch Logs<br/>원본 보존]
B -->|Subscription Filter| C[Lambda Log Forwarder]
C -->|성공| D[Loki]
C -->|실패| E[SQS DLQ]
E --> F[Reprocessor Lambda]
F --> D
이 설계의 핵심은 세 가지입니다.
- 원본 로그는 CloudWatch Logs에 그대로 쌓입니다. Lambda가 실패하든, Loki가 다운되든, 원본 로그는 유실되지 않습니다.
- Lambda는 Subscription Filter 트리거 방식입니다. 기존 로그 수집 경로를 변경하지 않고, 위에 파이프라인을 얹는 구조입니다.
- 실패 시 DLQ + 재처리로 복구합니다. “로그 전달 실패 = 즉시 유실”이 되지 않도록 설계했습니다.
이 구조 덕분에 기존 CloudWatch 기반 로그 시스템을 중단 없이 운영하면서, 새로운 LGTM 모니터링 인프라로 안전하게 전환할 수 있었습니다.
2) 애플리케이션 → 수집 → 저장 → 조회 흐름
로그/트레이스/메트릭은 아래 흐름으로 흘러갑니다.
flowchart LR
App[ECS Fargate App]
CloudMap[Cloud Map\n(Service Discovery)]
%% Logs
App -- stdout --> CW[CloudWatch Logs]
CW -- Subscription Filter --> LF[Log Forwarder Lambda]
LF -->|resolve ingester.deartail.local| CloudMap
CloudMap --> LokiIng[Loki (target=all, EC2+EBS, WAL)]
LF -->|실패| DLQ[SQS DLQ]
DLQ --> Reproc[Reprocessor Lambda]
Reproc --> LokiIng
%% Traces (OTLP direct)
App -- OTLP gRPC 4317 --> TempoDist[Tempo Distributor (Fargate)]
App -->|resolve lgtm.deartail.local| CloudMap
TempoDist --> TempoIng[Tempo Ingester (EC2+EBS)]
TempoIng --> TempoS3[S3 Tempo]
%% Metrics (Pull)
PromLGTM[Prometheus (ECS)] -- Scrape --> App
%% LGTM Query / Visualize
GrafanaLGTM[Grafana (ECS)] --> PromLGTM
GrafanaLGTM --> TempoQF[Tempo Query Frontend]
TempoQF --> TempoQuerier[Tempo Querier]
TempoQuerier --> TempoS3
GrafanaLGTM --> LokiIng
LokiIng --> LokiS3[S3 Loki]
TempoComp[Tempo Compactor] --> TempoS3
AlertLGTM[Alert Rules] --> AlertManLGTM[AlertManager (ECS)]
AlertManLGTM --> Slack[Slack]
%% Monitoring Stack (EC2)
subgraph MonStack["Monitoring Stack (EC2)"]
MonGrafana[Grafana (EC2)]
MonProm[Prometheus (EC2)]
MonLoki[Loki (EC2)]
MonAlert[AlertManager (EC2)]
MySQLExp[MySQL Exporter]
ValkeyExp[Valkey Exporter]
end
MonProm -- Scrape --> App
MonProm -- Scrape --> MySQLExp
MonProm -- Scrape --> ValkeyExp
MonGrafana --> MonProm
MonGrafana --> MonLoki
MonAlert --> Slack
%% ALB Logs / PI Collector
ALBLogs[ALB Access Logs S3 + Athena] --> MonGrafana
PiCollector[PI Collector Lambda] --> PiS3[PI Data (S3)]
PiS3 --> MonGrafana
%% External Health Check
ExternalHC[External Health Check Lambda] --> CWAlarm[CloudWatch Alarm]
CWAlarm --> Slack
Loki는 prod에서 target=all 단일 바이너리 모드로 운영 중이며, 별도 Query Frontend/Compactor 컨테이너 없이 내부 컴포넌트가 함께 동작합니다.
이 구조의 목적은 한 지점 장애가 전체 관측 불능으로 확산되는 걸 줄이는 데 있습니다.
3) HA 적용
단일 인스턴스 의존 구조에서는 저장소/쿼리 컴포넌트 장애가 곧 관측 단절로 이어집니다. 그래서 Tempo/Loki를 memberlist 기반으로 분산 구성했습니다.
memberlist는 gossip 프로토콜 기반의 클러스터 멤버십 라이브러리입니다. 각 노드가 주기적으로 다른 노드에 상태 정보를 전파하는 방식으로 동작합니다. 노드 하나가 장애를 일으키면 gossip으로 다른 노드들이 이를 감지하고, Distributor가 이미 복제해 둔 데이터 덕분에 다른 노드가 해당 데이터를 계속 서빙합니다. 복제 팩터가 2면 한 노드가 죽어도 다른 노드에서 같은 데이터를 조회할 수 있습니다. 이것이 “장애 중에도 관측이 끊기지 않는” 구조의 핵심 메커니즘입니다.
| 환경 | HA 적용 | 복제 전략 |
|---|---|---|
| Prod | 활성화 | 복제 강화, 관측 연속성 우선 |
| Staging | 활성화(검증 중심) | 최소 복제로 비용 통제 |
핵심은 “평시 성능 최고치”보다 “장애 중 최소 관측 경로 유지”입니다.
4) 외부 API 헬스체크 분리
내부 애플리케이션 헬스만으로는 외부 API 이상을 빠르게 감지하기 어렵습니다.
내부 /health가 정상이어도 외부 결제 API가 실패할 수 있기 때문입니다.
그래서 외부 API 전용 탐지 경로를 분리했습니다.
sequenceDiagram
participant Scheduler as EventBridge Scheduler
participant Lambda as Health Check Lambda
participant External as External API
participant CloudWatch as CloudWatch Alarm
participant Slack as Slack
Scheduler->>Lambda: 주기 실행
Lambda->>External: Synthetic request
External-->>Lambda: 성공/실패 응답
Lambda->>CloudWatch: metric publish
CloudWatch->>Slack: 임계치 초과 시 알림
이 분리를 통해 “고객 문의 이후 인지”에서 “사전 인지”로 탐지 시점을 앞당겼습니다.
서비스에서 외부 API를 사용하는 것도 모니터링하기 위해 주기적으로 헬스체크를 수행하는 Lambda 기반 스케줄러를 만들었습니다.
5) Service Discovery: 컴포넌트 간 통신
LGTM 컴포넌트들은 AWS Cloud Map 기반 서비스 디스커버리로 서로를 찾습니다.
컴포넌트들이 직접 IP를 하드코딩하지 않고, Private DNS 이름으로 통신합니다. ECS 태스크가 재배포되면 IP가 바뀌는데, Cloud Map이 DNS를 자동으로 갱신해주기 때문에 배포 중에도 컴포넌트 간 연결이 끊기지 않습니다.
| DNS 이름 | 연결 대상 | 용도 |
|---|---|---|
lgtm.{namespace} |
Fargate Stateless 태스크 | 트레이스 수신 (OTLP), 쿼리 처리 |
ingester.{namespace} |
EC2 Stateful 태스크 | 로그/트레이스 Ingester |
DNS TTL은 10초로 설정해서 태스크 교체 시 빠르게 새 IP로 전환됩니다. MULTIVALUE 라우팅을 사용하므로 같은 DNS 이름에 여러 태스크 IP가 등록되고, 클라이언트가 그중 하나를 선택합니다.
예를 들어 Lambda Log Forwarder가 Loki에 로그를 전송할 때도 ingester.{namespace}:3100이라는 DNS 이름만 알면 됩니다. Loki가 EC2 2대에 분산되어 있어도 DNS가 자동으로 라우팅합니다.
Write Path 설계: 쓰기 경로에 집중한 이유
LGTM 스택을 도입할 때 가장 먼저 고민한 건 “어떻게 보여줄 것인가”가 아니라 “어떻게 안전하게 쓸 것인가”였습니다.
이유는 간단합니다. 쓰기가 실패하면 관측 데이터가 유실되고, 장애 중에 원인을 추적할 수 없게 됩니다. 읽기 성능은 나중에 튜닝할 수 있지만, 유실된 데이터는 복구할 수 없습니다.
세 컴포넌트의 쓰기 경로
Loki, Tempo, Prometheus는 모두 같은 패턴으로 데이터를 씁니다.
flowchart LR
A[데이터 수신] --> B[Distributor]
B --> C[Ingester]
C --> D[WAL · 로컬 디스크]
C --> E[메모리 버퍼]
E --> F[원격 스토리지 · S3]
| 컴포넌트 | 수신 | Ingester 동작 | 최종 저장소 |
|---|---|---|---|
| Loki | Log Entry → Distributor | 메모리 버퍼 + WAL 동시 기록 | S3 |
| Tempo | Span → Distributor | 메모리 버퍼 + WAL 동시 기록 | S3 |
| Prometheus | Scrape → 수집기 | 메모리 → WAL → TSDB 블록 | 로컬 TSDB |
세 컴포넌트 모두 Ingester가 WAL(Write-Ahead Log)을 로컬 디스크에 먼저 기록합니다.
WAL은 데이터가 메모리에만 있을 때 프로세스가 죽더라도 디스크에서 복구할 수 있게 하는 안전장치입니다. 메모리 버퍼에 있는 데이터가 S3로 flush되기 전에 Ingester가 크래시하면, WAL이 없으면 그 데이터는 사라집니다. WAL이 있으면 프로세스 재시작 후 디스크에서 복구하여 S3로 재전송합니다.
복제 전략과 쓰기의 관계
WAL만으로는 부족합니다. 디스크 자체가 손상되거나 노드가 완전히 유실되면 WAL도 함께 사라집니다.
그래서 복제 팩터를 설정했습니다. 복제 팩터가 2면, Distributor가 같은 데이터를 2개의 Ingester에 동시에 전달합니다.
flowchart LR
D[Distributor] --> IA[Ingester A · WAL + Memory]
D --> IB[Ingester B · WAL + Memory]
IA --> S3[S3 Storage]
IB --> S3
Ingester A가 다운되어도 Ingester B에 같은 데이터가 남아 있어서 쿼리가 가능합니다.
| 장애 시나리오 | WAL만 있을 때 | WAL + 복제 팩터 2 |
|---|---|---|
| Ingester 프로세스 크래시 | WAL에서 복구 가능 | 다른 Ingester에서 즉시 쿼리 가능 |
| 디스크 손상 | 데이터 유실 | 다른 Ingester에서 복구 가능 |
| 노드 완전 유실 | 데이터 유실 | 다른 노드에서 계속 서빙 |
복제 팩터를 높이면 쓰기 부하도 비례해서 증가합니다. 팩터 2면 같은 데이터를 2번 써야 하므로 Ingester 리소스가 2배 필요합니다.
현재 트래픽에서는 Loki ingestion이 한도의 0.01% 수준이므로, 복제 팩터 2에서도 쓰기 부하가 문제되지 않았습니다. Loki ingestion 기준으로 트래픽이 수십 배 증가해도 쓰기 용량에는 충분한 여유가 있어 당분간 복제 비용을 감당할 수 있습니다.
이 복제 전략이 “장애 중에도 관측이 끊기지 않는” 구조의 실제 메커니즘입니다.
컴퓨팅 선택: 왜 Fargate와 ECS EC2를 나눴는가
모든 컴포넌트를 같은 방식으로 배포하지 않았습니다. 각 컴포넌트가 로컬 상태를 유지해야 하는지 여부에 따라 배포 대상을 나눴습니다.
구분 기준: 로컬 디스크에 상태를 유지하는가
| 특성 | 컴포넌트 | 배포 대상 |
|---|---|---|
| Stateless · Scale-out | 애플리케이션 서비스들 | Fargate |
| Stateful · WAL 필수 | Loki Ingester, Tempo Ingester, Prometheus TSDB | ECS EC2 + EBS |
애플리케이션 서비스들은 요청을 받아 처리하고 응답하면 끝입니다. 모든 상태는 데이터베이스에 위임하므로 컨테이너가 죽어도 다른 컨테이너가 즉시 대체할 수 있습니다. 이런 워크로드는 Fargate가 적합합니다. 인프라 관리 없이 태스크 수만 조절하면 됩니다.
반면 Loki/Tempo/Prometheus의 Ingester는 다릅니다. 이들은 WAL과 메모리 버퍼를 로컬에 유지하면서 데이터를 처리합니다. 컨테이너가 재시작되면 WAL에서 데이터를 복구해야 하는데, 이 WAL이 영구 디스크에 있어야만 복구가 가능합니다.
Fargate를 선택할 수 없었던 이유
Fargate에도 태스크 스토리지가 있습니다. 하지만 세 가지 제약이 LGTM 배포에 맞지 않았습니다.
| 제약 | 설명 | 영향 |
|---|---|---|
| Ephemeral Storage 소멸 | Fargate 태스크가 종료되면 로컬 스토리지가 함께 사라짐 | WAL 유실 → 아직 S3에 도달하지 못한 데이터 손실 |
| 태스크 IP 변경 | 재시작 시 새 IP가 할당됨 | memberlist gossip이 기존 노드를 인식하지 못해 클러스터 불안정 |
| EBS 직접 마운트 제한 | Fargate EBS(2024 출시)는 단일 태스크 연결만 지원하고 태스크 종료 시 detach | Ingester 재시작 시 볼륨 재연결 과정에서 WAL 복구 경로가 복잡해짐 |
Fargate EFS는 마운트 가능하지만, EFS는 네트워크 기반 파일시스템이라 WAL 쓰기에 필요한 순차 I/O 지연시간이 로컬 디스크 대비 높습니다. WAL은 데이터 안전성을 위해 디스크에 동기적으로 기록되는 구간이 있으므로, 쓰기 지연이 곧 전체 ingestion 처리량을 제한합니다.
ECS EC2 + EBS를 선택한 이유
| 이유 | 설명 |
|---|---|
| EBS 영구 볼륨 | 컨테이너 재시작, EC2 재부팅에도 데이터 보존. WAL이 항상 디스크에 남아 있어 크래시 복구 가능 |
| 고정 네트워크 주소 | EC2 인스턴스에 고정 ENI를 할당하면 memberlist가 안정적으로 동작. 노드 장애 감지와 복구가 예측 가능해짐 |
| 비용 효율 | 모니터링 스택은 24/7 상시 실행이므로 Reserved Instance 적용 시 Fargate 대비 저렴 |
EBS 볼륨 타입은 gp3을 선택했습니다. gp3은 기본 3,000 IOPS와 125 MB/s 처리량을 제공하며, Loki/Tempo WAL의 순차 쓰기 패턴에 충분합니다. Prometheus TSDB의 랜덤 읽기가 많은 경우에도 IOPS를 별도로 프로비저닝할 수 있어 유연합니다.
이 선택에서 감수한 것
EC2를 직접 사용하면 Fargate에 비해 운영 포인트가 늘어납니다.
- AMI 업데이트와 보안 패치를 직접 관리해야 합니다
- EC2 인스턴스의 용량 계획(디스크, CPU, 메모리)을 주기적으로 점검해야 합니다
- Auto Scaling Group 설정과 Capacity Provider 관리가 추가됩니다
이 부담은 모니터링 스택의 관측 연속성을 확보하기 위해 의도적으로 감수한 것입니다. 애플리케이션은 Fargate로 운영 부담을 줄이고, 상태를 유지해야 하는 모니터링 인프라에만 EC2를 집중시켰습니다.
왜 이렇게 동작해야 했는가
원인 1. 장애 상황에서는 단일 관측 경로가 먼저 끊깁니다
장애가 발생하면 보통 애플리케이션만 흔들리는 게 아닙니다. 로그 파이프라인, 쿼리 저장소, 알림 경로가 같이 영향을 받습니다.
그래서 모니터링 시스템 자체도 부분 장애를 전제로 설계해야 합니다.
원인 2. 내부 지표만 보면 외부 실패를 놓칩니다
5xx, CPU, 메모리만 보면 내부 서비스는 정상처럼 보일 수 있습니다.
하지만 결제/메시징 API가 부분 장애면 실제 고객 경험은 이미 나빠져 있습니다.
외부 API는 별도 synthetic check가 필요합니다.
원인 3. 비용은 순간값이 아니라 성장곡선으로 봐야 합니다
모니터링 비용은 트래픽과 보관 기간이 같이 증가할 때 급격히 커질 수 있습니다. 초기 월비용만 비교하면 장기 의사결정이 틀어지기 쉽습니다.
그래서 이번 결정에서는 “현재 비용”보다 “증가 시 상한 예측 가능성”을 우선했습니다.
운영 관점에서 얻은 것
- 로그/트레이스 상호 추적 경로가 하나로 합쳐져 RCA 시작까지의 시간이 줄었습니다. 기존에는 CloudWatch Logs에서 로그를 찾고 별도 APM에서 트레이스를 연결하는 과정이 필요했지만, Grafana 단일 화면에서 로그 ↔ 트레이스가 정확히 연동되어 로그에서 트레이스로, 트레이스에서 로그로 바로 점프할 수 있게 됐습니다. 이후 메트릭까지 자연스럽게 이어집니다.
- 보관 정책을 서비스 특성에 맞게 조정했습니다. 로그는 Loki에서 14일간 핫 데이터로 유지하고, S3에 365일 아카이브합니다. S3는 30일 후 Glacier Instant, 120일 후 Glacier, 365일 후 삭제로 전환됩니다. 메트릭은 Prometheus에서 15일, 트레이스는 Tempo에서 31일 + S3 365일입니다.
- 외부 API 헬스체크 분리로 탐지 시점을 앞당겼습니다. 기존에는 고객 문의가 들어온 뒤에야 외부 API 이상을 인지했지만, synthetic check 도입 후 사전 탐지가 가능해졌습니다.
- 측정 당시 기준 로그 수집량은 한도 대비 극히 낮은 수준(Loki ingestion 0.01 MB/s, 한도 100 MB/s)으로 트래픽이 수십 배 증가해도 현재 인프라로 대응 가능했습니다.
- CloudWatch Logs 보관 정책도 운영 기준으로 고정해 비용과 장애 분석의 균형을 맞췄습니다.
운영 관점에서 감수한 것
- IaC/배포/알림 규칙 관리 포인트가 늘었습니다.
- 팀 내 구조 이해 비용이 증가했습니다.
- 런북이 없으면 알림 대응 편차가 크게 벌어집니다.
- 장애 훈련을 하지 않으면 HA 설계 의도가 운영에서 사라집니다.
그래서 구조 도입과 동시에 아래를 고정했습니다.
- 알림 라우팅 표준
- 장애 분류 기준(내부/외부/관측 경로)
- 온콜 대응 런북
도입 순서(실무형)
- 먼저 “무엇을 포기할지” 결정합니다.
- 관측 연속성이 우선이면 HA를 먼저 넣고, 초기 단순성이 우선이면 단일 구성으로 시작합니다.
- 내부 헬스와 외부 의존성 헬스를 분리합니다.
- 비용 비교는 월 총액만 보지 말고 보관 기간/데이터 증가율까지 같이 봅니다.
- 마지막에 도구를 선택합니다.
구조를 먼저 정하고 도구를 고르면, 운영 중 방향 전환 비용이 훨씬 작습니다.
실패하기 쉬운 지점
- 대시보드를 많이 만드는 데 집중하고 알림 설계를 늦추는 경우
- 메트릭만 보고 외부 API synthetic check를 생략하는 경우
- HA를 켰지만 장애 연습이 없어 실제 대응이 단일 경로처럼 동작하는 경우
- 비용 비교를 초기 구간만 보고 보관 증가 시나리오를 빼는 경우
정리
- 이번 선택은 “최신 도구 도입”이 목적이 아니었습니다. 장애 중에도 관측이 유지되는 운영 구조를 먼저 고른 결과가 자체 구축 LGTM + HA + 외부 API 헬스체크 분리였습니다.
- 자체 구축 LGTM은 월 $303으로 SaaS 대비 4배 이상 비용 효율적이며, 스케일아웃 시에도 고정 비용 구조를 유지합니다.
- WAL + 복제 팩터 2 구성으로 Ingester 장애 시에도 데이터 유실 없이 관측을 지속할 수 있습니다.
- 외부 API 헬스체크를 분리하여 “고객 문의 이후 인지”에서 “사전 인지”로 탐지 시점을 앞당겼습니다.
- 운영 복잡도는 분명히 올라가지만, 관측 연속성/탐지 속도/비용 예측 가능성을 동시에 만족하려면 구조적 분리가 필요했습니다.
- 메트릭 규모가 Prometheus 단독 한계를 넘어서는 시점에 Mimir 도입을 검토할 예정입니다.
비슷한 모니터링 아키텍처를 고민하고 계시거나 다른 접근 방식이 있다면 댓글로 공유 부탁드립니다!
참고 자료
- Grafana Loki: https://grafana.com/docs/loki/latest/
- Grafana Tempo: https://grafana.com/docs/tempo/latest/
- Prometheus: https://prometheus.io/docs/introduction/overview/
- Grafana Alerting: https://grafana.com/docs/grafana/latest/alerting/
- AWS EventBridge Scheduler: https://docs.aws.amazon.com/scheduler/latest/UserGuide/what-is-scheduler.html
- AWS Lambda: https://docs.aws.amazon.com/lambda/latest/dg/welcome.html
- AWS CloudWatch Alarms: https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/AlarmThatSendsEmail.html
Comments