서론: 왜 OpenTelemetry + Push 방식이 필요한가?
클라우드 인프라가 커질수록 모니터링 비용과 복잡도는 함께 증가합니다. 특히 국내 기업들은 서드파티 라이선스 비용과 벤더 종속(Vendor Lock-in) 문제로 고민이 많죠. AWS 블로그에 소개된 이 아키텍처는 CloudWatch Metric Streams와 OpenTelemetry Collector를 Lambda로 연결해, VPC 내부에서 민감한 메트릭 데이터를 안전하게 수집하는 방법을 보여줍니다.
핵심 질문: "Prometheus Pull 방식으로 API를 계속 긁어오다가 Throttling 나고 비용 폭탄 맞기 전에, Push 방식으로 갈아탈 수 없을까?"
이 질문에 대한 해답이 바로 이 글입니다. AI가 생성한 코드, 그대로 배포하면 생기는 재앙과 Vercel의 해법에서도 강조했듯, 운영 환경에서의 데이터 파이프라인 안정성은 절대 타협할 수 없는 요소입니다.

본론 1: Pull vs Push — 왜 Push가 정답일까?
많은 팀이 Prometheus의 Pull 모델에 익숙합니다. 하지만 규모가 커지면 다음과 같은 문제가 발생합니다.
| 항목 | Pull (Prometheus) | Push (OpenTelemetry + Metric Streams) |
|---|---|---|
| API 호출 | 주기적 Polling → Throttling 위험 | Event-driven → 필요할 때만 전송 |
| 지연 시간 | 분 단위 (Polling 주기) | Sub-minute (실시간에 가까움) |
| 비용 | API 호출 비용 + 라이선스 비용 | 라이선스 무료 (Apache 2.0) |
| 확장성 | Scrape 대상 증가 시 병목 | Collector 수평 확장 가능 |
| 보안 | Endpoint 노출 필요 | VPC 내부에서 안전하게 수신 |
실제 아키텍처 (AWS 공식 레퍼런스)
CloudWatch Metric Streams → Firehose → Lambda (Transform) → NLB → OpenTelemetry Collector (EC2)
Lambda 함수가 핵심입니다. Firehose는 VPC 내부 endpoint로 직접 전송할 수 없기 때문에, Lambda가 중간에서 데이터를 받아 NLB를 통해 Collector로 밀어 넣습니다.
# Lambda Transform 함수 예시 (Python 3.9+)
import json
import base64
import requests
import os
def lambda_handler(event, context):
output = []
for record in event['records']:
# Firehose는 base64로 인코딩된 데이터를 전달
payload = base64.b64decode(record['data']).decode('utf-8')
metrics = json.loads(payload)
# OpenTelemetry Collector HTTP endpoint로 전송
otel_endpoint = os.environ['OTEL_COLLECTOR_ENDPOINT']
try:
resp = requests.post(
f"{otel_endpoint}/v1/metrics",
json=metrics,
timeout=5
)
resp.raise_for_status()
# 성공 시 Firehose에 OK 신호
output.append({
'recordId': record['recordId'],
'result': 'Ok',
'data': record['data']
})
except Exception as e:
print(f"전송 실패: {e}")
# 실패 시 Firehose가 S3 백업으로 보내도록 처리
output.append({
'recordId': record['recordId'],
'result': 'ProcessingFailed',
'data': record['data']
})
return {'records': output}
🚨 주의사항: Lambda 함수는 Firehose와 동기적으로 동작합니다. 즉, Lambda가 타임아웃(기본 60초)에 걸리면 전체 스트림이 지연됩니다.
timeout을 충분히 설정하고, 재시도 로직을 꼭 추가하세요.

본론 2: CloudFormation으로 한 방에 배포하기
AWS CLI와 CloudFormation을 이용해 전체 스택을 자동화할 수 있습니다. 아래는 핵심 파라미터 예시입니다.
// parameters.json
[
{
"ParameterKey": "VpcId",
"ParameterValue": "vpc-12345678"
},
{
"ParameterKey": "SubnetIds",
"ParameterValue": "subnet-11111111,subnet-22222222"
},
{
"ParameterKey": "OtelCollectorEndpoint",
"ParameterValue": "http://internal-nlb-123456789.elb.ap-northeast-2.amazonaws.com:4318"
},
{
"ParameterKey": "StageBucketName",
"ParameterValue": "cf-stage-bucket-203918862653"
}
]
# CloudFormation 스택 생성
aws cloudformation create-stack \
--stack-name cw-metrics-demo \
--template-body file://cf-packaged-file.yaml \
--parameters file://parameters.json \
--capabilities CAPABILITY_IAM
국내 SI 환경에서의 적용 맥락
- 보안 규제가 강한 금융권/공공기관에서는 VPC 내부에 Collector를 두고 NLB로만 접근을 허용하는 이 아키텍처가 매우 적합합니다.
- 다만, Lambda가 VPC 내부에 위치해야 NLB에 접근할 수 있다는 점을 잊지 마세요. Lambda에 VPC 설정을 추가하면 콜드 스타트가 길어질 수 있으므로, Reserved Concurrency를 설정해 두는 것이 좋습니다.
- S3 버킷은 백업 용도로만 사용하므로, 실제 비용이 거의 발생하지 않습니다. 하지만 데이터 유실 방지를 위해 S3에 실패한 레코드를 저장하는
ProcessingFailed경로를 꼭 구성하세요.

결론: 실무 적용 전 체크리스트
이 아키텍처는 Push 기반 모니터링으로 전환하려는 모든 팀에게 훌륭한 출발점이 됩니다. 하지만 몇 가지 고려사항이 있습니다.
이 기술의 한계 또는 주의사항
- Lambda 동기 호출 비용: Firehose가 레코드 1MB당 Lambda를 호출하므로, 메트릭 양이 많으면 Lambda 비용이 증가할 수 있습니다. 사전에 예상 비용을 계산해보세요.
- Collector 장애 시 데이터 손실: Lambda에서 Collector로 전송 중 장애가 발생하면, Firehose는 재시도하지만 일부 데이터가 손실될 수 있습니다. S3 백업 + Dead Letter Queue 구성이 필수입니다.
- OTLP 버전 호환성: CloudWatch Metric Streams는 OpenTelemetry 0.7과 1.0을 지원합니다. Collector 버전과 맞춰야 합니다.
다음 단계 학습 방향
- border-radius의 한계를 깬 CSS corner-shape, 실무 적용 가이드 — 프론트엔드 성능 최적화에 관심이 있다면 함께 보세요.
- AWS Distro for OpenTelemetry (ADOT) 공식 문서를 읽어보고, X-Ray나 AMP와의 연동을 테스트해보세요.
- 실제 프로덕션에 적용하기 전에 카나리아 배포로 일부 메트릭만 먼저 전환해 보는 것을 추천합니다.
함께 보면 좋은 글