Use Amazon CloudWatch Alarms#

Keywords: AWS, Amazon, CloudWatch, CW, Alarm, Alarms

What is Alarm#

Alarm 本质是对 Metrics 进行一些数学计算后的结果满足了某种条件的特殊事件. 例如对于每分钟的 CPU 的使用率这个 Metrics, 如果 10 分钟内的平均使用率超过了 90%, 那么这就是一个 Alarm 事件.

How to Create an Alarm#

创建 Alarm 的方式有很多种. 从你所使用的工具的角度, 你可以用 Console, 也可以用 API, 也可以用 IAC (Infrastructure as Code). 但我们重点不讨论这个. 我们重点讨论从业务逻辑的角度, 有哪些创建 Alarm 的方法.

AWS recommended Alarm 是一种很特别的方式. 因为很多 AWS service 例如 EC2, RDS, Lambda 有一些原生的 Metrics. 对这些原生 Metrics 进行监控是非常常见的需求. 所以 AWS 为这些 Metrics 创建了很多 recommended Alarm, 使得用户可以很轻松的创建针对这些 Metrics 的 Alarm. 这篇文档里有详细的使用说明以及所支持的 recommended Alarm 列表 Best practice alarm recommendations for AWS services.

主流的创建 Alarm 的逻辑是根据 Metrics 创建. 继续细分的话, 有下面几种方式:

  1. 根据 static threshold: 也就是这个 metrics 的值只要达到某个值, 就触发 Alarm.

  2. 根据 math express: 也就是对这个 metrics 进行数学计算, 例如求和, 平均, 最大最小值.

  3. 根据 metrics insights query: 也就是相当于你每几秒就 run 一个 metrics query 来计算一个值, 如果这个值达到某种条件就报警. 请注意, metrics query 和 logs insight query 是两个不同的东西.

  4. 根据 anomaly detection: 也就是统计中的 outlier, 你可以自己指定 N 倍的标准差, 如果 metrics 超过了这个范围就报警.

Combining Alarms#

这是一个非常有用的功能. 你可以将多个 Alarms 整合成一个 Composite Alarm. 当任意一个 Alarm 变成 InAlarm 的时候这个 Composite Alarm 就视为 InAlarm. 当所有的 Alarm 都变成了 OK 时它才会变为 OK.

Reference:

Alarm Actions#

在以下三种情况下 Alarm 会触发一个 Action.

  1. 当 Alarm 超过了指定的 Threshold 的时候. 这也是最常见的, 也就是普通意义上的报警.

  2. 当 Alarm 回到了指定的 Threshold 以下时, 也就是警报解除.

  3. 当出现了 Insufficient Data 的时候, 也就是数据不足. 这时候 Alarm 变得不可靠, 可能需要人工介入.

Alarm 的 Actions 有很多种. 最常用的, 也是扩展性最好的是发送一个 Notification 给 SNS. 这样你可以用 Lambda 来 Subscribe SNS 从而实现任何复杂的 Action 逻辑. 除此之外, 它还原生支持以下 Actions:

  1. Auto Scaling action: 自动 scale in / out

  2. EC2 Action: 关机或开机

  3. System Manager action:

注意, 这里的触发是一次性的, 也就是仅仅在 Transition 发生的时候会触发. 但如果它一直停留在 InAlarm 的状态下, 它是不会一直执行 Action 的. 如果你想要实现重复不断地执行 Action, 你可以用 Event Bridge 捕获 Alarm State Transition 的 event, 然后触发一个 StepFunction 来每隔一段时间检查一次 Alarm 的状态, 如果还是 InAlarm 的状态, 就执行 Action. 当然你也可以实现 exponential backoff 的机制. 这里有一篇博文详细介绍了这一方法 How to enable Amazon CloudWatch Alarms to send repeated notifications.

Example#

本节我们会在 AWS 上创建一个真实的 Alarm, 并且在警报响起的时候给自己发邮件.

第一步, 配置我们要用的 AWS Account 以及 log group,

 1# -*- coding: utf-8 -*-
 2
 3from boto_session_manager import BotoSesManager
 4from aws_console_url.api import AWSConsole
 5
 6bsm = BotoSesManager(profile_name="awshsh_app_dev_us_east_1")
 7aws = AWSConsole(aws_account_id=bsm.aws_account_id, aws_region=bsm.aws_region, bsm=bsm)
 8logs_client = bsm.cloudwatchlogs_client
 9
10group_name = "learn_aws_cloudwatch/use-alarms"
11stream_name_1 = "container-1"
12stream_name_2 = "container-2"

