从 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 boto3

autoscaling = 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.request

SLACK_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]}"
    
    # 发送到 Slack
    payload = 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/

posted @ 2026-03-23 09:03  亚马逊云开发者  阅读(0)  评论(0)    收藏  举报