Звонки по алармам CloudWatch без Lambda и Twilio

SNS не умеет звонить на телефон. Направьте алармы CloudWatch через EventBridge в звонковый вебхук: настройка, фильтры, подводные камни.

Обновлено

Содержание

Аларм CloudWatch не может позвонить вам на телефон. Его встроенные действия — это SNS, Auto Scaling, EC2 и Systems Manager, а SNS заканчивается письмом, SMS или очередью, за которой нужно следить. Руководства, после которых телефон всё-таки звонит, поднимают функцию Lambda и аккаунт Twilio. Можно обойтись без обоих: пусть EventBridge получает событие смены состояния аларма, превращает его в небольшое JSON-тело и отправляет POST-запросом в звонковый канал Echobell.

В этом руководстве — весь путь: канал, правило EventBridge, input transformer, фильтры, благодаря которым звонок сохраняет смысл, и объяснение, почему направить SNS прямо в вебхук кажется рабочей идеей, но в основном не работает.

Почему обычный путь через SNS неудобен

Стандартный совет — «аларм → топик SNS → подписчик». Для телефонного звонка у этой схемы три проблемы:

  • У SNS нет голосового протокола. Типы подписчиков — email, SMS, мобильный пуш, SQS, Lambda, Firehose и HTTP(S). SMS легко пропустить, и его блокирует «Не беспокоить»; для голосового звонка нужно собирать что-то своё.
  • HTTPS-сообщение — это строка внутри строки. SNS доставляет свой конверт с Content-Type: text/plain, а сам аларм — это JSON-документ, сериализованный в поле Message конверта. Приёмник вебхука, который читает поля — AlarmName, NewStateValue, — находит лишь один непрозрачный блок.
  • Конверт большой. Подпись, URL сертификата подписи и ссылка для отписки добавляют к конверту заметно больше килобайта ещё до того, как начнётся текст аларма. Echobell обрезает тело уведомления на 1500 байтах, поэтому шаблон «выведи всё тело» обрывается ровно там, где начинается полезное.

Заставить SNS работать можно с помощью Lambda, которая распаковывает сообщение и пересылает его дальше. Но CloudWatch и так публикует каждую смену состояния аларма в EventBridge как чистое JSON-событие — настраивать для этого ничего не нужно, — а EventBridge умеет переформатировать его и сам вызвать HTTPS-эндпоинт.

Что понадобится

  • Аккаунт AWS и права на создание правил, соединений (connections) и API destinations в EventBridge
  • Установленный Echobell (App Store / Google Play)
  • Хотя бы один существующий аларм CloudWatch
  • Десять минут

Ничего с вашей стороны не должно принимать входящий трафик. Исходящий запрос делает EventBridge.

Шаг 1 — Создайте канал, который звонит

В Echobell создайте канал и выберите тип уведомления Звонок. Звонковый канал ведёт себя как входящий вызов, а не как пуш, поэтому проходит через режим фокусирования iOS и «Не беспокоить».

Трансформер из шага 3 отправляет плоское тело с полями alarm, state, reason, region и link, так что шаблоны получаются короткими:

Заголовок: {{state}}: {{alarm}}
Текст: {{reason}}

В расширенных настройках задайте шаблон ссылки, чтобы нажатие на уведомление открывало аларм в консоли:

{{link}}

Скопируйте URL вебхука из карточки канала:

https://hook.echobell.one/t/<channel-token>

Шаг 2 — Создайте connection и API destination

EventBridge вызывает внешние HTTPS-эндпоинты через API destination, а ему нужен connection, который несёт аутентификацию. Echobell аутентифицирует по секретному токену в URL, так что connection здесь формальность, но без него EventBridge не создаст destination.

  1. Откройте EventBridge → Integration → API destinations → Connections → Create connection
  2. Name: echobell
  3. Authorization type: API key
  4. API key name: x-source, Value: eventbridge
  5. Создайте его, затем перейдите в API destinations → Create API destination
  6. Name: echobell-calling
  7. API destination endpoint: URL вашего канала Echobell из шага 1
  8. HTTP method: POST
  9. Invocation rate limit: достаточно 1 в секунду, это ещё и предохранитель
  10. Connection: echobell

Заголовок с API-ключом отправляется с каждым запросом. Его можно проверять в условии канала Echobell, если хотите второй барьер перед токеном:

header["x-source"] == "eventbridge"

Шаг 3 — Создайте правило и input transformer

Откройте 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. Позвольте консоли создать новую execution role — она выдаёт events:InvokeApiDestination для этого destination и больше ничего.

Затем раскройте 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 — Узнайте, как событие выглядит на самом деле

Input path читает данные из события, которое публикует 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 — сгенерированный текст. В нём названы datapoint и порог — именно то, что хочется услышать в три часа ночи.
  • Имена алармов подставляются как есть. Двойная кавычка внутри имени аларма или reason сломает JSON, который выдаёт трансформер. Используйте в именах алармов только буквы, цифры, дефисы и подчёркивания — это и так хорошая практика, — иначе звонок завершится ошибкой 400.
  • Для ссылки нужно простое имя. Имя с пробелами или # не приведёт по ссылке куда нужно; поиск в консоли его всё равно найдёт.

Шаг 5 — Сузьте до того, что заслуживает звонка

Канал, который звонит на каждый ALARM, — самый быстрый способ научить себя его игнорировать. Фильтруйте в двух местах.

В шаблоне EventBridge, который отрабатывает до отправки чего-либо:

