在华为云 ECS 上配一套真正有用的告警:CES 指标、阈值与通知落地
一个真实的教训
去年 9 月的一个早上,我那台 2 核 2G 的 ECS 上跑着的几个 Docker 容器全挂了,访问服务全是 502。登上去一看,根分区 100% 满,日志文件把盘撑爆,Docker 连新容器都起不来。
最气人的不是坏了,而是坏了八个小时我才知道。
日志写得再详细也是事后的东西:它记录已经发生的事故,不会告诉我"再过十分钟盘就要满了"。指标是实时的,阈值一越线就有人被叫醒——这才是我真正缺的那块拼图。
那次之后我给这台机器配了云监控告警。这篇写的是这套东西怎么从零落地,以及中间那些文档上不写、只能自己踩出来的坑。
一、先想清楚:告警不是越多越好
装监控很容易,难的是装完之后没人看、看了也不动手,最后所有告警都被静音,监控变成摆设。
我给自己定了三条规矩:
- 一条告警必须能回答"我现在该做什么"。回答不了的,先别加。
- 宁可少而准,不要多而全。起步只配三条:内存、磁盘、CPU。
- 每条告警都要有对应的处置动作,写下来放在手边。告警本身只是通知,动作得是人定的。
二、CES 的三层结构
云监控服务(CES,Cloud Eye)的模型其实只有三层。理解了这三层,剩下的就全是填参数。
第一层:命名空间 + 指标 + 维度。
ECS 的基础监控指标都挂在 SYS.ECS 命名空间下,维度只有一个:instance_id。
第二层:告警规则。
一条规则由四部分组成:作用对象(资源)+ 触发条件(指标 + 聚合方式 + 阈值 + 连续次数)+ 告警级别 + 通知方式。
第三层:通知。
规则本身不发消息,它只负责"判定"。真正把消息送出去的是通知渠道——SMN 主题、消息中心这些。
这三层是解耦的,实践中最常见的翻车就发生在第三层:规则建成功了,却没人收到通知。原因通常是 SMN 主题没订阅端点,或者订阅还在"待确认"状态。
三、第一步:先摸清有哪些指标
不要凭记忆写指标名。CES 有接口可以直接查。
import os
from huaweicloudsdkcore.auth.credentials import BasicCredentials
from huaweicloudsdkcore.region.region import Region
from huaweicloudsdkces.v1.ces_client import CesClient
from huaweicloudsdkces.v1 import ListMetricsRequest
REGION = "cn-east-3"
creds = BasicCredentials(
ak=os.environ["HW_AK"],
sk=os.environ["HW_SK"],
)
# ces / smn 的 SDK 都没有内置区域常量,endpoint 必须自己拼
region = Region(REGION, f"ces.{REGION}.myhuaweicloud.com")
ces = CesClient.new_builder().with_credentials(creds).with_region(region).build()
resp = ces.list_metrics(ListMetricsRequest(namespace="SYS.ECS", limit=100))
for m in resp.metrics:
print(m.metric_name, "|", m.unit, "|", [d.name for d in (m.dimensions or [])])
跑出来的指标清单大致是这样(ECS 基础监控):
| 指标名 | 含义 | 单位 |
cpu_util |
CPU 使用率 | % |
mem_util |
内存使用率 | % |
disk_util_inband |
磁盘使用率 | % |
disk_read_bytes_rate / disk_write_bytes_rate |
磁盘读/写带宽 | byte/s |
disk_read_requests_rate / disk_write_requests_rate |
磁盘读/写 IOPS | Request/s |
network_incoming_bytes_rate_inband / network_outgoing_bytes_rate_inband |
带内网络流入/流出速率 | byte/s |
network_vm_connections |
网络连接数 | Count |
network_vm_bandwidth_in / network_vm_bandwidth_out |
虚拟机整机入/出方向带宽 | Byte/s |
注意命名规律:cpu_util、mem_util 是不带后缀的,磁盘和带内网络这几个则带 inband(另有 aggregate 一套,是从虚拟化层统计的)。指标名写错,规则就永远不会触发——这是配告警时最常见的"配了没反应"。
四、第二步:别拍脑袋定阈值
阈值拍脑袋的结果只有两种:太低了天天告警,太高了出事不告警。
靠谱的做法是先用历史数据算出基线。CES 能拉历史指标数据,拉过去一周,算个 P95,比阈值高一点就行。
import time
from huaweicloudsdkces.v1 import ShowMetricDataRequest
instance_id = "<你的 ECS 实例 ID>"
now = int(time.time())
resp = ces.show_metric_data(ShowMetricDataRequest(
namespace="SYS.ECS",
metric_name="mem_util",
dim_0=instance_id, # v1 里维度值直接用 dim_0 / dim_1 / dim_2 / dim_3
filter="average",
period=3600, # 聚合粒度,单位是秒
_from=now - 7 * 24 * 3600,
to=now,
))
values = sorted(p.average for p in resp.datapoints if p.average is not None)
if values:
p95 = values[int(len(values) * 0.95)]
print("mem_util 基线 P95 =", p95, "%")
几个参数要注意:
dim_0就是维度值,这里填instance_id的取值。v1 用dim_0~dim_3这种扁平参数,最多四个维度;v2 才改成结构化的Dimension列表。period是聚合粒度,单位秒,3600 表示按小时聚合。不是"多少分钟"。_from/to是毫秒级时间戳。参数名带下划线是因为from是 Python 关键字,SDK 只能这么写。- 返回的
datapoints里同时给average/max/min/sum/variance,按你请求的filter取对应字段。
我的经验值:2 核 2G 的小机器,内存基线 P95 大概在 60% 上下,阈值定 85%;磁盘使用率定 80%,因为磁盘一满,很多服务会直接写不进去,比内存耗尽更难恢复。
五、第三步:先把通知通道准备好
告警规则里的 notification_list 填的是 SMN 主题的 URN,形如 smn:cn-east-3:<project_id>:<topic_id>。主题和订阅端点要先在 SMN 里准备好,下一步写规则时才有 URN 可填。
from huaweicloudsdksmn.v2 import (
AddSubscriptionRequest,
AddSubscriptionRequestBody,
CreateTopicRequest,
CreateTopicRequestBody,
)
from huaweicloudsdksmn.v2.smn_client import SmnClient
smn_region = Region(REGION, f"smn.{REGION}.myhuaweicloud.com")
smn = SmnClient.new_builder().with_credentials(creds).with_region(smn_region).build()
topic = smn.create_topic(CreateTopicRequest(body=CreateTopicRequestBody(
name="ecs-alarm",
display_name="ECS 告警",
)))
topic_urn = topic.topic_urn # 这就是 notification_list 要填的东西
smn.add_subscription(AddSubscriptionRequest(
topic_urn=topic_urn,
body=AddSubscriptionRequestBody(
protocol="email",
endpoint="ops@example.com",
remark="值班邮箱",
),
))
两个提醒:
- 加完订阅要去控制台点确认。邮件订阅会先发一封确认信,不点确认,订阅一直是"待确认"状态,告警永远发不出来。
- SMN 的 endpoint 和 CES 不一样(
smn.前缀 vsces.前缀),但要在同一个区域。在华东-上海二建 CES 规则,就得用华东-上海二的 SMN 主题,跨区域填不进去。
六、第四步:写告警规则
规则放在 v2 SDK 里创建。注意 v2 的客户端要单独建一个——指标查询在 v1,告警规则在 v2。
from huaweicloudsdkces.v2.ces_client import CesClient as CesClientV2
from huaweicloudsdkces.v2 import (
AlarmRulePolicy,
CreateAlarmRulesRequest,
Dimension,
Notification,
PostAlarmsReqV2,
)
ces_v2 = CesClientV2.new_builder().with_credentials(creds).with_region(region).build()
rule = PostAlarmsReqV2(
name="ecs-mem-high",
description="内存使用率连续 10 分钟高于 85%",
namespace="SYS.ECS",
type="RESOURCE_GROUP",
resources=[[Dimension(name="instance_id", value=instance_id)]],
policies=[AlarmRulePolicy(
metric_name="mem_util",
period=300, # 5 分钟聚合,单位是秒
filter="average",
comparison_operator=">=",
value=85,
unit="%",
count=2, # 连续 2 个周期都满足才告警
level=2, # 1=紧急 2=重要 3=次要 4=提示
suppress_duration=3600, # 同一告警 1 小时内不重复通知,单位秒
)],
enabled=True,
notification_enabled=True,
alarm_notifications=[Notification(
type="notification",
notification_list=[topic_urn], # 上一步拿到的 SMN 主题 URN
)],
effective_timezone="Asia/Shanghai",
)
resp = ces_v2.create_alarm_rules(CreateAlarmRulesRequest(body=rule))
print("新建告警规则 ID:", resp.alarm_id)
几个参数容易踩坑:
resources是二维数组:list[list[Dimension]]。外层是"或"关系(满足任意一组即命中),内层是"且"关系(一组里的所有维度都要满足)。只写一维会直接报错。period单位是秒:300 = 5 分钟,3600 = 1 小时。写period=5不是 5 分钟,是 5 秒。count是"连续几个周期":period=300, count=2才是"连续 10 分钟"。很多人在这里配了一次性告警。comparison_operator可选:>、<、>=、<=、=、!=,还有cycle_decrease、cycle_increase、cycle_wave三个周期性比较。filter可选:average、variance、min、max、sum。suppress_duration是抑制时间,单位秒,用来防止阈值在临界点反复横跳时刷屏。level是告警级别:1=紧急、2=重要、3=次要、4=提示。- 接口名是复数,body 是单个:
CreateAlarmRules(复数)接收的却是单个PostAlarmsReqV2,返回也只有一个alarm_id。这个命名不一致很容易让人以为能一次建一批。
七、第五步:验证与清理
配完一定要验证,别相信"创建成功"等于"能告警"。
from huaweicloudsdkces.v2 import (
BatchDeleteAlarmRulesRequest,
BatchDeleteAlarmsRequestBody,
ListAlarmHistoriesRequest,
ListAlarmRulesRequest,
)
# 看规则
rules = ces_v2.list_alarm_rules(ListAlarmRulesRequest(namespace="SYS.ECS", limit=100))
for r in rules.alarms:
print(r.alarm_id, r.name, r.enabled, r.notification_enabled)
# 看告警历史
hist = ces_v2.list_alarm_histories(ListAlarmHistoriesRequest(
namespace="SYS.ECS",
status=["alarm"],
_from="2026-10-25 00:00:00",
to="2026-10-26 00:00:00",
limit=50,
))
for h in hist.alarm_histories:
print(h.record_id, h.name, h.status, h.level, h.first_alarm_time)
# 删除
ces_v2.batch_delete_alarm_rules(BatchDeleteAlarmRulesRequest(
body=BatchDeleteAlarmsRequestBody(alarm_ids=["al1234567890"])))
注意 ListAlarmHistoriesRequest 的 _from / to 是字符串时间("2026-10-25 00:00:00"),和 v1 拉指标数据时的毫秒时间戳不一样。同一套 SDK 里两种时间格式并存,翻车率很高。
还有 BatchDeleteAlarmsRequestBody——名字里是 Alarms 不是 AlarmRules,跟外层的 BatchDeleteAlarmRulesRequest 差一个字母。
八、几个更隐蔽的坑
v1 和 v2 要混着用。 指标查询(list_metrics、show_metric_data)只在 v1 里,告警规则(create_alarm_rules 等)只在 v2 里。SDK 是同一个包,客户端要建两个。
Region 的导入路径很反直觉。 正确的是:
from huaweicloudsdkcore.region.region import Region
huaweicloudsdkcore.region 这个包的 __init__.py 是空的,直接 from huaweicloudsdkcore.region import Region 会报 ImportError。同一个包里 BasicCredentials 也一样,得写全 huaweicloudsdkcore.auth.credentials。
区域常量不要指望。 huaweicloudsdkces.v2.region 和 huaweicloudsdksmn.v2.region 里都是空的,没有 CN_EAST_3 之类的现成常量,只能自己 Region(id, endpoint) 拼。
全局服务在另一个区域。 告警历史这类全局资源,数据默认落在华北-北京四,在别的区域查不到,会误以为"没有告警记录"。
规则是资源级的。 规则建在哪个区域,就只能监控那个区域的资源。跨区域部署的业务,得在每个区域各建一套。
九、我的实践清单
现在这台机器上常驻的告警就三条:
| 告警 | 指标 | 条件 | 级别 | 触发后我做什么 |
| 内存吃紧 | mem_util |
5 分钟均值 ≥ 85%,连续 2 次 | 重要 | 查容器内存占用,必要时重启服务 |
| 磁盘告急 | disk_util_inband |
5 分钟均值 ≥ 80%,连续 2 次 | 紧急 | 清理日志和 Docker 悬空镜像 |
| CPU 持续打满 | cpu_util |
5 分钟均值 ≥ 90%,连续 3 次 | 次要 | 看是不是有定时任务或压测 |
三条规则,两个半小时的活配出来的。但它们覆盖了这类小机器上最常见的三种死法。
另外三个我觉得值得单独说一句的习惯:
- 告警一定要写
description。 控制台上一排alarm_ecs_xxx,事后根本分不清是哪个。没描述的告警,等于没配。 - 先建看板,后建告警。 告警告诉你"出事了",看板告诉你"到哪一步了"。没看板的告警,人被叫醒之后还是得现查。
- 告警规则纳入代码管理。 规则本身是配置,扔进 Git,用 IaC 部署到新机器。靠控制台手点出来的规则,换个账号就没了。
小结
给云主机配监控告警这件事,技术难度不高,难的是想清楚三件事:
- 配什么:从少量高价值指标开始,别一上来就上几十条。
- 阈值怎么定:用历史数据的 P95 当基线,不要拍脑袋。
- 通知怎么送达:SMN 主题 + 订阅 + 确认,三步都做完才算数。
把这套流程走通之后,最直接的收益是:机器出问题的那天,你是十分钟内知道的,而不是第二天早上。
---
*文中所有 SDK 调用均已用 huaweicloudsdkces / huaweicloudsdksmn 实际校验过字段名与结构;实例 ID、项目 ID、密钥等均使用占位符。*
- 点赞
- 收藏
- 关注作者
评论(0)