当前位置: 首页 > news >正文

从 MySQL 到 CloudWatch:一个运维事故后搭建的零人工告警系统

上周五晚上 11 点,手机响了——线上服务 CPU 飙到 95%,用户开始投诉卡顿。

等我打开电脑登上服务器,已经过去 15 分钟了。手动扩了一台实例,又花了 5 分钟。整个故障影响了将近 20 分钟。

后来我花了一个下午搭了一套 CloudWatch 告警 + 自动响应系统。现在 CPU 超过 80% 自动扩容,账单超预算自动 Slack 通知。零人工干预,睡觉都踏实了。

这篇记录下具体怎么搞的。

先搞清楚 CloudWatch 的基本套路

CloudWatch 是亚马逊云科技的监控服务,核心就三件事:

  1. 收指标(Metrics)— EC2 的 CPU、内存、磁盘;Lambda 的调用次数、错误率;RDS 的连接数
  2. 设告警(Alarms)— 指标超阈值就触发
  3. 做动作(Actions)— 触发后干嘛:发通知、自动扩容、跑 Lambda

画个图就是这样:

指标超阈值 → CloudWatch Alarm → SNS Topic → Lambda/Email/Slack↓Auto Scaling Action

场景一:CPU 超 80% 自动扩容

前提

假设你有一个 Auto Scaling Group(ASG),里面跑着 2-6 台 EC2 实例。

第一步:创建扩容策略

import boto3autoscaling = boto3.client('autoscaling', region_name='ap-northeast-1')# 创建扩容策略:加 1 台实例
response = autoscaling.put_scaling_policy(AutoScalingGroupName='my-web-asg',PolicyName='scale-out-on-high-cpu',PolicyType='SimpleScaling',AdjustmentType='ChangeInCapacity',ScalingAdjustment=1,Cooldown=300  # 扩容后 5 分钟内不重复扩
)scale_out_arn = response['PolicyARN']
print(f'扩容策略 ARN: {scale_out_arn}')

第二步:创建 CloudWatch 告警

cloudwatch = boto3.client('cloudwatch', region_name='ap-northeast-1')cloudwatch.put_metric_alarm(AlarmName='high-cpu-alarm',AlarmDescription='CPU 超过 80% 持续 3 分钟,自动扩容',MetricName='CPUUtilization',Namespace='AWS/EC2',Statistic='Average',Period=60,           # 每 60 秒采样一次EvaluationPeriods=3, # 连续 3 个周期都超阈值才触发Threshold=80.0,ComparisonOperator='GreaterThanThreshold',Dimensions=[{'Name': 'AutoScalingGroupName','Value': 'my-web-asg'}],AlarmActions=[scale_out_arn],  # 触发扩容策略Unit='Percent'
)print('告警创建完成')

关键参数解释:

  • Period=60 + EvaluationPeriods=3:连续 3 分钟 CPU > 80% 才触发。避免瞬时毛刺误触发。
  • Cooldown=300:扩容后 5 分钟内不会再次扩容。新实例需要时间启动和加入负载均衡。

缩容也别忘了

流量下来后要自动缩容,不然一直跑着白花钱:

# 缩容策略
response = autoscaling.put_scaling_policy(AutoScalingGroupName='my-web-asg',PolicyName='scale-in-on-low-cpu',PolicyType='SimpleScaling',AdjustmentType='ChangeInCapacity',ScalingAdjustment=-1,Cooldown=300
)scale_in_arn = response['PolicyARN']# CPU 低于 30% 持续 10 分钟,缩容
cloudwatch.put_metric_alarm(AlarmName='low-cpu-alarm',AlarmDescription='CPU 低于 30% 持续 10 分钟,自动缩容',MetricName='CPUUtilization',Namespace='AWS/EC2',Statistic='Average',Period=60,EvaluationPeriods=10,Threshold=30.0,ComparisonOperator='LessThanThreshold',Dimensions=[{'Name': 'AutoScalingGroupName','Value': 'my-web-asg'}],AlarmActions=[scale_in_arn],Unit='Percent'
)

缩容的判定条件比扩容松——10 分钟而不是 3 分钟。因为缩容比扩容风险大,误缩容会导致服务抖动。

场景二:账单超预算 Slack 通知

这个更实用。每个月云上花了多少钱,超预算了马上通知。

第一步:创建 SNS 主题

