被线上故障电话叫醒后,我花一下午搭了套零人工告警系统

20 阅读3分钟

周五晚上 11 点接到电话:CPU 95%,用户投诉卡顿。

开电脑、登服务器、手动扩容——前后折腾 20 分钟。事后想想,这种事完全可以自动化。

第二天花了一个下午,用 CloudWatch 搞定了:CPU 飙高自动扩容,账单超了自动 Slack 通知。从此再没被半夜电话叫醒过。

CloudWatch 干嘛的?一句话说清楚

三件事:收指标 → 设告警 → 做动作

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

EC2 的 CPU、Lambda 的错误率、RDS 的连接数,都是指标。指标超了设定值,就触发告警。告警触发后可以发通知、自动扩容、跑 Lambda——想干嘛干嘛。

场景一:CPU 飙了自动扩容

前提:你有一个 Auto Scaling Group(ASG),跑着 2-6 台 EC2。

创建扩容策略

import boto3

autoscaling = boto3.client('autoscaling', region_name='ap-northeast-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,
    EvaluationPeriods=3,
    Threshold=80.0,
    ComparisonOperator='GreaterThanThreshold',
    Dimensions=[
        {
            'Name': 'AutoScalingGroupName',
            'Value': 'my-web-asg'
        }
    ],
    AlarmActions=[scale_out_arn],
    Unit='Percent'
)

为什么是连续 3 分钟?因为瞬时毛刺很常见——数据库慢查询、GC 暂停都可能短暂拉高 CPU。连续 3 分钟才是真的扛不住了。

Cooldown=300 也很关键:新实例从启动到进入负载均衡至少要 2-3 分钟,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']

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 通知

这个我觉得比 CPU 告警还实用。谁没被月底账单吓过一跳?

SNS 主题 + 邮件订阅

sns = boto3.client('sns', region_name='us-east-1')  # 账单指标只在 us-east-1!

topic = sns.create_topic(Name='billing-alerts')
topic_arn = topic['TopicArn']

sns.subscribe(
    TopicArn=topic_arn,
    Protocol='email',
    Endpoint='your-email@example.com'
)
# 注意去邮箱点确认

账单告警

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'
)

坑:

  • 账单指标只在 us-east-1,不管你资源在哪个 Region
  • 要先去控制台开:Billing → Billing Preferences → Receive Billing Alerts
  • EstimatedCharges 是当月累计值,不是每天增量

接 Slack

邮件太容易漏。用个 Lambda 转发到 Slack:

# lambda_function.py
import json
import urllib.request

SLACK_WEBHOOK_URL = 'https://hooks.slack.com/services/YOUR/WEBHOOK/URL'

def lambda_handler(event, context):
    message = event['Records'][0]['Sns']['Message']
    
    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]}"
    
    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:

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'
                }
            ]
        }
    ]
)

每次请求结束推一个数据点,然后设告警——超过 1 秒报警:

cloudwatch.put_metric_alarm(
    AlarmName='api-slow-response',
    AlarmDescription='API 响应 > 1s',
    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'
)

花多少钱?

项目免费额度超出价格
基础监控(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 分钟服务异常
P99 > 2s连续 5 分钟用户体验差
月账单 > 80%预算6 小时查防月底惊喜
Lambda 错误 > 5%连续 3 分钟函数异常

5 个坑

  1. 账单指标只在 us-east-1 — 很多人在别的 Region 建账单告警,死活没数据
  2. 三种告警状态 — OK/ALARM/INSUFFICIENT_DATA,新建的初始状态是第三种,别慬
  3. 内存要装 Agent — EC2 默认不推内存指标,需要装 CloudWatch Agent
  4. 告警名全局唯一 — 同账号同区域不能重名,建议 {环境}-{服务}-{指标} 命名
  5. SNS 邮件要确认 — 创建订阅后去邮箱点链接,否则收不到

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


📌 CloudWatch 免费套餐:10 个告警 + 100 万次 API 调用 + EC2 基础监控,入门够用。 🔗 文档:docs.aws.amazon.com/cloudwatch/