AWS 비용을 매일 1회 조회해 Discord 웹훅으로 보내는 Lambda 프로그램.
대상은 특정 리전 단위이며, 현재는 서울 리전(ap-northeast-2)만 본다.
상태: 구현 완료. Cost Explorer 조회(1회)·집계·Discord embed 렌더링까지 동작하고 테스트 22건이 통과한다. 배포는 로컬에서 Terraform 수동 실행.
ce:GetCostAndUsage 를 하루 한 번만 호출한다 — 이달 1일(매월 1일이면 어제)부터
오늘까지, Granularity=DAILY, GroupBy=SERVICE. 이 일별 데이터에서 아래 4개를 모두 파생한다.
| 항목 | 표현 방식 | 계산 |
|---|---|---|
| 어제 사용액 | 숫자 (headline) | 마지막 완료일(T-1) 서비스 합계 |
| 이번 달 누적액 (month-to-date) | 숫자 (headline) | 이달 1일~어제 전체 합 |
| 월말 예상 결제액 | 숫자 (headline) | 누적액 + (최근 7일 평균 일비용 × 남은 일수) |
| 서비스별 사용액 (어제 기준) | 막대그래프 (한눈에) | 어제 행의 GroupBy=SERVICE |
조회에 Filter: { Dimensions: { Key: REGION, Values: ["ap-northeast-2"] } } 를 건다.
리전을 바꾸려면 cost_region_filter 변수만 수정한다.
월말 예상은 AWS
GetCostForecast(ML 예측, +$0.01/요청)를 쓰지 않고 자체 추세로 계산한다. CE 호출을 하루 1건으로 줄이고, 신규 계정에서 예측 API가 실패하는 경우도 없앤다.
┌────────────────────────────────────────────────────────┐
│ 💸 AWS 비용 리포트 · 서울 리전 (ap-northeast-2) │
│ 2026-08-26 (어제) 기준 · Cost Explorer │
├────────────────────────────────────────────────────────┤
│ 어제 이번 달 누적 월말 예상 │
│ $4.21 $52.30 $68.90 │
│ │
│ 서비스별 (어제) │
│ ┌──────────────────────────────────────────────────┐ │
│ │ RDS (Aurora) ███████████████░░░░░ $1.90 45% │ │
│ │ EC2 ████████░░░░░░░░░░░░ $1.01 24% │ │
│ │ VPC █████░░░░░░░░░░░░░░░ $0.63 15% │ │
│ │ S3 ███░░░░░░░░░░░░░░░░░ $0.38 9% │ │
│ │ CloudWatch ██░░░░░░░░░░░░░░░░░░ $0.29 7% │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ 월 예산 $80.00 대비 86% 예상 │
└────────────────────────────────────────────────────────┘
- headline 3개는 embed의 inline
fields로 가로 배치. - 막대그래프는 유니코드 블록(
█ ░)으로 그려 monospace 코드블록에 넣는다. 외부 차트 서비스·이미지 호스팅 불필요. - embed
color는 월말 예상 / 예산 비율로 초록(<0.8)·노랑(<1.0)·빨강 전환 (예산 미설정 시 고정색). - embed 제목은 Cost Explorer 콘솔 링크다 (누르면 바로 이동).
- 서비스명은 약칭으로 표시한다 (
Amazon Relational Database Service→RDS등,report.short_service_name). - 원화 병기(
krw_per_usd > 0)는 headline 3필드에만. 막대그래프는 정렬 유지를 위해 USD 만.
flowchart LR
S["EventBridge Scheduler<br/>매일 09:00 KST"] --> L["Lambda (python3.12, arm64)<br/>aws-cost-monitoring"]
L -->|"GetCostAndUsage x1<br/>(DAILY, GroupBy=SERVICE)"| CE["Cost Explorer API<br/>(us-east-1 엔드포인트)"]
L -->|"GetParameter"| SSM["SSM Parameter Store<br/>/cost-monitoring/discord-webhook-url<br/>(SecureString)"]
L -->|"HTTPS POST (embed)"| D["Discord Webhook"]
L --> CW["CloudWatch Logs<br/>보존 14일"]
- Cost Explorer(
ce)는 글로벌 서비스지만 SDK 엔드포인트가 us-east-1이다. Lambda는 서울에 두되 boto3ce클라이언트만region_name="us-east-1"로 만든다 (CE_REGION환경변수). - 조직(Organizations) 관리 계정에서 실행하면
GroupBy=LINKED_ACCOUNT로 전 계정 비용을 한 번에 볼 수 있으나, 현재 스펙은 단일 계정·단일 리전 기준이다.
- 리포트 생성/전송이 실패하면
handler가 같은 Discord 채널에 에러 embed(⚠️ + traceback 꼬리)를 보내고 예외를 다시 던진다 → Lambda 가 실패로 기록된다. - EventBridge Scheduler 가 실패한 호출을 재시도한다 (
schedule_retry_attempts, 기본 2회 / 최대 1시간). 일시적 CE·Discord 오류는 대개 여기서 복구된다. - 알림 전송마저 실패해도 원래 예외는 그대로 노출된다 (CloudWatch Logs 에 stack trace).
| 항목 | 월 비용 |
|---|---|
| Cost Explorer API | 하루 1요청 x $0.01 x 30.4일 ≈ $0.30 |
| Lambda | $0 (월 30회, 상시 무료 티어) |
| EventBridge Scheduler | $0 (월 1,400만 호출 무료) |
| CloudWatch Logs | ~$0 (하루 수 KB, 14일 보존) |
SSM Parameter Store (Standard) + KMS(aws/ssm) |
$0 |
| 합계 | 약 $0.3 / 월 |
- Cost Explorer 활성화 — AWS 콘솔 → Billing → Cost Explorer 활성화 (24시간 소요, 무료).
- Discord 웹훅 생성 — 채널 설정 → 연동 → 웹훅 → URL 복사.
- 웹훅 URL을 SSM에 저장 (SecureString, 기본 키):
Terraform은 이 파라미터를 읽기만 한다(값이 state에 남지 않음). 없으면
aws ssm put-parameter \ --region ap-northeast-2 \ --name "/cost-monitoring/discord-webhook-url" \ --type SecureString \ --value "https://discord.com/api/webhooks/XXXX/YYYY"
plan이 실패한다. - AWS 자격증명 — 로컬에
ap-northeast-2기본 프로파일 (aws configure).
cp terraform.tfvars.example terraform.tfvars # 필요한 값만 수정
terraform init
terraform plan
terraform apply즉시 한 번 실행해 확인:
aws lambda invoke --region ap-northeast-2 \
--function-name aws-cost-monitoring /dev/stdout| 변수 | 기본값 | 설명 |
|---|---|---|
aws_region |
ap-northeast-2 |
리소스를 만들 리전 |
cost_region_filter |
ap-northeast-2 |
비용을 집계할 대상 리전 (리포트 범위) |
schedule_expression |
cron(0 9 * * ? *) |
실행 시각 |
schedule_timezone |
Asia/Seoul |
위 cron의 타임존 |
schedule_retry_attempts |
2 |
호출 실패 시 Scheduler 재시도 횟수 (0~185) |
webhook_ssm_parameter_name |
/cost-monitoring/discord-webhook-url |
웹훅 URL이 담긴 SSM 파라미터 이름 |
top_service_count |
6 |
막대그래프에 표시할 상위 서비스 수 |
monthly_budget_usd |
0 |
0이면 예산 대비 표시·색상 전환 없음 |
krw_per_usd |
0 |
0보다 크면 원화 환산액 병기 (고정 환율) |
log_retention_days |
14 |
CloudWatch Logs 보존 기간 |
lambda_memory_mb |
128 |
Lambda 메모리 |
log_level |
INFO |
Lambda 로그 레벨 |
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements-dev.txt
pytestlambda/ 는 zip 루트로 패키징되므로 모듈은 플랫 임포트(import cost_explorer)한다.
테스트는 conftest.py 가 lambda/ 를 sys.path 에 추가한다.
AWS-Cost-Monitoring/
├── README.md
├── .github/workflows/ci.yml # pytest + terraform fmt/validate
├── versions.tf # Terraform / provider 버전 제약
├── main.tf # Lambda · Scheduler(재시도) · IAM · 로그그룹
├── variables.tf
├── outputs.tf
├── terraform.tfvars.example
├── requirements.txt # 런타임 의존성 (없음 — 런타임 boto3 + stdlib)
├── requirements-dev.txt # boto3, pytest
├── conftest.py
├── lambda/
│ ├── handler.py # 진입점 lambda_handler (+ 실패 시 에러 알림)
│ ├── config.py # 환경변수 파싱
│ ├── cost_explorer.py # CE 조회 1회 (DAILY, GroupBy=SERVICE) + 페이지네이션
│ ├── report.py # 데이터 모델 + 집계(build_cost_report) + 막대그래프
│ └── discord_client.py # embed 조립 + 웹훅 POST
├── tests/
│ ├── test_cost_explorer.py
│ ├── test_discord_client.py
│ ├── test_handler.py
│ └── test_report.py
└── scripts/
└── invoke.sh # 배포된 함수 수동 실행
-
cost_explorer.py/report.py/discord_client.py— 구현 완료, 테스트 22건 - Lambda 실패 시 Discord 에러 알림 · Scheduler 재시도(기본 2회) · GitHub Actions CI
- 크레딧 표기 — 현재는 순비용(
UnblendedCost) 기준. 프로모션 크레딧 소진액을 따로 보고 싶으면RECORD_TYPE=Credit필터 호출을 1건 추가 (+$0.01/일) - 상위 서비스 외 나머지는 "기타"로 합산 표시 여부
- Cost Explorer 데이터는 최대 24시간 지연되고 당일 값은 불완전하다. 리포트는 항상 어제(T-1) 기준이다.
- 월말 예상은 최근 7일 평균 일비용 기반 추정이다. 비용이 급증/급감한 직후 며칠은 실제와 벌어질 수 있다.
- 매월 1일 리포트는 "어제"가 지난달 말일이라 이달 누적이 $0 으로 표시된다.
- 금액 단위는 CE 응답의
Unit(보통 USD)을 그대로 쓴다.