用 Python + 华为云 SDK 做一份 ECS 巡检表:从 AK/SK 签名到 Excel 输出
一、为什么要自己写巡检脚本
控制台的云服务器列表能看规格、能看状态,但"到期时间"“续费方式”"带宽峰值"这些字段散落在不同页面。手上超过十几台机器之后,靠翻控制台做月度巡检基本不现实:容易漏、容易看错、也没法留痕。
这篇记录一个够用的做法:用华为云官方 Python SDK(huaweicloudsdkecs + huaweicloudsdkbss)把 ECS 清单和费用信息拉下来,拼成一张 Excel,每周跑一次。
二、环境准备
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install huaweicloudsdkcore huaweicloudsdkecs huaweicloudsdkbss openpyxl
SDK 是分产品拆包的,用到哪个装哪个,不要装 huaweicloudsdkall,它会把几十个产品包的依赖一起拖进来。
三、AK/SK 与客户端初始化
AK/SK 建议放在环境变量里,不要写死在代码中:
import os
from huaweicloudsdkcore.auth.credentials import BasicCredentials
AK = os.environ["HUAWEI_AK"]
SK = os.environ["HUAWEI_SK"]
PROJECT_ID = os.environ["HUAWEI_PROJECT_ID"]
REGION = os.environ.get("HUAWEI_REGION", "cn-north-4")
ECS 客户端这样建:
from huaweicloudsdkecs.v2.ecs_client import EcsClient
from huaweicloudsdkecs.v2.region.ecs_region import EcsRegion
credentials = BasicCredentials(AK, SK, PROJECT_ID)
ecs_client = (
EcsClient.new_builder()
.with_credentials(credentials)
.with_region(EcsRegion.value_of(REGION))
.build()
)
三个容易踩的点:
- PROJECT_ID 必须和 REGION 配套。用 cn-north-4 的区域,却传了 cn-east-3 的 project_id,会得到 401 而不是 404,很容易误判成 AK 过期。
- BasicCredentials 的第三个参数是项目 ID,不是账号 ID。控制台"我的凭证 → API 凭证"里能直接复制。
- 不要用永久 AK。生产上建一个只读策略的 IAM 子用户,把
ECS ReadOnlyAccess和BSS ReadOnlyAccess挂上就够了。
四、分页拉取 ECS 列表
ECS 的 ListServersDetails 一次最多返回 100 条,用 offset 翻页:
from huaweicloudsdkecs.v2.model import ListServersDetailsRequest
def iter_servers(client, page_size=100):
offset = 1 # 注意:ECS 的 offset 从 1 开始
while True:
req = ListServersDetailsRequest(limit=page_size, offset=offset)
resp = client.list_servers_details(req)
servers = resp.servers or []
if not servers:
return
for s in servers:
yield s
if len(servers) < page_size:
return
offset += page_size
offset 从 1 开始这一点,和多数云厂商的 0 基偏移不一样,写成 0 会拿到看似重复的数据。
从 ServerDetail 里能直接拿到的字段:
| 字段 | 说明 |
|---|---|
id / name |
实例 ID 与名称 |
status |
ACTIVE / SHUTOFF / ERROR 等 |
flavor.vcpus / flavor.ram |
vCPU 与内存(MB) |
image.id |
镜像 ID |
created |
创建时间,ISO8601 |
addresses |
网卡与私网/公网 IP 字典 |
metadata |
含 charging_mode、vpc_id 等 |
metadata 里的 charging_mode 是关键:0 是按需,1 是包年包月。按需机器没有"到期"概念,包年包月才有,巡检表要分开统计。
五、到期时间走费用中心
ECS 接口不返回到期时间,得问 BSS(费用中心)的 ListResourceInstances:
from huaweicloudsdkbss.v2.bss_client import BssClient
from huaweicloudsdkbss.v2.region.bss_region import BssRegion
from huaweicloudsdkbss.v2.model import ListResourceInstancesRequest
bss = (
BssClient.new_builder()
.with_credentials(credentials)
.with_region(BssRegion.value_of("cn-north-1")) # 费用中心固定这一区域
.build()
)
req = ListResourceInstancesRequest(
domain_id=DOMAIN_ID,
resource_type="ecs",
offset=0,
limit=100,
)
resp = bss.list_resource_instances(req)
BSS 的 region 固定用 cn-north-1(费用中心是全局服务),填业务区域会报 403。返回体里 expire_time 为 null 表示按需资源。
六、拼表并导出
from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill, Alignment
from datetime import datetime, timezone
def days_left(expire_time):
if not expire_time:
return None
dt = datetime.fromisoformat(expire_time.replace("Z", "+00:00"))
return (dt - datetime.now(timezone.utc)).days
wb = Workbook()
ws = wb.active
ws.title = "ECS巡检"
ws.append(["实例名", "实例ID", "状态", "规格", "vCPU", "内存(GB)",
"计费模式", "创建时间", "到期时间", "剩余天数"])
expire_map = {} # resource_id -> expire_time,由 BSS 结果填充
for s in iter_servers(ecs_client):
if s.status == "DELETED":
continue
exp = expire_map.get(s.id)
ws.append([
s.name,
s.id,
s.status,
getattr(s.flavor, "id", ""),
getattr(s.flavor, "vcpus", None),
round(getattr(s.flavor, "ram", 0) / 1024, 1) if getattr(s.flavor, "ram", None) else None,
"包年包月" if (s.metadata or {}).get("charging_mode") == "1" else "按需",
s.created,
exp,
days_left(exp),
])
# 30 天内到期的整行标红
red = PatternFill("solid", fgColor="FFE8E8")
for row in ws.iter_rows(min_row=2):
d = row[9].value
if isinstance(d, int) and 0 <= d <= 30:
for cell in row:
cell.fill = red
cell.font = Font(color="C00000")
for col, w in zip("ABCDEFGHIJ", [22, 40, 10, 26, 8, 10, 12, 22, 22, 10]):
ws.column_dimensions[col].width = w
ws.freeze_panes = "A2"
wb.save(f"ecs-audit-{datetime.now():%Y%m%d}.xlsx")
七、几个实际踩过的坑
坑一:flavor.ram 单位是 MB。 直接当 GB 用会把 4 GB 的机器写成 4096 GB,报表一眼假。除以 1024 后保留一位小数即可。
坑二:created 带时区。 字符串是 2026-03-11T08:12:33Z,直接 datetime.fromisoformat 会得到 naive 对象,和 datetime.now() 相减可能报 TypeError。统一转成 UTC aware 再算。
坑三:SDK 异常要分类处理。 ServiceResponseException 里有 status_code:401 是凭证问题,403 是策略没授权,429 是限流。限流时不要立刻重试,按 Retry-After 退避,否则会持续被打回。
from huaweicloudsdkcore.exceptions import exceptions
try:
resp = ecs_client.list_servers_details(req)
except exceptions.ServiceResponseException as e:
if e.status_code == 429:
time.sleep(int(e.error_msg.get("Retry-After", 5)))
else:
raise
坑四:不要把 AK/SK 打进日志。 SDK 的 debug 模式会打印完整请求头,里面含签名信息。巡检脚本一般跑在 CI 或定时任务里,日志是集中收集的,务必别开 debug 上生产。
八、小结
这套东西加起来不到 150 行,但把"我这堆机器什么时候到期、什么规格、多少钱"变成了每周一张固定的表。后面想加告警很简单:days_left 小于 7 就调一次 SMN 发邮件,剩下的交给时间。
真正省事的不是脚本本身,而是把巡检这件事从"想起来才做"变成"定时一定会发生"。
- 点赞
- 收藏
- 关注作者
评论(0)