ЦельИзменение шаблона
Только продакшен-алармы"alarmName": [{ "prefix": "prod-" }] внутри detail
Пропускать всё несрочноеНазовите такие алармы 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"] сообщит вам, что проблема исчезла, без звонка. Создайте второй канал Echobell типа Срочное (Time Sensitive) (или обычного), второй API destination, указывающий на него, и используйте тот же input transformer. Шаблон заголовка {{state}}: {{alarm}} уже читается как «OK: prod-api-5xx».

ПравилоСостояниеТип каналаПоведение
alarms-to-callALARM, префикс prod-ЗвонокЗвонит сквозь режим фокусирования
alarms-warnALARM, префикс warn-СрочноеЭкран блокировки, без звонка
alarms-recoveredOKОбычноеОбычный пуш

Проверьте, прежде чем доверять

Не ждите, пока сломается продакшен. Принудительно переведите аларм в нужное состояние:

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 и email-to-call?

Если SNS-топик у вас уже везде и трогать EventBridge не хочется, есть ещё два пути:

  • SNS → Lambda → вебхук. Несколько строк кода разбирают Message, затем отправляют плоское тело на тот же URL Echobell. Работает, но теперь у вас есть функция, роль и её логи.
  • SNS → email → email-триггер Echobell. Подпишите на топик адрес email-триггера канала. Аларм приходит как письмо, а {{subject}} и {{text}} в ваших шаблонах становятся уведомлением. Это самая простая настройка из всех, с оговоркой: email-подписки SNS требуют однократного клика для подтверждения, а текст не преобразуется.

Подписывать URL вебхука Echobell прямо на топик как HTTPS-эндпоинт не стоит: запрос подтверждения придёт как уведомление, а каждый следующий аларм будет тем самым длинным конвертом.

Ограничения, которые важны во время инцидента

  • Тело 1 MiB. Echobell отклоняет тела триггера больше 1 MiB с HTTP 413. Преобразованный аларм занимает несколько сотен байт, так что здесь вы до этого не дойдёте.
  • 120 запросов в минуту на токен. Дальше триггер отвечает 429 с RATE_LIMIT_EXCEEDED и Retry-After. Лимит destination в 1 запрос в секунду держит вас под ним, а EventBridge повторяет отправку событий, которые не удалось доставить.
  • 1500 байт тела уведомления. Более длинный результат обрезается. state.reason — это одно-два предложения, и оно помещается.

Чего эта схема не даёт

  • Нет подтверждения. Ответ на звонок ничего не сообщает CloudWatch и не заглушает других подписчиков.
  • Нет эскалации и ротации. Звонят всем подписчикам канала; следующему не звонит никто.
  • Нет дедупликации сверх самого аларма. Аларм, который «дребезжит» и переходит OK → ALARM каждые несколько минут, будет звонить каждый раз. Исправляйте это более длинными периодами оценки или DatapointsToAlarm в самом аларме.
  • Нет двусторонней синхронизации. Устранение причины ничего не очищает на вашем телефоне.

Если это неприемлемо — вам нужна полноценная платформа дежурств. Если нужно просто «позвони, когда сработает продакшен-аларм», это несколько экранов консоли.

Разбор проблем

Аларм сработал, но ничего не пришло. Откройте вкладку Monitoring правила: Invocations показывает, совпал ли шаблон, FailedInvocations — не удалась ли доставка. Правила в другом регионе, чем аларм, никогда не срабатывают.

Правило срабатывает, но звонок падает с ошибкой 400. Трансформер выдал невалидный JSON. Поищите кавычку или обратный слеш в имени аларма или в reason.

Уведомление пустое. Шаблон читает поле, которое трансформер не отправлял. Тело плоское: {{alarm}}, а не {{detail.alarmName}}.

Звонит снова и снова по одному аларму. Аларм «дребезжит». Увеличьте период оценки или потребуйте больше datapoints; это настройка CloudWatch, а не Echobell.

Звонит по алармам, которых вы не ждали. Проверьте, не пропущен ли в шаблоне фильтр prefix, и помните, что составные алармы и алармы, созданные другими инструментами (например, политиками 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?

События смены состояния аларма попадают в шину событий по умолчанию бесплатно; вызовы API destination тарифицируются за запрос. При объёмах здорового набора алармов это ничтожная сумма, но проверьте актуальные цены EventBridge для вашего региона.

Можно ли звонить разным людям по разным алармам?

Да. Создайте по каналу на аудиторию, по правилу на канал, и пусть каждый подписывается с нужным ему типом уведомления. Подтверждения нет, поэтому звонят всем, кто подписался с типом «звонок».

А что с алармами в нескольких регионах или аккаунтах?

Правила действуют в пределах региона, так что создайте правило в каждом используемом регионе (то же определение API destination повторяется для каждого региона). Для нескольких аккаунтов отправляйте события в центральную шину одного аккаунта и поместите правило туда.

Итог

Схема состоит из пяти частей: звонковый канал, connection и API destination, указывающие на него, правило, которое ловит только алармы, ради которых стоит будить человека, input transformer, выравнивающий событие, и принудительный тест set-alarm-state, прежде чем на что-то из этого полагаться. Всё остальное здесь — о том, как держать схему достаточно узкой, чтобы через месяц звонок всё ещё что-то значил.

Скачайте Echobell для iPhone или возьмите в Google Play, а затем запустите тест set-alarm-state.

Похожие статьи