免费云服务器巡检自动化实战:几十台机器,只看一份日报
免费云服务器、免费容器、试用主机,单看每一台都不难管。难的是数量上来以后:这台 48 小时要续期一次,那台连续运行 24 小时会被平台强制停止,还有的面板上写着 Running,服务其实早就访问不通了。
靠人每天逐个登录面板检查,不现实,也必然漏检。我这段时间的做法,是把巡检做成一套自动化的闭环:定时探测、按规则判断、能自动修的小故障直接修,修不了的才汇总成一份日报推给我。下面把这套思路和可直接改造的代码分享出来。
巡检不能只看面板状态
这是我踩坑最多的一点:面板状态不等于服务可用。
有的平台接口长期返回 starting,实际进程已经稳定运行;有的平台显示 Running / Healthy,公网地址却完全连不上;还有的服务对 GET 请求返回 404,对 HEAD 请求才是正常的存活响应。如果只采集面板字段就下结论,误报和漏报都会很多。
可靠的巡检至少要做三层验证:
- 面板或接口层:读取实例状态、到期时间、资源用量,判断是否临近续期或已被暂停。
- 网络层:对公网地址或业务入口做真实请求,看状态码是否符合预期,而不是只看端口开不开。
- 业务层:请求一个固定的健康路径(例如
/health),校验返回内容,确认进程不只是活着,而是真的在提供服务。
三层里任意一层异常,都先记录现场再处置,不要看到状态不对就立刻重启。先留证据,后面复盘才知道是平台停机、进程崩溃,还是网络问题。
一套可落地的三层巡检架构
我的巡检拆成三层,互不耦合:
|
层级 |
做什么 |
典型实现 |
|---|---|---|
|
采集层 |
拉取面板状态、发探针请求、读到期时间 |
平台接口、HTTP 请求 |
|
判断层 |
按规则分类为正常、临期、异常、真停机 |
阈值和状态机 |
|
处置层 |
自动重启、自动续期、汇总通知 |
接口调用、定时任务 |
这样拆的好处是:换一个平台,只需要改采集层的适配;判断和通知的逻辑可以一直复用。机器从 3 台涨到 30 台时,新增的只是一行配置,而不是一套新脚本。
核心巡检脚本示例
下面是一个精简版的巡检脚本。它读取一份机器清单,逐台做探针,把结果分成正常、临期和异常三类,最后输出一份可读的汇总。实际使用时,把平台的续期和重启接口补到处置环节即可。
import urllib.request
import urllib.error
from datetime import datetime, timezone
# 机器清单:名称、健康检查地址、可接受的状态码、到期时间(ISO 格式,可为空)
HOSTS = [
{
"name": "host-a",
"health_url": "https://example.com/health",
"ok_codes": [200],
"expire_at": "2026-11-20T00:00:00+00:00",
},
{
"name": "host-b",
"health_url": "https://example.com/health",
"ok_codes": [200, 400], # 有些服务以 400 表示“已到达业务层”
"expire_at": None,
},
]
WARN_HOURS = 24 # 到期前多少小时开始提醒
def probe(url: str, timeout: int = 10) -> int:
"""返回 HTTP 状态码,连不上返回 -1。用 HEAD 优先,个别服务可用 GET 替换。"""
req = urllib.request.Request(url, method="HEAD")
try:
with urllib.request.urlopen(req, timeout=timeout) as resp:
return resp.status
except urllib.error.HTTPError as e:
return e.code
except Exception:
return -1
def check_host(host: dict) -> str:
code = probe(host["health_url"])
if code not in host["ok_codes"]:
return f"[异常] {host['name']} 探针返回 {code},疑似停机或网络不通"
if host.get("expire_at"):
expire = datetime.fromisoformat(host["expire_at"])
left_hours = (expire - datetime.now(timezone.utc)).total_seconds() / 3600
if left_hours < 0:
return f"[过期] {host['name']} 已过期,请立即处理"
if left_hours <= WARN_HOURS:
return f"[临期] {host['name']} 剩余约 {left_hours:.1f} 小时,请及时续期"
return f"[正常] {host['name']} 探针 {code}"
if __name__ == "__main__":
lines = [check_host(h) for h in HOSTS]
print("\n".join(lines))
# 这里可以接你的通知通道:把非“正常”的行汇总后推送,避免逐条打扰
几个细节值得注意:
- 可接受状态码要按服务实测来定,不要一律认为只有 200 才算活着。有些 WebSocket 或代理类服务,探针返回 400 恰恰说明请求已经到达业务层。
- 探针要有超时。没有超时的巡检,一台机器卡住,整轮巡检都会被拖死。
- 到期提醒的阈值要留足余量。周期 48 小时的免费续期,我一般提前 24 小时就进提醒名单,给网络波动和人工处理留时间。
自动修复的边界要先划清
巡检发现问题就自动处理,效率确实高,但边界必须在写代码之前就定死。我的划分原则只有一条:可逆的自动做,不可逆的先问人。
可以直接自动执行的:
- 服务进程退出时按原参数拉起
- 接口确认停机后调用平台的 Start / Restart
- 到期前在免费额度内自动续期
- 临时文件、日志轮转等不影响数据的清理
必须停下来人工确认的:
- 任何涉及付费、升级套餐的动作
- 删除实例、删除数据、重装系统
- 修改密码、密钥、令牌等凭据类操作
这条边界不是保守,而是算账:一台免费机器误重启一次,代价是几分钟不可用;误删除或误付费一次,代价可能是整台机器和里面的部署全部重来。自动化省的是重复劳动,不该替人做不可逆的决定。
还有一个容易忽略的点:自动重启之后必须再次探针验证。重启接口返回成功,只代表指令发出去了,不代表服务恢复了。我的流程是重启后等待一小段时间,重新走一遍三层探针,通了才把事件标记为已恢复,没通就升级为人工处理。
调度、去重与通知
调度上,我用的是最朴素的组合:系统定时任务负责触发,脚本本身负责判断。周期按平台特性分开设置,快过期的平台查得勤一点,长期稳定的平台一天一次足够。
通知最容易做错的,是把每一条异常都单独推一条消息。机器一多,一次网络抖动就是几十条报警,看的人很快就会麻木,真正的故障反而被淹没。我的做法是三点:
- 汇总推送:一轮巡检只发一份汇总,正常项一行带过,异常项展开写清机器、现象和已执行的处置。
- 去重限频:同一台机器的同一类异常,在未恢复前只提醒一次,恢复后再发一条恢复通知。持续故障靠日报兜底,不靠刷屏。
- 静默正常项:全部正常时只发一句结论。巡检的价值是让人不用看,而不是每天制造阅读负担。
凭据管理上还有一条硬规矩:接口密钥、令牌只放在机器本地的受限文件里(权限 600),不写进脚本、不提交到代码仓库、不出现在日志和通知内容里。巡检脚本一旦要分享或开源,先把配置抽到环境变量或独立配置文件,这一步不能省。
踩过的坑
- 只信控制台:有一次控制台连续几天打不开,我以为机器出事了,后来发现是平台自己的 Web 端故障,机器一直在正常运行。所以巡检结论要多源交叉验证,单一来源不可用时只能记为“未核实”,不能直接判故障。
- 探针方法选错:同一个健康路径,用
GET是 404、用HEAD是 200,差点误判停机去重启一台好机器。每个服务接入巡检时,先手动把探针方法试对,再写进配置。 - 虚拟机或运行环境被更换后,定时任务丢失:托管环境重建后,脚本文件还在,但系统级的定时单元没有了,巡检静默停摆。教训是巡检本身也要被巡检——加一个简单的存活心跳,长时间没有新一轮结果时主动报警。
- 只看助手侧探针就宣布恢复:探针通了,只能证明从巡检机这个网络位置能访问。最终是否真的可用,还要以实际使用端的连接结果为准,尤其是网络路径差异很大的场景。
写在最后
免费资源的玩法,拼的从来不是谁找到的机器多,而是谁的机器能长期稳定地活着。把续期、探活、重启、通知这四件重复的事交给脚本,人只处理真正需要判断的异常,几十台机器的运维量可以压缩到每天看一份日报。
上面这套方案没有用任何重型框架,一个 Python 脚本加定时任务就能起步。等机器再多起来,再考虑把结果落库、做趋势统计也不迟——先跑起来,比先设计完美更重要。
- 点赞
- 收藏
- 关注作者
评论(0)