배경
주말에 ECS 앱이 컨테이너 이미지를 찾지 못하는 에러(CannotPullContainerError)가 발생하여 서비스가 중단되었습니다. 문제는 즉시 감지되지 않았고, 업무 영향도 파악 요청이 들어온 후에야 인지되었습니다.
이를 계기로 ECS Task가 STOPPED 상태로 전환될 때 즉시 알람을 받을 수 있는 모니터링을 구축했습니다.
CloudWatch vs EventBridge 비교
| 감시 항목 | CloudWatch 지표 | EventBridge 이벤트 |
|---|---|---|
| ECS 서비스 전체 상태 | 가능 | 가능 |
| 개별 태스크 STOPPED 감지 | 불가능 | 가능 |
| STOPPED 사유 확인 (CannotPullContainerError 등) | 불가능 | 가능 |
| 알람 연동 (SNS, 이메일 등) | 가능 (지표 기준) | 가능 (이벤트 기준) |
Task 상태만 확인하면 되는 상황이므로 CloudWatch 지표(RunningTaskCount) 방식을 선택했습니다.
개별 태스크의 STOPPED 사유까지 추적해야 한다면 EventBridge 이벤트 룰을 사용하는 것이 적합합니다.
아키텍처
ECS Service (RunningTaskCount 지표)
↓
CloudWatch Alarm (RunningTaskCount <= 0, 5분 내 5회 연속)
↓
SNS Topic (ecs-task-alerts)
↓
Lambda (SendSlackNotification)
↓
Slack Webhook → 채널 알림
구축 과정
1. Slack Webhook URL 생성
Slack 앱에서 Incoming Webhook을 생성하여 알림을 받을 채널에 연결합니다.
Slack 워크스페이스 > 앱 관리 > Incoming Webhooks > 새 Webhook 생성
→ 알림 채널 선택 → Webhook URL 복사
2. Lambda 함수 생성 (Slack 알림 처리)
SNS 메시지를 받아 Slack으로 전달하는 Lambda 함수를 생성합니다.
import json
import urllib.request
import os
SLACK_WEBHOOK_URL = os.environ['SLACK_WEBHOOK_URL']
def lambda_handler(event, context):
# SNS 메시지 파싱
sns_message = event['Records'][0]['Sns']
subject = sns_message.get('Subject', 'ECS Alert')
message = json.loads(sns_message['Message'])
alarm_name = message.get('AlarmName', 'Unknown')
new_state = message.get('NewStateValue', 'Unknown')
reason = message.get('NewStateReason', '')
# Slack 메시지 구성
slack_message = {
"text": f":rotating_light: *ECS Task Alert*",
"attachments": [
{
"color": "#ff0000" if new_state == "ALARM" else "#36a64f",
"fields": [
{"title": "Alarm", "value": alarm_name, "short": True},
{"title": "State", "value": new_state, "short": True},
{"title": "Reason", "value": reason[:200], "short": False},
],
}
],
}
req = urllib.request.Request(
SLACK_WEBHOOK_URL,
data=json.dumps(slack_message).encode('utf-8'),
headers={'Content-Type': 'application/json'},
)
urllib.request.urlopen(req)
return {'statusCode': 200}환경 변수:
SLACK_WEBHOOK_URL: Slack Webhook URL
3. SNS 주제 생성
AWS 콘솔 → SNS > 주제 > 새 주제 생성
이름: ecs-task-alerts
유형: 표준(Standard)
4. CloudWatch Alarm 생성
각 ECS 서비스마다 RunningTaskCount 알람을 설정합니다.
import boto3
cloudwatch = boto3.client('cloudwatch')
def create_ecs_task_alarm(cluster_name, service_name, alarm_name, sns_topic_arn):
cloudwatch.put_metric_alarm(
AlarmName=alarm_name,
MetricName='RunningTaskCount',
Namespace='AWS/ECS',
Dimensions=[
{'Name': 'ClusterName', 'Value': cluster_name},
{'Name': 'ServiceName', 'Value': service_name},
],
Statistic='Average',
Period=60, # 1분 간격 수집
EvaluationPeriods=5, # 5개 데이터 포인트
Threshold=0,
ComparisonOperator='LessThanOrEqualToThreshold',
AlarmActions=[sns_topic_arn],
TreatMissingData='breaching',
)알람 조건:
- 지표:
RunningTaskCount - 조건:
<= 0 - 기간: 1분 간격, 5분 내 5개 데이터 포인트 연속 충족 시 알람
- 알림: SNS 주제
ecs-task-alerts
5. SNS → Lambda 연결
Lambda > SendSlackNotification 함수 > 트리거 추가
트리거 유형: SNS
SNS 주제: ecs-task-alerts
→ Lambda IAM 역할에 SNS 실행 권한 자동 부여
알람 흐름 정리
1. ECS 서비스의 Task가 모두 중지됨 (RunningTaskCount = 0)
2. CloudWatch가 1분 간격으로 수집, 5분 연속 0 감지
3. CloudWatch Alarm → ALARM 상태 전환
4. SNS Topic (ecs-task-alerts)에 알림 발행
5. Lambda (SendSlackNotification) 트리거
6. Slack 채널에 알람 메시지 전송
설계 포인트
5분 연속 조건으로 오탐 방지 배포 중 일시적으로 Task가 0이 되는 상황(롤링 업데이트 등)에서 불필요한 알람이 발생하지 않도록 5회 연속 조건을 설정했습니다.
TreatMissingData = breaching 데이터 포인트가 누락되는 경우에도 알람이 발생하도록 설정합니다. 서비스 자체가 삭제되거나 메트릭 수집이 불가능한 상황도 감지합니다.
서비스별 개별 알람 각 ECS 서비스마다 별도 알람을 생성하여 어떤 서비스에서 문제가 발생했는지 즉시 식별할 수 있습니다.
정리
- ECS RunningTaskCount 지표 기반 Task 중단 감지
- CloudWatch Alarm → SNS → Lambda → Slack 알림 파이프라인 구축
- 5분 연속 조건으로 배포 중 오탐 방지
- 서비스별 개별 알람으로 장애 서비스 즉시 식별
- 주말/야간 장애 인지 지연 문제 해결