V2 중앙 집중식 모니터링 시스템 설계 - 100-hours-a-week/KTB4-3rd-CLOUD GitHub Wiki
V2는 외부 API 도입, ECS 기반 분산 서버, SLO 기반 운영으로 요구사항이 커졌다. 이 문서는 V2 요구사항에서 모니터링이 답해야 할 질문을 정리하고, 검토한 구조 후보 중 PLG(Prometheus · Loki · Grafana) + Grafana Alloy 구조를 선택한 이유와 V2 인프라 안의 배치를 정리한다.
이 문서의 PLG는 Prometheus(메트릭) · Loki(로그) · Grafana(시각화·알림)를 뜻한다. 로그 수집기는 Promtail이 아닌 Alloy를 쓴다.
1. V2에서 모니터링은 무엇을 보아야 하는가?
2. 어떤 구조 후보가 있었고, 왜 PLG + Alloy를 선택했는가?
3. 로그는 왜, 어떻게 수집하는가?
4. V2 구조 안에서 모니터링 시스템은 어디에 두는가?
| 요구사항 | 모니터링이 답해야 하는 질문 | 필요한 신호 |
|---|---|---|
| 외부 API 서비스 도입 (Kakao OAuth·지도, 외부 AI·교통 API) | 느려진 원인이 우리 서버인가, 외부 API인가? | 외부 호출 지연·오류 메트릭, 에러 로그 |
| ECS 기반 분산 서버 (rest·websocket·frontend·fastapi 분리, Spot, 오토스케일링) | 바뀌는 태스크 중 어디서 문제가 났나? 태스크가 사라져도 로그가 남는가? | 서비스·태스크별 메트릭, 중앙 수집된 로그 |
| 중앙 집중식 모니터링 (서버·컨테이너·DB·애플리케이션 심층 추적) | 인프라부터 애플리케이션 내부(Hikari, JVM)까지 한 곳에서 보이는가? | 메트릭·로그를 한 화면에서 |
| SLI·SLO 기반 SLA 모니터링 | 지금 오류 예산을 얼마나 쓰고 있나? 언제 사람을 불러야 하나? | 상시 계산되는 가용성·지연 메트릭, burn-rate 알림 |
| 비즈니스 협업 | 인프라 담당이 아니어도 장애 원인과 사용자 영향을 따라갈 수 있는가? | 공유 대시보드, Discord 알림, 에러 ↔ 로그 연결 |
이 질문들을 신호별로 묶으면 다음과 같다.
| 신호 | 필요도 | 담당 |
|---|---|---|
| 메트릭 | 필수. 알림·SLO·확장 신호의 기준 | Prometheus (+ AWS 관리형 지표는 CloudWatch) |
| 로그 | 필수. 에러 내용, 태스크가 사라진 뒤의 사후 분석 | Loki (Alloy로 수집) |
| 트레이스 | 필요하지만 기존 도구로 충분 | Sentry (요청 트랜잭션, 외부 호출 span) |
| 알림 | 필수 | Grafana Alerting → Discord, CloudWatch Alarm(독립 경로) |
트레이스를 별도 백엔드로 두지 않은 이유는 두 가지다. V2에서 확인된 병목(HikariCP 대기, WebSocket 연결 수, 호스트 메모리)은 메트릭으로 먼저 드러나고, 요청 단위 추적은 이미 쓰고 있는 Sentry가 제공한다. Sentry Spring Boot SDK는 들어온 요청마다 트랜잭션을, RestTemplate·RestClient·WebClient로 나가는 요청마다 span을 만든다.
| 방법 1. CloudWatch 단독 | 방법 2. OTel + LGTM | 방법 3. AWS 관리형 APM | 방법 4. PLG + Alloy (선택) | |
|---|---|---|---|---|
| 구성 | JSON 로그 + EMF 지표 + Logs Insights + CloudWatch 대시보드·알람 | OTel 계측·Collector + Prometheus(Mimir) + Loki + Tempo + Grafana | ADOT + X-Ray·Application Signals + AMP + Managed Grafana | Prometheus + Loki + Grafana, 로그 수집은 Alloy, 트레이스는 Sentry |
| 새 인프라 | 없음 | 모니터링 호스트 + Collector | 없음 (관리형) | 모니터링 호스트 1대 + 호스트별 Alloy |
| 앱 변경 | 로그 형식(EMF) 변경 | 모든 서비스에 OTel 계측 추가 | ADOT 계측 추가 | 설정 수준 (JSON 로그, 추적 ID) |
| 비용 성격 | 로그 수집량·조회 스캔량·지표 수에 비례 | 고정 호스트 비용 | 신호량 비례 + Grafana 사용자당 | 고정 호스트 비용 (월 약 $105~125) |
방법 1을 쓰지 않은 이유. 새 인프라 없이 가장 빨리 시작할 수 있지만, 대시보드 커스터마이징과 세밀한 알림 설정이 번거로운 CloudWatch의 한계가 그대로 남는다. Logs Insights는 조회할 때마다 스캔한 GB만큼 과금되고 위젯 로딩에 5~10초가 걸리며, 그대로는 알람을 걸 수 없다. Discord로 알리려면 SNS 뒤에 Lambda도 필요하다.
방법 2를 쓰지 않은 이유. 트레이스와 서비스 그래프까지 직접 갖는 가장 완전한 구성이지만, 트레이스 역할이 Sentry와 겹친다. BE·FE·FastAPI 모두에 계측을 추가해야 하고, Java agent는 라이브러리 버전과 맞아야 해서 rest·websocket(컨테이너 960MiB)에 대한 검증 부담이 크다. 10/15 알림, 10/19 prod 일정 안에서 범위가 과하다.
방법 3을 쓰지 않은 이유. 운영 부담은 가장 적지만 비용이 신호량에 비례해 늘고, 화면과 알림은 방법 1과 같은 CloudWatch 중심이 된다. Grafana로 보려면 Managed Grafana 사용자당 비용(편집자 월 $9, 뷰어 월 $5)이 추가된다.
방법 4를 선택한 이유.
- 요구사항에 필수인 메트릭·로그·알림을 Grafana 한 곳에서 본다.
- 앱은 지금처럼 stdout에 로그를 쓰고 Actuator로 메트릭을 노출하기만 하면 된다. 앱 팀 공수가 설정 수준이다.
- 트레이스는 Sentry를 그대로 쓰고, ALB가 붙이는
X-Amzn-Trace-Id로 Sentry ↔ Loki 로그를 잇는다. - 같은 방향의 사례가 있다. 핀다는 메트릭과 PLG로 먼저 관측 체계를 만들고 트레이스(Tempo)를 나중에 붙였고, 우아한형제들은 ELK에서 Loki로 옮긴 뒤 수집량 1.6배에도 비용을 약 23% 줄였다.
- Prometheus·Loki·Grafana·Alloy는 쿠버네티스에서도 그대로 쓰이므로 V3 이행 시 설정만 옮기면 된다.
나중에 방법 2로 넓힐 조건. Sentry span 쿼터를 넘어 비용이 커질 때, Redis Pub/Sub·WebSocket 구간을 트레이스로 봐야 하는 장애가 반복될 때, 서비스가 늘어 서비스 그래프가 필요할 때 OTel 계측과 Tempo를 추가한다. Alloy가 OpenTelemetry Collector 배포판이므로 수집기는 그대로 쓸 수 있다.
| 구성요소 | 역할 | 위치 |
|---|---|---|
| Grafana Alloy | 컨테이너 로그를 읽어 Loki로 전송 | ECS 컨테이너 인스턴스마다 1개 (ECS DAEMON 서비스) |
| Loki | 로그 저장·검색. 라벨만 인덱싱하고 로그 블록은 S3에 저장 | 모니터링 EC2 |
| Prometheus | 메트릭 수집·저장, SLO 기록 규칙 | 모니터링 EC2 |
| Grafana | 대시보드, 알림(Discord), CloudWatch 데이터소스 | 모니터링 EC2 |
| CloudWatch | ALB·ECS(Container Insights)·RDS·ElastiCache 지표, awslogs 원본 로그, 모니터링과 독립된 알람 | AWS 관리형 |
| Sentry | FE·BE 에러, 요청 트레이스 | SaaS |
flowchart LR
subgraph HOST["ECS 컨테이너 인스턴스"]
TASK["태스크 · awsvpc<br/>rest · websocket · frontend · fastapi"]
DOCKER["Docker 데몬"]
ALLOY["Alloy DAEMON"]
TASK -->|stdout| DOCKER
DOCKER -->|Docker API| ALLOY
end
subgraph MON["모니터링 EC2"]
PROM["Prometheus"]
LOKI["Loki"]
GRAF["Grafana"]
end
CLOUDMAP["Cloud Map DNS"]
CW["CloudWatch<br/>ALB · ECS · RDS · Redis 지표"]
S3B[("S3")]
SENTRY["Sentry"]
DISCORD["Discord"]
ALLOY -->|로그 push| LOKI
LOKI --> S3B
CLOUDMAP -.->|태스크 IP| PROM
PROM -->|:8090 scrape| TASK
TASK -->|에러 · 트레이스| SENTRY
PROM & LOKI & CW --> GRAF
GRAF -->|알림| DISCORD
CW -->|독립 알람 · SNS → Lambda| DISCORD
메트릭은 "무엇이 얼마나 나빠졌는가"를 알려주지만 "왜"는 알려주지 않는다. 예외 메시지, 실패한 요청의 파라미터, 외부 API가 돌려준 에러 본문은 로그에만 있다. V2 구조에서는 로그를 컨테이너 밖 한 곳에 모아야 하는 이유가 더 분명해진다.
| 이유 | 설명 |
|---|---|
| 태스크는 사라진다 | ECS는 배포·오토스케일링·Spot 회수 때마다 태스크를 내리고 새로 띄운다. 컨테이너 안이나 호스트 디스크에만 있는 로그는 태스크·호스트와 함께 사라진다. 장애 원인이 된 태스크일수록 이미 교체된 뒤에 분석하게 된다 |
| 한 요청이 여러 곳에 흩어진다 | rest가 처리하고 Redis를 거쳐 websocket이 전달하는 채팅처럼, 한 사건의 로그가 여러 서비스와 호스트에 나뉜다. 한 곳에서 시간순으로 검색해야 이어서 볼 수 있다 |
| 호스트에 들어가지 않고 봐야 한다 | 운영 접근은 SSM·ECS Exec으로 제한돼 있다. 인프라 담당이 아닌 팀원도 Grafana에서 로그를 검색할 수 있어야 장애 대응이 한 사람에게 몰리지 않는다 |
| 알림과 에러를 원인까지 잇는다 | Discord 알림 → Grafana 패널 → 같은 시간대 로그, Sentry 이슈의 X-Amzn-Trace-Id → 같은 요청의 로그로 이어지는 경로가 있어야 알림이 행동으로 이어진다 |
로그를 Loki로 보내는 방법은 크게 "앱이 직접 보낸다"와 "앱은 stdout에 쓰고 밖에서 가져간다" 두 갈래다.
| 방법 | 동작 | 장점 | 사용하지 않은 이유 |
|---|---|---|---|
A. 백엔드가 직접 전송 (loki-logback-appender 등) |
Logback appender가 앱 안에서 Loki HTTP API로 push | 수집기 없음, 앱이 라벨·필드를 직접 제어 | Grafana 문서상 서드파티 클라이언트라 지원 대상이 아님. 서비스마다(Java·Python·Node) 라이브러리를 따로 넣어야 함. Loki가 느리거나 멈추면 앱 안의 버퍼·재시도가 앱 메모리와 스레드를 씀 |
| B. 앱이 OTel SDK로 OTLP 전송 | 앱이 OTLP로 로그를 내보내고 Loki가 네이티브 OTLP로 수신 | 표준 형식, 나중에 트레이스와 묶기 쉬움 | 방법 2(OTel 계측)를 도입하는 것과 같은 범위. 이번 범위에서 제외 |
| C. Loki Docker 로그 드라이버 | Docker 로그 드라이버 플러그인이 Loki로 직접 전송 | 앱 변경 없음 | ECS on EC2가 지원하는 로그 드라이버는 awslogs, fluentd, gelf, json-file, journald, syslog, splunk, awsfirelens뿐. 다른 드라이버는 ECS 에이전트를 직접 수정해야 하고 AWS가 지원하지 않음 |
| D. FireLens sidecar | 태스크마다 Fluent Bit 로그 라우터 컨테이너를 붙이고 awsfirelens 드라이버로 전달 |
ECS 공식 기능 | 모든 태스크 정의를 바꿔야 하고, 960MiB로 빡빡한 태스크에 컨테이너가 하나씩 늘어남. 수집기 수 = 태스크 수 |
| E. CloudWatch Logs 구독 → Firehose → Loki | 이미 CloudWatch로 가는 로그를 다시 전달 | 앱·태스크 변경 없음 | Firehose 스트림과 HTTPS 수신 엔드포인트를 추가 구성해야 하고, CloudWatch 수집 비용 위에 전달 비용이 더해짐 |
| F. Alloy DAEMON (선택) | 호스트마다 Alloy 1개가 같은 호스트의 컨테이너 로그를 Docker API로 읽어 Loki로 전송 | 앱·태스크 정의 변경 없음, 호스트당 수집기 1개 | — |
방법 A가 가장 직관적이지만 선택하지 않은 핵심 이유는 로그 전송 책임이 앱에 들어오기 때문이다. Twelve-Factor App은 "앱은 출력 스트림의 라우팅이나 저장에 관여하지 않고, 각 프로세스는 이벤트 스트림을 stdout에 쓰며, 실행 환경이 이를 모아 목적지로 보낸다"고 정리한다. 앱이 stdout에만 쓰면 로그 저장소를 바꾸거나 Loki가 멈춰도 앱 코드와 요청 처리에는 영향이 없다.
| 이유 | 근거 |
|---|---|
| Promtail의 공식 후속 | Promtail은 2026-03-02에 EOL이 됐고, 신규 기능 개발은 모두 Alloy에서 진행된다. Grafana Labs가 공식 지원하는 Loki 클라이언트도 Alloy다 |
| 앱을 바꾸지 않는다 | 앱은 지금처럼 stdout에 쓴다. 태스크 정의도 그대로다 |
| 호스트당 1개 | ECS DAEMON 전략은 조건에 맞는 인스턴스마다 정확히 1개를 띄우고, 새 인스턴스에도 자동으로 배치하며, 앱 태스크보다 먼저 뜨고 나중에 내려간다. 수집기 수가 태스크 수가 아닌 호스트 수를 따른다 |
| ENI를 쓰지 않는다 | host 네트워크 모드로 띄운다. V2 호스트(t3)는 awsvpc 태스크를 호스트당 2개까지만 올릴 수 있어 ENI가 귀하다 |
| 라벨을 자동으로 붙인다 | ECS가 컨테이너에 붙이는 Docker 라벨(태스크 정의 family, 컨테이너 이름)을 읽어 service·container 라벨로 바꾼다 |
| 나중에 넓힐 수 있다 | Alloy는 OpenTelemetry Collector 배포판이고 Prometheus 파이프라인을 내장해 메트릭·로그·트레이스·프로파일을 모두 수집한다. 방법 2로 넓힐 때 수집기를 바꾸지 않는다 |
awslogs와의 공존. V2 태스크는 awslogs 드라이버로 CloudWatch에 로그를 보낸다. Docker 문서에 따르면 로그 드라이버가 읽기를 지원하지 않을 때 Docker Engine은 자동으로 dual logging을 켜고, 컨테이너마다 로컬 캐시(최대 20MB × 5개)를 둬 docker logs로 읽을 수 있게 한다. Alloy는 이 Docker 데몬에서 로그를 읽으므로, CloudWatch 원본은 안전망으로 남기고 Loki에도 같은 로그가 들어간다. ECS 최적화 AMI에서 docker logs가 동작하는지 stg에서 먼저 확인하고, 동작하지 않으면 로그 드라이버를 json-file로 바꾼다.
로그 형식과 라벨.
- 로그는 JSON으로 쓴다. Spring Boot 3.4 이상은
logging.structured.format.console=ecs설정으로 JSON 로그를 내고 MDC 값을 필드로 넣는다. - BE 필터가 ALB의
X-Amzn-Trace-Id를 MDC와 Sentry 태그에 함께 넣는다. ALB는 요청을 타깃에 보내기 전에 이 헤더를 추가하거나 갱신한다. - Loki 라벨은
stage·service·container세 개만 쓴다. Loki 공식 모범 사례대로 추적 ID·사용자 ID 같은 일회성 값은 라벨이 아니라 로그 본문에서 필터로 찾는다.
discovery.docker "local" {
host = "unix:///var/run/docker.sock"
}
discovery.relabel "ecs" {
targets = discovery.docker.local.targets
rule {
source_labels = ["__meta_docker_container_name"]
regex = ".*(ecs-agent|internalecspause).*"
action = "drop"
}
rule {
source_labels = ["__meta_docker_container_label_com_amazonaws_ecs_task_definition_family"]
target_label = "service"
}
rule {
source_labels = ["__meta_docker_container_label_com_amazonaws_ecs_container_name"]
target_label = "container"
}
}
loki.source.docker "ecs" {
host = "unix:///var/run/docker.sock"
targets = discovery.relabel.ecs.output
labels = { stage = "prod" }
forward_to = [loki.write.monitor.receiver]
}
loki.write "monitor" {
endpoint {
url = "http://<모니터링 호스트>:3100/loki/api/v1/push"
}
}설정은 stg에서 검증한 뒤 확정한다. 스테이지별로 stage 값만 다르다.
| 항목 | 결정 |
|---|---|
| 수집 대상 | BE는 Actuator /actuator/prometheus(관리 포트 8090), FastAPI는 /metrics
|
| 수집 방식 | Prometheus가 pull. 바뀌는 태스크 IP는 Cloud Map 프라이빗 DNS(monitor.moyeota.internal)에 서비스별 A 레코드로 등록하고 dns_sd_configs로 조회. AWS 공식 "Prometheus metric collection on Amazon ECS" 솔루션과 같은 방식 |
| Service Connect와의 관계 | Service Connect와 같은 이름으로 등록하면 Cloud Map에서 충돌하므로 rest-metrics처럼 다른 이름을 쓴다 |
| SLO 정확도 |
management.metrics.distribution.percentiles-histogram.http.server.requests=true로 histogram bucket을 켜 p95·p99를 계산 |
| AWS 관리형 지표 | ALB·RDS·ElastiCache·Container Insights는 복제하지 않고 Grafana CloudWatch 데이터소스로 조회 |
| SLO 알림 | Sloth로 Google SRE 방식의 다중 윈도 burn-rate 규칙 생성 (99.9%, rolling 28일) |
flowchart TB
TEAM["사용자 · 팀원"] --> R53["Route 53"]
subgraph VPC["VPC 10.20.0.0/16 · 워크로드 AZ-a"]
subgraph PUB["public-a · public-b"]
PALB["Prod ALB"]
NALB["Non-Prod ALB<br/>dev · stg · monitor 호스트 규칙"]
NAT["NAT Gateway"]
end
subgraph APPA["private-app · app-a"]
subgraph PRODHOST["prod ECS 컨테이너 인스턴스 · 호스트마다"]
PRODTASK["rest · websocket · frontend · fastapi<br/>awsvpc · :8090"]
PRODALLOY["Alloy DAEMON<br/>host 네트워크"]
end
end
subgraph DEVA["private-dev · dev-a / stg-a 예정"]
subgraph DEVHOST["dev · stg ECS 컨테이너 인스턴스"]
DEVTASK["dev · stg 태스크"]
DEVALLOY["Alloy DAEMON"]
end
end
subgraph OPSA["private-ops · ops-a 10.20.43.0/24 신설 제안"]
MONH["모니터링 EC2<br/>Prometheus · Loki · Grafana"]
end
subgraph DATA["private-data"]
RDS["RDS MySQL"]
REDIS["ElastiCache Redis"]
end
CLOUDMAP["Cloud Map 프라이빗 DNS"]
S3GW["S3 Gateway Endpoint"]
end
S3B[("S3 · Loki 블록")]
CW["CloudWatch"]
DISCORD["Discord"]
SENTRY["Sentry"]
R53 --> PALB --> PRODTASK
R53 --> NALB --> DEVTASK
NALB -->|monitor.moyeota.com| MONH
PRODTASK -.->|IP 등록| CLOUDMAP
MONH -->|DNS 조회| CLOUDMAP
MONH -->|scrape :8090| PRODTASK
MONH -->|scrape :8090| DEVTASK
PRODALLOY -->|push :3100| MONH
DEVALLOY -->|push :3100| MONH
PRODTASK --> RDS
PRODTASK --> REDIS
MONH --> S3GW --> S3B
MONH -->|조회| CW
MONH --> NAT
PRODTASK --> NAT
NAT --> DISCORD
NAT --> SENTRY
| 항목 | 결정 | 이유 |
|---|---|---|
| 모니터링 호스트 | ECS 클러스터 밖의 전용 EC2 1대 (m7i.large에서 시작), loadtest-infra Terraform 재사용 |
감시 대상과 다른 장애 도메인. prod 클러스터의 용량 부족·배포 장애에 함께 멈추지 않음 |
| 서브넷 | private ops-a 10.20.43.0/24 신설 제안 (미루면 app-a에 SG 분리) |
공인 IP 없음, 기존 예약 대역과 겹치지 않음 |
| 접근 | 팀원은 Non-Prod ALB 호스트 규칙 + Grafana 로그인, 관리자는 SSM | Non-Prod ALB는 이미 특정 그룹만 접속 가능 |
| 저장 | Loki 블록은 S3(Gateway Endpoint 경유), Prometheus TSDB는 EBS | 호스트를 교체해도 로그가 남음 |
| SG | 모니터링 ← 3100(ECS 인스턴스 SG), 3000(Non-Prod ALB SG) / 태스크 ← 8090(모니터링 SG) | 수집 포트를 내부로 제한 |
| 독립 알림 | 모니터링 호스트 상태, ALB 5xx, ECS Running < Desired는 CloudWatch Alarm → SNS → Lambda → Discord | 모니터링 호스트가 죽어도 알림 경로가 남아야 함 |
| 구분 | 내용 |
|---|---|
| 월 비용 | 약 $105 |
| 구축 공수 | 인프라 약 5~6일, BE 0.5일(JSON 로그·추적 ID·histogram 설정), AI 0.5일 이하(/metrics), FE 0일 |
| 런타임 부담 | 호스트마다 Alloy 메모리 예약. frontend·fastapi가 함께 뜨는 general-od 호스트의 여유를 apply 전에 확인 |
| 관리 포인트 | 내용 |
|---|---|
| Docker 소켓 권한 | Alloy에 docker.sock을 마운트하면 호스트 Docker 제어권을 주는 것과 같다. 읽기 전용 마운트도 API 호출은 막지 못하므로 이미지 버전을 고정하고, GET만 허용하는 소켓 프록시를 검토한다 |
| Alloy 수집 공백 | Alloy가 멈춘 동안 dual logging 캐시(컨테이너당 20MB × 5)가 순환하면 그 구간은 Loki에서 빠진다. Alloy 생존·전송 실패 알림을 둔다 |
| 라벨·카디널리티 | Loki 라벨 3개 고정, 메트릭 라벨에 사용자·채팅방 ID 금지 |
| 메트릭 수집 공백 | 태스크가 OOM 직전이면 scrape가 실패한다. up == 0 알림 + ALB 지표로 교차 확인 |
| 이중 로그 수집 비용 | CloudWatch 원본과 Loki에 같은 로그가 들어간다. stg 수집량을 보고 원본 유지 여부를 정한다 |
| 설정 드리프트 | 대시보드·알림·Alloy 설정은 Terraform과 프로비저닝 파일로만 바꾼다 |
| 항목 | 확인 방법 | 시점 |
|---|---|---|
| awslogs 컨테이너 로그를 Docker API로 읽을 수 있는가 | ECS 인스턴스에서 docker logs <컨테이너>
|
stg 1단계 |
| Cloud Map DNS와 Service Connect 공존 |
rest-metrics 이름 등록 후 Prometheus 타깃 up 확인 |
stg 1단계 |
| Alloy 메모리 여유 | 호스트별 등록 메모리와 예약 합계 계산 | apply 전 |
| 알림 경로 | 테스트 알람이 Discord와 이메일에 도착 | 10/15 |
| 로그 ↔ Sentry 연결 | Sentry 이슈의 X-Amzn-Trace-Id로 Loki에서 같은 요청 로그 검색 |
stg QA |
- Grafana Loki — Send log data to Loki (클라이언트 목록)
- Grafana Loki — Promtail EOL 안내
- Grafana Alloy — Introduction
- Grafana Alloy — loki.source.docker
- Grafana Loki — Best practices (라벨)
- The Twelve-Factor App — XI. Logs
- Amazon ECS — LogConfiguration (EC2 지원 로그 드라이버)
- Amazon ECS — FireLens 로그 라우팅
- Amazon ECS — DAEMON 스케줄링 전략
- Amazon ECS — 서비스 디스커버리
- AWS — Prometheus metric collection on Amazon ECS
- Docker — dual logging
- Elastic Load Balancing — ALB 요청 추적 (X-Amzn-Trace-Id)
- Spring — Structured logging in Spring Boot 3.4
- Spring — OpenTelemetry with Spring Boot
- Sentry — Spring Boot 자동 계측
- Google SRE Workbook — Alerting on SLOs
- Sloth — Prometheus SLO 생성기
- cloudonaut — CloudWatch Dashboard Cost Efficiency
- Amazon Managed Grafana 요금
- 우아한형제들 — ELK Stack에서 Loki로 전환한 이유
- 핀다 — LGTM 스택 도입기