企业微信接收 Amazon CloudWatch 告警
AWS CloudWatch 默认只支持邮件告警,时效性比较低。本方案是使用 CloudWatch 如何集成第三方如微信、钉钉、等支持API操作的即时聊天软件,以下以微信告警为例。
在这篇文章中,我将会介绍如何通过 Amazon SNS 和 AWS Lambda 来实现将 AWS CloudWatch 告警信息发送到微信。
方案获取
方案架构
本方案中 CloudWatch 接收 EC2 运行指标并进行监控。当 EC2 指标超出设定阈值后,CloudWatch 触发告警事件,并将事件消息通过 SNS 发送到 Lambda 函数。Lambda函数执行用户自定义的代码,包括:解析告警消息并发送到企业微信、钉钉机器人、或者 Prometheus 等平台。
架构中 AWS 服务简介:
Amazon CloudWatch 介绍
Amazon CloudWatch 可以用来收集 AWS 包括 EC2 、 RDS 、 EKS 等各种服务运行日志也可以收集用户应用程序日志,可以作为 AWS 上日志数据集散地。同时在 CloudWatch 上可以设置基于指标的告警、基于时间和事件的规则,它可以与 Amazon SNS 和 Lambda 等其他AWS服务进行使用。
AWS Lambda 介绍
AWS Lambda 是无服务器的函数计算服务。通过 AWS Lambda ,无需预置或管理服务器即可运行代码。借助 Lambda ,您几乎可以为任何类型的应用程序或后端服务运行代码,而且完全无需管理。您可以将您的代码设置为自动从其他 AWS 服务如: CloudWatch 、 SNS 等触发,或者直接从任何 Web 或移动应用程序调用。
Amazon SNS 介绍
Amazon SNS 是一项用于应用与应用之间以及应用与人之间通信的完全托管型消息收发服务。
使用企业微信接收 CloudWatch 告警
企业微信设置
-
注册企业微信账号
- 在企业微信中创建应用
- 创建完成后记录应用 Secret
- 企业微信告警流程
获取 access_token ,参考微信官方说明文档
示例代码:
tokenUrl = "https://qyapi.weixin.qq.com/cgi-bin/gettoken"
def get_token():
values = {'corpid': ''<corpid>'', 'corpsecret': '<'corpsecret'>'}
req = requests.post(tokenUrl, params=values)
data = json.loads(req.text)
return data["access_token"]
缓存和刷新 access_token,然后调用具体的业务接口,示例代码:
sendMsg = "https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token="
def send_msg(msg):
url = sendMsg + get_token()
print(url)
values = """{"touser" : "<成员ID列表>" ,
"msgtype":"text",
"agentid":"<企业应用ID>",
"text":{
"content": "发送的告警内容!"
},
"safe":"0"
}"""
requests.post(url, values)
创建 Amazon SNS
创建主题
创建订阅
这里我创建两种订阅,一种基于邮件(发送邮箱链接确认),一种基于Lambda。
(1)基于邮件
邮箱链接确认:
(2)基于Lambda
- 创建AWS Lambda 函数
- 配置 Lambda 环境变量(可选)
- 新建 corpid 和 corpsecret 环境变量,根据从企业微信获取的值填充(可选)
- 创建 Lambda 协议的订阅
为Lambda添加触发器:选择上一步创建的SNS作为触发器
创建 Lambda 函数
创建lambda_function.py
import requests
import json
import os
#corpid = os.getenv('corpid')
#corpsecret = os.getenv('corpsecret')
#agentid = os.getenv('agentid')
def lambda_handler(event, context):
# TODO implement
url = "https://qyapi.weixin.qq.com"
# 设置企业微信参数
corpid = "xxxxxxxxxxxxxx"
secret = "xxxxxxxxxxxxxxxxxx"
#agentid = 1000002
#touser = "@all"
#toparty = ''
#totag = ''
headers = {
'Content-Type': 'application/json'
}
access_token_url = '{url}/cgi-bin/gettoken?corpid={id}&corpsecret={crt}'.format(url=url, id=corpid, crt=secret)
access_token_response = requests.get(url=access_token_url, headers=headers)
token = json.loads(access_token_response.text)['access_token']
send_url = '{url}/cgi-bin/message/send?access_token={token}'.format(url=url, token=token)
message = event['Records'][0]['Sns']
Timestamp = message['Timestamp']
Subject = message['Subject']
sns_message = json.loads(message['Message'])
region = message['TopicArn'].split(':')[-3]
NewStateReason = json.loads(event['Records'][0]['Sns']['Message'])['NewStateReason']
if "ALARM" in Subject:
title = '<font color=\"info\">[aws] 警报!!警报!!</font>'
elif "OK" in Subject:
title = '<font color=\"info\">[aws] 故障恢复</font>'
else:
title = '<font color=\"info\">[aws]</font>'
content = title \
+ "\n> **详情信息**" \
+ "\n> **时间**: " + Timestamp \
+ "\n> **内容**: " + Subject \
+ "\n> **状态**: <font color=\"comment\">{old}</font> => <font color=\"warning\">{new}</font>".format(
old=sns_message['OldStateValue'], new=sns_message['NewStateValue']) \
+ "\n> " \
+ "\n> **AWS区域**: " + sns_message['Region'] \
+ "\n> **监控资源对象**: " + sns_message['Trigger']['Namespace'] \
+ "\n> **监控指标**: " + sns_message['Trigger']['MetricName'] \
+ "\n> " \
+ "\n> **报警名称**: " + sns_message['AlarmName'] \
+ "\n> **报警创建方式**: " + sns_message['AlarmDescription'] \
+ "\n> " \
+ "\n> **报警细节**: " + NewStateReason
msg = {
"touser" : "@all" ,
"msgtype": 'markdown',
"agentid": 1000xxx,
"markdown": {'content': content},
"safe": 0
}
#if touser:
# msg['touser'] = touser
#if toparty:
# msg['toparty'] = toparty
#if toparty:
# msg['totag'] = totag
response = requests.post(url=send_url, data=json.dumps(msg), headers=headers)
errcode = json.loads(response.text)['errcode']
if errcode == 0:
print('Succesfully')
else:
print(response.json())
print('Failed')
corpid: 即你的企业微信 ID。
agentid: 即在你的企业微信中向用户发送消息的应用的 ID。
corpsecret: 即该应用的密钥。你可以在企业微信的应用管理中获取这两个参数。
requests 库发送 post 请求
本例中使用 python requests 库发送 post 请求,目前有两种办法实现:
-
直接使用 vendored 中的 requests ,但该方法2021年1月30日以后将不能使用。故不做推荐。
- 独立安装 requests 库,需要将包和 lambda 代码一起上传到 AWS Lambda 执行环境。以使用虚拟环境来安装函数依赖为例。
具体步骤如下:
# 创建虚拟环境
mkvirtualenv alarm
# 进入虚拟环境
workon alarm
# 使用 pip 安装库
pip install requests
# 停用虚拟环境
deactivate
# 使用库内容创建一个 ZIP 存档
cd v-env/lib/python3.7/site-packages
# 将函数代码添加到存档中
zip -r9 ${OLDPWD}/function.zip .
cd $OLDPWD
zip -g function.zip lambda_function.py
关于虚拟环境使用和基本操作,详见:
将二进制 ZIP 部署包上传到Lambda 并更新函数代码
创建EC2 CloudWatch告警并与SNS关联
- 选择要监控的 EC2 监控选项卡,创建警报。(下图是中国区控制台界面, AWS Global 控制台界面略有不同)
- 选择前面步骤创建的 SNS 主题,并选择合适的监控告警指标
- 测试告警接收
如上图所示, CPU 利用率已经超过设定阈值, CloudWatch 发出告警。
- 在 CloudWatch 日志组中查看 Lambda 日志
- 收到告警邮件
- 收到微信报警
[aws] 警报!!警报!!
详情信息
时间: 2021-03-13T14:00:33.486Z
内容: ALARM: "AWS-AI_Voice_Test" in Asia xxxxx (xxxxx)
状态: INSUFFICIENT_DATA => ALARM
AWS区域: Asia xxxxx (xxxxx)
监控资源对象: AWS/EC2
监控指标: NetworkOut
报警名称: AWS-AI_Voice_Test
报警创建方式: 从 EC2 控制台创建
报警细节: Threshold Crossed: 1 datapoint [3324909.2 (13/03/21 13:55:00)] was greater than or equal to the threshold (3000000.0).