sns = boto3.client('sns', region_name='us-east-1')  # 账单指标只在 us-east-1# 创建通知主题
topic = sns.create_topic(Name='billing-alerts')
topic_arn = topic['TopicArn']# 订阅邮件(先用邮件测试,后面换 Slack)
sns.subscribe(TopicArn=topic_arn,Protocol='email',Endpoint='your-email@example.com'
)print(f'Topic ARN: {topic_arn}')
# 注意:邮件订阅需要去邮箱点确认链接

第二步:设置账单告警

cloudwatch_billing = boto3.client('cloudwatch', region_name='us-east-1')cloudwatch_billing.put_metric_alarm(AlarmName='monthly-bill-over-100',AlarmDescription='当月账单超过 $100 告警',MetricName='EstimatedCharges',Namespace='AWS/Billing',Statistic='Maximum',Period=21600,          # 6 小时检查一次EvaluationPeriods=1,Threshold=100.0,ComparisonOperator='GreaterThanThreshold',Dimensions=[{'Name': 'Currency','Value': 'USD'}],AlarmActions=[topic_arn],Unit='None'
)print('账单告警设置完成')

踩坑提醒:

  • 账单指标只存在 us-east-1 区域,不管你的资源在哪个区。
  • 需要先在控制台开启账单告警:Billing → Billing Preferences → Receive Billing Alerts。
  • EstimatedCharges 是当月累计预估值,不是每天的增量。

第三步:接入 Slack

邮件通知太容易漏看了。接 Slack 更实时。

用一个 Lambda 函数做转发:

# lambda_function.py — 部署到 Lambda
import json
import urllib.requestSLACK_WEBHOOK_URL = 'https://hooks.slack.com/services/YOUR/WEBHOOK/URL'def lambda_handler(event, context):# 从 SNS 消息中提取告警信息message = event['Records'][0]['Sns']['Message']# 解析 CloudWatch 告警详情try:alarm = json.loads(message)alarm_name = alarm.get('AlarmName', 'Unknown')new_state = alarm.get('NewStateValue', 'Unknown')reason = alarm.get('NewStateReason', '')text = f" *CloudWatch Alert*\n"text += f"*告警名称:* {alarm_name}\n"text += f"*状态:* {new_state}\n"text += f"*原因:* {reason[:200]}"except:text = f"⚠️ CloudWatch 告警:\n{message[:500]}"# 发送到 Slackpayload = json.dumps({'text': text}).encode('utf-8')req = urllib.request.Request(SLACK_WEBHOOK_URL,data=payload,headers={'Content-Type': 'application/json'})urllib.request.urlopen(req)return {'statusCode': 200}

然后让 SNS 触发这个 Lambda:

# 订阅 Lambda 到 SNS 主题
sns.subscribe(TopicArn=topic_arn,Protocol='lambda',Endpoint='arn:aws:lambda:us-east-1:123456789012:function:slack-notifier'
)

场景三:自定义指标监控

CloudWatch 不只能监控亚马逊云科技自带的指标。你的业务指标也能推上去。

比如监控 API 响应时间:

cloudwatch.put_metric_data(Namespace='MyApp/API',MetricData=[{'MetricName': 'ResponseTime','Value': 235.5,'Unit': 'Milliseconds','Dimensions': [{'Name': 'Endpoint','Value': '/api/v1/users'}]}]
)

在你的 API 代码里每次请求结束后推一个数据点,然后对这个自定义指标设告警:

cloudwatch.put_metric_alarm(AlarmName='api-slow-response',AlarmDescription='API 响应时间超过 1 秒',MetricName='ResponseTime',Namespace='MyApp/API',Statistic='Average',Period=60,EvaluationPeriods=5,Threshold=1000.0,ComparisonOperator='GreaterThanThreshold',Dimensions=[{'Name': 'Endpoint','Value': '/api/v1/users'}],AlarmActions=[topic_arn],Unit='Milliseconds'
)

成本

CloudWatch 的定价其实挺便宜:

项目 免费额度 超出价格
基础监控(5 分钟间隔) 所有 EC2 免费
详细监控(1 分钟间隔) $0.30/实例/月
告警 前 10 个免费 $0.10/告警/月
自定义指标 $0.30/指标/月
API 调用 前 100 万次免费 $0.01/千次

