목차
- 흔한 SNS 경로가 불편한 이유
- 준비물
- 1단계 — 울리는 채널 만들기
- 2단계 — 커넥션과 API destination 만들기
- 3단계 — 규칙과 입력 트랜스포머 만들기
- 4단계 — 이벤트가 실제로 어떻게 생겼는지 알기
- 5단계 — 전화를 받을 만한 것까지 좁히기
- 6단계 — 복구 알림에는 조용한 문을 따로
- 믿기 전에 테스트하기
- SNS와 이메일-전화 변환은?
- 인시던트 중에 중요한 제한
- 이 구성이 제공하지 않는 것
- 문제 해결
- 자주 묻는 질문
- CloudWatch가 자체적으로 전화번호에 전화를 걸 수 있나요?
- SNS 토픽이 필요한가요?
- CloudWatch 알람이 방해 금지를 뚫나요?
- 웹훅 URL을 SNS에 바로 구독시키면 안 되나요?
- AWS에서 비용이 드나요?
- 알람마다 다른 사람에게 전화할 수 있나요?
- 여러 리전이나 계정의 알람은요?
- 정리
- 관련 글
CloudWatch 알람은 여러분에게 전화를 걸 수 없습니다. 내장된 액션은 SNS, Auto Scaling, EC2, Systems Manager뿐이고, SNS의 종착지는 메일, SMS, 아니면 계속 지켜보고 있어야 하는 큐입니다. 실제로 전화가 울리게 해 주는 튜토리얼은 Lambda 함수와 Twilio 계정을 엮어 놓았습니다. 둘 다 건너뛸 수 있습니다. EventBridge가 알람 상태 변경을 받게 하고, 작은 JSON 본문으로 재구성한 다음, Echobell 전화 채널로 POST하면 됩니다.
이 글은 전체 경로를 다룹니다. 채널, EventBridge 규칙, 입력 트랜스포머, 벨소리가 의미를 유지하게 해 주는 필터, 그리고 SNS를 웹훅에 곧바로 연결하면 될 것 같은데 대부분 안 되는 이유까지.
흔한 SNS 경로가 불편한 이유
흔한 조언은 "알람 → SNS 토픽 → 구독자"입니다. 전화를 거는 용도로는 세 가지 문제가 있습니다.
- SNS에는 음성 프로토콜이 없습니다. 구독자 유형은 이메일, SMS, 모바일 푸시, SQS, Lambda, Firehose, HTTP(S)입니다. SMS는 놓치기 쉽고 방해 금지에 막힙니다. 음성 통화는 직접 만들어야 합니다.
- HTTPS 메시지는 문자열 안의 문자열입니다. SNS는 봉투(envelope)를
Content-Type: text/plain으로 전달하고, 알람 자체는 JSON 문서로 직렬화되어 봉투의Message필드에 들어 있습니다.AlarmName,NewStateValue같은 필드를 읽는 웹훅 수신기에는 내용을 알 수 없는 덩어리 하나로만 보입니다. - 봉투가 큽니다. 서명, 서명 인증서 URL, 구독 해지 링크만으로도 알람 본문이 시작되기 전에 1킬로바이트를 훌쩍 넘습니다. Echobell은 알림 본문을 1500바이트에서 자르므로, "본문 전체를 출력하는" 템플릿은 유용한 부분이 시작되는 바로 그 지점에서 잘립니다.
메시지를 풀어서 전달하는 Lambda를 두면 SNS도 동작하게 만들 수 있습니다. 하지만 CloudWatch는 이미 모든 알람 상태 변경을 깔끔한 JSON 이벤트로 EventBridge에 발행하고 있고(설정할 액션도 필요 없습니다), EventBridge는 직접 재구성해서 HTTPS 엔드포인트를 호출할 수 있습니다.
준비물
- AWS 계정과 EventBridge 규칙, 커넥션, API destination을 만들 권한
- Echobell 설치(App Store / Google Play)
- 기존 CloudWatch 알람 하나 이상
- 10분
여러분 쪽에서 인바운드 트래픽을 받을 필요는 없습니다. 아웃바운드 요청은 EventBridge가 보냅니다.
1단계 — 울리는 채널 만들기
Echobell에서 채널을 만들고 알림 유형을 전화(Calling) 로 설정합니다. 전화 채널은 푸시가 아니라 수신 전화처럼 동작하므로 iOS 집중 모드와 방해 금지를 통과합니다.
3단계의 트랜스포머는 alarm, state, reason, region, link 필드를 가진 평평한 본문을 보내므로, 템플릿은 짧게 유지할 수 있습니다.
제목: {{state}}: {{alarm}}
본문: {{reason}}
고급 설정에서 링크 템플릿을 지정하면 알림을 눌렀을 때 콘솔에서 해당 알람이 열립니다.
{{link}}
채널 상세 화면에서 채널의 웹훅 URL을 복사합니다.
https://hook.echobell.one/t/<channel-token>
2단계 — 커넥션과 API destination 만들기
EventBridge는 API destination으로 외부 HTTPS 엔드포인트를 호출하며, 이를 위해서는 인증 정보를 담은 connection이 필요합니다. Echobell은 URL 안의 비밀 토큰으로 인증하므로 커넥션은 형식적인 절차입니다. 그래도 커넥션이 없으면 EventBridge는 destination을 만들어 주지 않습니다.
- EventBridge → Integration → API destinations → Connections → Create connection을 엽니다
- Name:
echobell - Authorization type: API key
- API key name:
x-source, Value:eventbridge - 생성한 뒤 API destinations → Create API destination으로 이동
- Name:
echobell-calling - API destination endpoint: 1단계의 Echobell 채널 URL
- HTTP method:
POST - Invocation rate limit: 초당
1회면 충분하며 안전 밸브 역할도 합니다 - Connection:
echobell
API 키 헤더는 모든 요청에 실려 갑니다. 토큰 앞에 두 번째 관문을 두고 싶다면 Echobell 채널 조건에서 확인할 수 있습니다.
header["x-source"] == "eventbridge"
3단계 — 규칙과 입력 트랜스포머 만들기
알람과 같은 리전에서 EventBridge → Rules → Create rule로 이동합니다. 규칙은 리전 단위입니다. eu-west-1의 알람은 us-east-1의 규칙에서 보이지 않습니다.
default 이벤트 버스와 event pattern 규칙을 사용합니다. 아래 패턴은 ALARM으로 전환되는 모든 알람과 일치합니다.
{
"source": ["aws.cloudwatch"],
"detail-type": ["CloudWatch Alarm State Change"],
"detail": {
"state": { "value": ["ALARM"] }
}
}
타깃으로는 EventBridge API destination을 고르고 echobell-calling을 선택합니다. 실행 역할은 콘솔이 새로 만들게 하세요. 이 destination에 대한 events:InvokeApiDestination만 부여하고 그 밖에는 아무것도 주지 않습니다.
그런 다음 Additional settings → Configure target input → Input transformer를 펼칩니다.
Input path
{
"alarm": "$.detail.alarmName",
"state": "$.detail.state.value",
"reason": "$.detail.state.reason",
"region": "$.region",
"time": "$.time"
}
Template
{
"alarm": "<alarm>",
"state": "<state>",
"reason": "<reason>",
"region": "<region>",
"time": "<time>",
"link": "https://console.aws.amazon.com/cloudwatch/home?region=<region>#alarmsV2:alarm/<alarm>"
}
EventBridge는 결과를 application/json으로 보내므로, Echobell이 {{alarm}}과 {{state}}를 바로 읽을 수 있습니다.
4단계 — 이벤트가 실제로 어떻게 생겼는지 알기
입력 경로는 CloudWatch가 발행하는 이벤트에서 값을 읽습니다. 핵심만 남기면 이렇습니다.
{
"version": "0",
"id": "c4c1c1c9-6542-e61b-6ef0-8c4d36933a92",
"detail-type": "CloudWatch Alarm State Change",
"source": "aws.cloudwatch",
"account": "123456789012",
"time": "2026-10-16T03:12:44Z",
"region": "us-east-1",
"resources": ["arn:aws:cloudwatch:us-east-1:123456789012:alarm:prod-api-5xx"],
"detail": {
"alarmName": "prod-api-5xx",
"state": {
"value": "ALARM",
"reason": "Threshold Crossed: 1 out of the last 1 datapoints [42.0] was greater than the threshold (10.0).",
"timestamp": "2026-10-16T03:12:44.000+0000"
},
"previousState": { "value": "OK" },
"configuration": { "description": "API 5xx errors on the prod load balancer" }
}
}
템플릿을 고치기 전에 알아둘 만한 것들입니다.
- 알람 정보는
detail아래에 있습니다.$.alarmName은 아무것도 일치시키지 못하고,$.detail.alarmName은 일치합니다. state.reason은 자동 생성된 텍스트입니다. 데이터 포인트와 임계값이 적혀 있어서, 새벽 3시에 딱 듣고 싶은 내용입니다.- 알람 이름은 그대로 삽입됩니다. 알람 이름이나 reason에 큰따옴표가 들어가면 트랜스포머가 만드는 JSON이 깨집니다. 알람 이름에는 영문자, 숫자, 하이픈, 밑줄만 쓰세요. 어차피 좋은 습관입니다. 그렇지 않으면 호출이 400으로 실패합니다.
- 링크에는 단순한 이름이 필요합니다. 공백이나
#이 들어간 이름은 딥링크가 제대로 동작하지 않습니다. 콘솔 검색에서는 여전히 찾을 수 있습니다.
5단계 — 전화를 받을 만한 것까지 좁히기
모든 ALARM에 울리는 알람 채널은 스스로 무시하는 법을 가르치는 가장 빠른 방법입니다. 두 군데서 걸러내세요.
EventBridge 패턴에서. 무엇이든 전송되기 전에 실행됩니다.
| 목표 | 패턴 변경 |
|---|---|
| 운영 환경 알람만 | detail 안에 "alarmName": [{ "prefix": "prod-" }] |
| 긴급하지 않은 것 제외 | 이름을 warn-…으로 짓고 "alarmName": [{ "anything-but": { "prefix": "warn-" } }] 사용 |
| 특정 알람만 | "alarmName": ["prod-api-5xx", "prod-db-cpu"] |
| 진짜 알람만, 테스트 상태 제외 | "state": { "value": ["ALARM"] } 유지 — INSUFFICIENT_DATA 이벤트는 절대 일치하지 않습니다 |
| 한 계정 또는 한 리전만 | 최상위에 "account": ["123456789012"] 또는 "region": ["us-east-1"] 추가 |
Echobell 쪽에서. 시간대처럼 패턴으로 표현할 수 없는 것에는 채널 조건을 씁니다.
state == "ALARM" && (hour >= 17 || hour < 9)
Echobell의 시간 변수는 UTC이므로 먼저 자신의 표준시에서 환산하세요. 전체 변수 목록은 조건 레퍼런스에 있습니다.
6단계 — 복구 알림에는 조용한 문을 따로
"value": ["OK"]인 두 번째 규칙을 두면 울리지 않고 문제가 해소되었음을 알릴 수 있습니다. 시간 민감(Time Sensitive)(또는 일반) 유형의 두 번째 Echobell 채널과 그 채널을 가리키는 두 번째 API destination을 만들고, 같은 입력 트랜스포머를 재사용하세요. 제목 템플릿 {{state}}: {{alarm}}은 이미 "OK: prod-api-5xx"로 읽힙니다.
| 규칙 | 상태 | 채널 유형 | 동작 |
|---|---|---|---|
alarms-to-call | ALARM, prod- 접두사 | 전화 | 집중 모드를 뚫고 울림 |
alarms-warn | ALARM, warn- 접두사 | 시간 민감 | 잠금 화면에 표시, 울리지 않음 |
alarms-recovered | OK | 일반 | 평범한 푸시 |
믿기 전에 테스트하기
운영 환경이 망가질 때까지 기다리지 마세요. 알람을 원하는 상태로 강제로 바꿉니다.
aws cloudwatch set-alarm-state \
--alarm-name prod-api-5xx \
--state-value ALARM \
--state-reason "Manual test of the Echobell path"
알람은 다음 평가 때 실제 상태로 돌아가지만, 상태 변경 이벤트는 진짜이며 여러분의 규칙을 통과합니다. Echobell 쪽만 테스트하려면 채널을 직접 호출하세요.
curl -X POST https://hook.echobell.one/t/<channel-token> \
-H 'Content-Type: application/json' \
-d '{"alarm":"prod-api-5xx","state":"ALARM","reason":"Manual test","region":"us-east-1","link":"https://console.aws.amazon.com/cloudwatch/"}'
curl은 울리는데 알람 테스트는 울리지 않는다면, 문제는 채널이 아니라 규칙, 트랜스포머, 또는 destination에 있습니다.
SNS와 이메일-전화 변환은?
이미 SNS 토픽이 곳곳에 있고 EventBridge는 건드리고 싶지 않다면, 다른 경로가 두 가지 있습니다.
- SNS → Lambda → 웹훅. 몇 줄의 코드가
Message를 파싱한 뒤 같은 Echobell URL로 평평한 본문을 POST합니다. 동작은 하지만, 이제 함수와 역할, 그리고 그 로그를 직접 관리해야 합니다. - SNS → 이메일 → Echobell 이메일 트리거. 채널의 이메일 트리거 주소를 토픽에 구독시킵니다. 알람이 이메일로 도착하고, 템플릿의
{{subject}}와{{text}}가 알림이 됩니다. 설정이 가장 적게 들지만, SNS 이메일 구독은 확인 링크를 한 번 클릭해야 하고 텍스트가 재구성되지 않는다는 단서가 붙습니다.
Echobell 웹훅 URL을 HTTPS 엔드포인트로 토픽에 직접 구독시키는 것은 피해야 할 경로입니다. 확인 요청이 알림으로 도착하고, 그 후의 모든 알람은 위에서 설명한 긴 봉투가 됩니다.
인시던트 중에 중요한 제한
- 본문 1 MiB. Echobell은 1 MiB를 넘는 트리거 본문을 HTTP 413으로 거부합니다. 변환된 알람은 수백 바이트이므로 여기서 걸릴 일은 없습니다.
- 토큰당 분당 120회 요청. 초과하면 트리거는
429와RATE_LIMIT_EXCEEDED,Retry-After를 반환합니다. destination의 초당 1회 속도 제한이 그 아래로 유지해 주고, EventBridge는 전달하지 못한 이벤트를 재시도합니다. - 알림 본문 1500바이트. 더 긴 결과는 잘립니다.
state.reason은 한두 문장이라 들어갑니다.
이 구성이 제공하지 않는 것
- 확인(ack)이 없습니다. 전화를 받아도 CloudWatch에는 아무것도 전달되지 않고, 다른 구독자를 조용하게 만들지도 않습니다.
- 에스컬레이션도 로테이션도 없습니다. 채널을 구독한 모든 사람에게 전화가 가고, 그다음 사람은 없습니다.
- 알람 자체를 넘어서는 중복 제거가 없습니다. 몇 분마다
OK → ALARM을 오가며 흔들리는 알람은 매번 울립니다. 알람 자체에서 평가 기간을 늘리거나DatapointsToAlarm을 설정해 해결하세요. - 양방향 동기화가 없습니다. 원인을 고쳐도 휴대폰에서는 아무것도 정리되지 않습니다.
이것들이 결정적 문제라면 완전한 온콜 플랫폼이 필요합니다. 필요한 게 "운영 알람이 발생하면 전화해 줘"라면, 콘솔 화면 몇 개면 됩니다.
문제 해결
알람은 발생했는데 아무것도 오지 않음. 규칙의 Monitoring 탭을 여세요. Invocations는 패턴이 일치했는지, FailedInvocations는 전달이 실패했는지 보여 줍니다. 알람과 다른 리전의 규칙은 절대 일치하지 않습니다.
규칙은 일치하는데 호출이 400으로 실패함. 트랜스포머가 잘못된 JSON을 만들었습니다. 알람 이름이나 reason에 따옴표나 역슬래시가 있는지 찾아보세요.
알림이 비어 있음. 템플릿이 트랜스포머가 보내지 않은 필드를 읽고 있습니다. 본문은 평평합니다. {{detail.alarmName}}이 아니라 {{alarm}}입니다.
한 알람이 반복해서 울림. 알람이 흔들리고(flapping) 있습니다. 평가 기간을 늘리거나 더 많은 데이터 포인트를 요구하세요. 이것은 Echobell이 아니라 CloudWatch 설정입니다.
예상하지 못한 알람에 울림. 패턴에 prefix 필터가 빠지지 않았는지 확인하고, 복합 알람(composite alarm)과 다른 도구가 만든 알람(예: Auto Scaling 정책)도 상태 변경을 발생시킨다는 점을 기억하세요.
자주 묻는 질문
CloudWatch가 자체적으로 전화번호에 전화를 걸 수 있나요?
아니요. CloudWatch 알람 액션은 SNS 알림, Auto Scaling, EC2 액션, Systems Manager입니다. 음성 통화에는 외부 서비스가 필요합니다. 공급자를 통해 발신하는 Lambda, 온콜 플랫폼, 또는 Echobell 같은 전화 웹훅입니다.
SNS 토픽이 필요한가요?
아니요. CloudWatch는 알람에 액션이 있든 없든 스스로 알람 상태 변경 이벤트를 EventBridge로 보냅니다. 알람의 액션 목록은 비워 두어도 됩니다.
CloudWatch 알람이 방해 금지를 뚫나요?
전화형 알림으로 도착할 때만 그렇습니다. 전화 유형의 Echobell 채널은 수신 전화처럼 동작하므로 iOS 집중 모드와 방해 금지가 통과시킵니다. SMS나 일반 푸시는 통과하지 못합니다.
웹훅 URL을 SNS에 바로 구독시키면 안 되나요?
SNS는 text/plain으로 보내고 알람이 Message 문자열 안에 직렬화되어 있어서 템플릿이 개별 필드를 읽을 수 없고, 봉투도 잘릴 만큼 깁니다. 또 SNS는 먼저 구독 확인을 보내는데, 이것이 알림으로 도착하게 됩니다.
AWS에서 비용이 드나요?
알람 상태 변경 이벤트가 default 이벤트 버스에 도달하는 것은 무료이고, API destination 호출은 요청 단위로 과금됩니다. 건강한 알람 집합의 양이라면 무시할 만한 금액이지만, 사용 중인 리전의 최신 EventBridge 요금을 확인하세요.
알람마다 다른 사람에게 전화할 수 있나요?
가능합니다. 대상 그룹마다 채널 하나, 채널마다 규칙 하나를 만들고, 각자 원하는 알림 유형으로 구독하게 하세요. 확인 기능이 없으므로 "전화"로 구독한 모든 사람에게 전화가 갑니다.
여러 리전이나 계정의 알람은요?
규칙은 리전 단위이므로 사용하는 각 리전에 규칙을 만드세요(같은 API destination 정의를 리전마다 반복합니다). 여러 계정이라면 이벤트를 한 계정의 중앙 버스로 보내고 규칙을 거기에 두세요.
정리
경로는 다섯 부분입니다. 전화 채널, 그것을 가리키는 커넥션과 API destination, 누군가를 깨울 만한 알람에만 일치하는 규칙, 이벤트를 평평하게 만드는 입력 트랜스포머, 그리고 무엇이든 의존하기 전에 실행하는 set-alarm-state 강제 테스트. 여기 적힌 나머지는 전부, 한 달 뒤에도 그 벨소리가 의미를 갖도록 충분히 좁게 유지하는 방법에 관한 이야기입니다.
iPhone용 Echobell 다운로드 또는 Google Play에서 받기 후, set-alarm-state 테스트를 실행해 보세요.