用 Python + 华为云 SDK 做一份 ECS 巡检表:从 AK/SK 签名到 Excel 输出

举报
yd_248934043 发表于 2026/10/26 15:20:03 2026/10/26
【摘要】 用 huaweicloudsdkecs + huaweicloudsdkbss 把云服务器清单和到期时间拉成一张 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()
)

三个容易踩的点:

  1. PROJECT_ID 必须和 REGION 配套。用 cn-north-4 的区域,却传了 cn-east-3 的 project_id,会得到 401 而不是 404,很容易误判成 AK 过期。
  2. BasicCredentials 的第三个参数是项目 ID,不是账号 ID。控制台"我的凭证 → API 凭证"里能直接复制。
  3. 不要用永久 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 发邮件,剩下的交给时间。

真正省事的不是脚本本身,而是把巡检这件事从"想起来才做"变成"定时一定会发生"。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。