一般中小项目,10 个告警 + 几个自定义指标,每月不到 $5。

我的告警清单

跑了一段时间后,总结出这些告警是必须有的:

告警 阈值 为什么
CPU > 80% 连续 3 分钟 扩容信号
CPU < 30% 连续 10 分钟 缩容省钱
磁盘 > 85% 连续 5 分钟 磁盘满了服务会挂
内存 > 90% 连续 5 分钟 OOM 前告警
5xx 错误 > 1% 连续 2 分钟 服务异常
API P99 > 2s 连续 5 分钟 用户体验恶化
月账单 > 预算 80% 6 小时检查 避免月底惊喜
Lambda 错误率 > 5% 连续 3 分钟 函数异常

踩坑总结

  1. 账单指标只在 us-east-1 — 这个坑很多人都踩过,在其他区域创建账单告警会发现没有指标数据。
  2. 告警状态有三种 — OK、ALARM、INSUFFICIENT_DATA。新建的告警初始状态是 INSUFFICIENT_DATA,不代表有问题。
  3. 内存指标需要 CloudWatch Agent — EC2 默认不推送内存使用率,需要安装 CloudWatch Agent。
  4. 告警名称全局唯一 — 同一个账号同一个区域内不能重名。建议用 {环境}-{服务}-{指标} 的命名规范。
  5. SNS 邮件订阅要确认 — 创建订阅后必须去邮箱点确认链接,否则收不到通知。

以上代码基于亚马逊云科技 CloudWatch + Auto Scaling + SNS + Lambda,Python boto3 验证通过。


CloudWatch 免费套餐包含 10 个告警、100 万次 API 调用和所有 EC2 基础监控(5 分钟间隔),入门阶段完全够用。
CloudWatch 文档:https://docs.aws.amazon.com/cloudwatch/

http://www.jsqmd.com/news/521743/

相关文章:

  • 【ROS】利用moveit控制自制机械臂(0)
  • Arduino 24LC64F EEPROM 驱动库:字节级擦写与I²C高可靠实现
  • DEVOPS-WORLD完整指南:从零到精通DevOps的终极学习路径
  • 环境配置——python代码打包超详细教程
  • AlphaFold更上一层楼
  • 阿里二面:什么是 MySQL 回表查询?如何避免?(修订版)
  • Rerank效果差?Dify 0.7+版本重排序失效全排查,87%团队忽略的3个元数据埋点
  • 雷诺运输定理的三种特殊形式及其在物理建模中的应用
  • 南方电网电费监控完整指南:5分钟实现Home Assistant智能集成
  • 嵌入式按键消抖库DebounceIn:轻量、确定性、零堆内存
  • Step3-VL-10B与Java企业级开发:SpringBoot智能客服集成指南
  • mosdns序列执行器深度解析:构建复杂DNS处理流程
  • 三菱E800变频器CC-Link IE Basic网络通讯配置全解析
  • GLM-4.7-Flash保姆级部署教程:从下载到运行,每一步都详细讲解
  • 避开这些坑!Calico v3.27.0生产环境部署实操记录(含Operator排错技巧)
  • CosyVoice3快速部署指南:一键运行,开启你的语音克隆之旅
  • 科研学习|研究方法——扎根理论三阶段编码如何做?
  • 如何快速掌握Octant:Kubernetes集群状态监控的终极指南
  • 保姆级教程:用Docker快速部署QQ-GPT机器人(基于Napcat和NoneBot)
  • BLE简介、体系结构与核心概念
  • Aria2 完美配置自动化部署:Docker 与一键脚本的完整教程
  • HY-Motion 1.0实战手册:支持中文提示词转义的本地化Prompt工程方案
  • 新手必看:QWEN-AUDIO超简单部署教程,轻松生成带情绪的语音
  • 科研学习|研究方法——定性数据的定量编码方法
  • GD32实战:FlashDB在片外Flash的移植与关键配置详解
  • 如何在《英雄联盟》《无畏契约》中实现完美隐身:Deceive工具终极指南
  • Superagent终极指南:如何通过API快速构建AI智能体应用
  • 终极指南:如何为JavaScript NES模拟器添加TypeScript类型安全
  • ESP32-C3硬件定时器中断库:1个物理定时器虚拟化16个ISR定时器
  • 高效AE转JSON完整指南:从动画设计到数据应用的全流程解析