然后我们运行一个 data faker 不断地把 log 打到 log group.

  1# -*- coding: utf-8 -*-
  2
  3import typing as T
  4import time
  5import random
  6import dataclasses
  7
  8from recipe import (
  9    create_log_group,
 10    delete_log_group,
 11    create_log_stream,
 12    Event,
 13    BaseJsonMessage,
 14    put_log_events,
 15)
 16from shared import bsm, logs_client, aws, group_name, stream_name_1, stream_name_2
 17
 18
 19def set_up():
 20    """
 21    Set up cloudwatch logs resource for this example.
 22    """
 23    create_log_group(logs_client, group_name)
 24    create_log_stream(logs_client, group_name, stream_name_1)
 25    create_log_stream(logs_client, group_name, stream_name_2)
 26    print(aws.cloudwatch.get_log_group(group_name))
 27
 28
 29@dataclasses.dataclass
 30class StatusMessage(BaseJsonMessage):
 31    server_id: str = dataclasses.field()
 32    status: str = dataclasses.field()
 33
 34
 35@dataclasses.dataclass
 36class ProcessingTimeMessage(BaseJsonMessage):
 37    server_id: str = dataclasses.field()
 38    processing_time: int = dataclasses.field()
 39
 40
 41server_id_list = [stream_name_1, stream_name_2]
 42
 43
 44def rand_event() -> T.List[T.Union[ProcessingTimeMessage, StatusMessage]]:
 45    """
 46    70% chance it succeeds, 30% chance it fails. When succeeded, it will generate
 47    two messages, one for status and one for processing time. When failed, it will
 48    generate one failed message for status.
 49    """
 50    server_id = random.choice(server_id_list)
 51    stream_name = server_id
 52    if random.randint(1, 100) <= 70:
 53        messages = [
 54            StatusMessage(
 55                server_id=server_id,
 56                status="succeeded",
 57            ),
 58            ProcessingTimeMessage(
 59                server_id=server_id,
 60                processing_time=random.randint(1000, 10000),
 61            ),
 62        ]
 63    else:
 64        messages = [
 65            StatusMessage(
 66                server_id=server_id,
 67                status="failed",
 68            )
 69        ]
 70    put_log_events(
 71        bsm.cloudwatchlogs_client,
 72        group_name,
 73        stream_name,
 74        events=[Event(message=message.to_json()) for message in messages],
 75    )
 76    return messages
 77
 78
 79def run_data_faker():
 80    """
 81    Run :func:`rand_event` every 1 second.
 82
 83    Ref: https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/cloudwatch_limits_cwl.html
 84
 85    The maximum batch size of a PutLogEvents request is 1MB.
 86
 87    **800** transactions per second per account per Region, except for the following Regions where the quota is 1500 transactions per second per account per Region: US East (N. Virginia), US West (Oregon), and Europe (Ireland). You can request an increase to the per-second throttling quota by using the Service Quotas service.
 88    """
 89    ith = 0
 90    while True:
 91        ith += 1
 92        print(f"ith: {ith} sec")
 93        time.sleep(1)
 94        messages = rand_event()
 95        for message in messages:
 96            print(f"  {message}")
 97
 98
 99def clean_up():
100    """
101    Clearn up cloudwatch logs resource for this example.
102    """
103    delete_log_group(logs_client, group_name)
104
105
106if __name__ == "__main__":
107    set_up()
108    run_data_faker()
109    # clean_up()

然后我们用 metrics filter 创建一个 Metrics

 1# -*- coding: utf-8 -*-
 2
 3"""
 4This script creates a custom metrics based on the log event data.
 5"""
 6
 7from shared import logs_client, group_name
 8
 9logs_client.put_metric_filter(
10    logGroupName=group_name,
11    filterName="ProcessingTime",
12    filterPattern="{ $.processing_time = \"*\" }",
13    metricTransformations=[
14        {
15            "metricName": "AverageProcessingTime",
16            "metricNamespace": "LearnCloudWatchLogs",
17            "metricValue": "$.processing_time",
18            "defaultValue": 0,
19        },
20    ],
21)

接下来我们手动进入 AWS Console, 找到我们的 metrics, 选中这个 metrics, 然后点击 Create Alarm. 接下来填入以下信息:

  • Specify metric and conditions:
    • Statistic = Average

    • Period = 1 minute

    • Threshold type = Static

    • Whenever AverageProcessingTime is… = Greater

    • than… = 1000

    • Next

  • Configure actions:
    • Notification
      • Alarm state trigger = In alarm

      • Create new topic, 并填写 topic 名字 (默认的就好) 以及你的 email. 之后你会收到一封确认 subscription 的邮件.

由于我们的设定是 AverageProcessingTime 大约是 5500 左右, 所以一分钟后你就会收到警报邮件.

当然, 我们可以用 Lambda Function 来实现任何复杂的逻辑.