亚马逊AWS官方博客

使用Amazon Step Functions实现Amazon CloudWatch持续报警

背景介绍

监控与报警系统对企业的生产环境来说是不可或缺的,完善的监控与及时的报警通知可以帮助企业快速定位并解决问题,从而减少经济损失。

Amazon CloudWatch是亚马逊云上的统一监控平台,实现对云上所有资源的监控。Amazon CloudWatch Alarm可以基于Amazon CloudWatch Metrics里面的指标,根据您自定义的规则触发警报。通过集成Amazon SNSAmazon Lambda,您可以实现邮件、短信、企业微信、钉钉、飞书等多种方式的通知。

但是,Amazon CloudWatch Alarm仅会在警报从【正常】状态转变为【告警】状态时触发一次警报。此后,即使警报仍然处于【告警】状态,也不会有新的通知产生。

本文将介绍,如何基于无服务器函数编排工具Amazon Step Functions,在警报被触发后轮询警报状态,实现持续报警的效果,以强调警报的存在,确保您系统中的问题可以得到足够的重视。

 

架构图

本方案包含如下组件:

  • 您可以根据您的需要,创建Amazon CloudWatch Alarm。
  • Amazon CloudWatch Alarm会通过Amazon EventBridge,触发Amazon Step Functions工作流
  • Amazon Step Functions工作流的具体内容见下文。工作流中会包含以下两个函数:
    • HandleAlarm:您自定义的警报处理函数,用来实现通知逻辑。
    • CheckAlarm:下文会指导您创建的警报状态检查函数,用来判断是否继续发出通知。

 

配置步骤

创建Lambda函数以检查警报状态

创建一个Lambda函数,命名为CheckAlarm,使用Amazon SDK检测警报状态。比如,如果使用Python 3.8作为runtime创建此函数,可以参考如下代码:

import boto3

cloudwatch = boto3.client('cloudwatch')

def lambda_handler(event, context):
  print(f'event={event}')
  try:
    # check alarm state
    res = cloudwatch.describe_alarms(AlarmNames=[event['alarmName']])
    print(f'res={res}')
    if 'MetricAlarms' in res:
      return res['MetricAlarms'][0][
          'StateValue']  # 'OK'|'ALARM'|'INSUFFICIENT_DATA'
    elif 'CompositeAlarms' in res:
      return res['CompositeAlarms'][0][
          'StateValue']  # 'OK'|'ALARM'|'INSUFFICIENT_DATA'
    else:
      print('no alarms found')
      return 'ERROR'
  except Exception as e:
    print(e)
    return 'ERROR'

此函数需要调用cloudwatch:DescribeAlarms API,所以需要为此函数所使用的IAM Role添加此权限。

此函数会自动从event 参数中解析出警报的名称。最终返回值为OK/ALARM/INSUFFICIENT_DATA/ERROR中的一个。

创建Amazon Step Functions

使用如下内容创建Step Functions工作流:

{
  "Comment": "Handle Alarm Periodically",
  "StartAt": "In Alarm?",
  "States": {
    "In Alarm?": {
      "Type": "Choice",
      "Choices": [
        {
          "Variable": "$.detail.state.value",
          "StringEquals": "ALARM",
          "Next": "HandleAlarm"
        }
      ],
      "Default": "Pass"
    },
    "HandleAlarm": {
      "Type": "Task",
      "Resource": "arn:aws:states:::lambda:invoke",
      "OutputPath": "$.Payload",
      "Parameters": {
        "FunctionName": "<AlarmHandler>",
        "Payload.$": "$"
      },
      "Retry": [
        {
          "ErrorEquals": [
            "Lambda.ServiceException",
            "Lambda.AWSLambdaException",
            "Lambda.SdkClientException"
          ],
          "IntervalSeconds": 2,
          "MaxAttempts": 6,
          "BackoffRate": 2
        }
      ],
      "Next": "Wait",
      "InputPath": "$$.Execution.Input"
    },
    "Wait": {
      "Type": "Wait",
      "Seconds": 300,
      "Next": "CheckAlarm"
    },
    "CheckAlarm": {
      "Type": "Task",
      "Resource": "arn:aws:states:::lambda:invoke",
      "OutputPath": "$.Payload",
      "Parameters": {
        "FunctionName": "<CheckAlarm>",
        "Payload.$": "$"
      },
      "Retry": [
        {
          "ErrorEquals": [
            "Lambda.ServiceException",
            "Lambda.AWSLambdaException",
            "Lambda.SdkClientException"
          ],
          "IntervalSeconds": 2,
          "MaxAttempts": 6,
          "BackoffRate": 2
        }
      ],
      "Next": "Still In Alarm?",
      "InputPath": "$$.Execution.Input.detail"
    },
    "Still In Alarm?": {
      "Type": "Choice",
      "Choices": [
        {
          "Variable": "$",
          "StringEquals": "ALARM",
          "Next": "HandleAlarm"
        },
        {
          "Variable": "$",
          "StringEquals": "OK",
          "Next": "Pass"
        }
      ],
      "Default": "Fail"
    },
    "Pass": {
      "Type": "Pass",
      "End": true
    },
    "Fail": {
      "Type": "Fail"
    }
  }
}

您需要把上述文档中的如下内容进行替换:

  • <AlarmHandler>: 响应警报的Lambda函数的ARN。您需要自行创建此函数,并在里面实现您的通知/处理逻辑。
  • <CheckAlarm>: 刚才创建的CheckAlarm函数的ARN。

如果使用此文档创建Step Functions,那么轮询周期为5分钟(300秒)。您可以通过修改文档中的下述内容来修改轮询周期:

"Wait": {
      "Type": "Wait",
      "Seconds": 300, // 修改这个值,单位为秒
      "Next": "CheckAlarm"
},

上述文档会创建如下图所示的状态机:

当警报状态改变,触发此状态机时,会顺序执行下述操作:

  1. 警报的状态是否是ALARM。如果不是,则跳转到Pass状态并结束状态机
  2. 调用HandleAlarm函数,根据您自定义的逻辑发出通知
  3. 进入Wait状态,等待一段时间
  4. 调用CheckAlarm函数,判断警报状态
    1. 如果函数返回OK,即警报已经恢复正常,则进入Pass 状态并结束状态机
    2. 如果函数返回ALARM,即警报仍然在告警,则回到步骤1
    3. 如果函数返回其他值,视为故障并进入Fail状态,结束状态机

此状态机的两个Lambda函数都在InputPath 中引用了$$.Execution.Input 变量,使它们即使被多次触发,或者在循环中被触发,也会有一致的输入值。

配置CloudWatch警报

根据您的需求,创建CloudWatch警报。此方案会使用EventBridge触发Step Functions,所以它在创建的时候不需要配置任何通知。删除所有的通知,并直接点击Next.

创建完毕后,复制EventBridge规则

创建EventBridge规则,目标为刚才创建的Step Functions状态机:

至此,每当警报状态发生改变的时候,状态机就会被触发。如果警报的状态为【报警中】,状态机就会发出持续通知。

 

结束语

综上所述,您只需要准备好您自定义的警报处理函数HandleAlarm ,并将其集成到上述系统中,即可实现CloudWatch持续报警,提升您的报警触达率,加速问题的解决。

 

 

本篇作者

王景辉

Amazon Web Services解决方案架构师,负责基于 Amazon Web Services 的云计算方案的咨询与架构设计,同时致力于 Amazon Web Services 云服务知识体系的传播与普及。目前关注游戏行业。