一台 VPS 自己管:用 60 行 Python 写个巡检脚本,磁盘内存端口全盯住

一台 VPS 自己管:用 60 行 Python 写个巡检脚本,磁盘内存端口全盯住

为什么需要自己巡检

VPS(Virtual Private Server,虚拟专用服务器)就是你在云厂商那里租的一台"远程小电脑",按月或按年付费,装什么系统、跑什么服务都由你说了算。黑五季很多人会趁便宜入手一台,但买回来之后最大的坑往往不是性能,而是没人替你看着它:日志把磁盘写满了、某个服务半夜挂了、内存被吃光导致 SSH 都连不上——等你发现时,博客已经打不开了。

大厂云有完善的监控告警,但便宜小鸡通常没有。这篇文章就带你写一个 60 行左右的 Python 巡检脚本,一次查清磁盘、内存、CPU 负载和关键服务端口,配合定时任务就能实现"机器自己盯自己"。全文代码都在我自己的测试机上真实跑过,输出原样贴出。

前置知识:一台能 SSH 登录的 Linux 服务器、会用 pip 装包、Python 3.10 以上。

脚本设计:四项检查,三个原则

巡检脚本的核心就四件事:

  1. 磁盘:根分区使用率超过 80% 就告警(日志爆盘是最常见的挂机原因);
  2. 内存:内存和 swap 使用率双看,内存吃满会先触发 OOM killer 随机杀进程;
  3. CPU 负载:看 1/5/15 分钟平均负载,结合核数判断机器是不是在"喘";
  4. 服务存活:对 127.0.0.1 的关键端口做 TCP 连接测试,SSH、Web 服务挂了立刻知道。

三个设计原则:单项检查失败不影响其它项(比如磁盘信息读不到,不能导致内存也不查);用退出码表达结果(0=正常,1=有告警,2=脚本自身出错),方便定时任务和外部监控对接;阈值集中放在文件头,换机器只改几行数字。

环境准备

只依赖一个第三方库 psutil,它是 Python 下读取系统信息的标准工具,跨平台、API 稳定:

pip install psutil

官方文档:psutil 官方文档,Python 官网下载。

完整代码

#!/usr/bin/env python3
"""单机 VPS 健康巡检:磁盘 / 内存 / CPU 负载 / 关键服务存活。
依赖:pip install psutil
用法:python3 healthcheck.py
退出码:0=全部正常,1=有告警,2=脚本自身出错
"""
import psutil
import socket
import sys
from datetime import datetime

# ---------- 阈值配置 ----------
DISK_WARN = 80      # 根分区使用率告警线(%)
MEM_WARN = 85       # 内存使用率告警线(%)
SWAP_WARN = 50      # swap 使用率告警线(%)
LOAD_WARN = 4.0     # 1 分钟平均负载告警线(按核数自行调整)
# 需要存活的 TCP 服务:(名称, 主机, 端口)
SERVICES = [
    ("SSH", "127.0.0.1", 22),
    ("HTTP", "127.0.0.1", 80),
    ("HTTPS", "127.0.0.1", 443),
]


def check_disk():
    du = psutil.disk_usage("/")
    ok = du.percent < DISK_WARN
    return ok, f"磁盘 /:{du.percent:.1f}%(已用 {du.used // 2**30}G / 共 {du.total // 2**30}G)"


def check_memory():
    vm = psutil.virtual_memory()
    swap = psutil.swap_memory()
    ok = vm.percent < MEM_WARN and swap.percent < SWAP_WARN
    return ok, (f"内存:{vm.percent:.1f}%(已用 {vm.used // 2**20}M / 共 {vm.total // 2**30}G),"
                f"swap:{swap.percent:.1f}%")


def check_load():
    load1, load5, load15 = psutil.getloadavg()
    cores = psutil.cpu_count()
    ok = load1 < LOAD_WARN
    return ok, f"负载:{load1:.2f} / {load5:.2f} / {load15:.2f}({cores} 核)"


def check_services():
    fails = []
    for name, host, port in SERVICES:
        s = socket.socket()
        s.settimeout(3)
        try:
            s.connect((host, port))
        except OSError:
            fails.append(f"{name}(:{port})")
        finally:
            s.close()
    ok = not fails
    msg = "服务端口:全部存活" if ok else "服务端口异常:" + "、".join(fails)
    return ok, msg


def main():
    print(f"[{datetime.now():%Y-%m-%d %H:%M:%S}] 开始巡检")
    checks = [check_disk, check_memory, check_load, check_services]
    alerts = []
    for fn in checks:
        try:
            ok, msg = fn()
        except Exception as e:          # 单项失败不影响其它项
            ok, msg = False, f"{fn.__name__} 采集失败:{e}"
        print(("✅ " if ok else "❌ ") + msg)
        if not ok:
            alerts.append(msg)
    if alerts:
        print(f"⚠️ 发现 {len(alerts)} 项异常,请登录处理")
        return 1
    print("🎉 全部正常")
    return 0


if __name__ == "__main__":
    sys.exit(main())

几个细节值得注意:

  • 端口检测用 socket.connect 而不是调 curl,不依赖外部命令,在最小化系统上也能跑;
  • 每个检查函数只返回 (是否正常, 描述),新增检查项就是加一个函数、往 checks 列表里塞一行;
  • psutil.getloadavg() 在 Windows 上不存在,这个脚本定位就是 Linux 服务器,不用考虑跨平台。

真实运行效果

在我的测试机上直接跑(这台测试机内存吃得比较满,正好演示告警态):

[2026-10-22 01:16:09] 开始巡检
✅ 磁盘 /:1.8%(已用 0G / 共 7G)
❌ 内存:95.8%(已用 7602M / 共 7G),swap:0.0%
✅ 负载:0.05 / 0.51 / 0.87(2 核)
❌ 服务端口异常:SSH(:22)、HTTP(:80)、HTTPS(:443)
⚠️ 发现 2 项异常,请登录处理

退出码为 1。把阈值放宽、服务指向正常端口后,再跑一次就是全绿:

[2026-10-22 01:16:25] 开始巡检
✅ 磁盘 /:1.8%(已用 0G / 共 7G)
✅ 内存:96.0%(已用 7616M / 共 7G),swap:0.0%
✅ 负载:0.80 / 0.64 / 0.91(2 核)
✅ 服务端口:全部存活
🎉 全部正常

两种状态、两种退出码都验证通过,脚本可以直接拿去用。

让它每天自己跑

脚本本身只负责"检查并报告",定时交给 cron。编辑 crontab:

crontab -e

加一行,每 30 分钟跑一次,输出追加到日志:

*/30 * * * * /usr/bin/python3 /root/healthcheck.py >> /var/log/healthcheck.log 2>&1

注意把 /usr/bin/python3 换成你机器上真实的 Python 路径(which python3 查看),脚本路径换成你实际存放的位置。

如果想"出问题才通知我",可以在 main() 的告警分支里加一段推送。比如 Telegram Bot(Bot API 文档),核心就几行:

import urllib.request, json

def tg_push(text):
    token = "<你的BotToken>"      # 换成自己的,绝不要写进公开仓库
    chat_id = "<你的ChatID>"
    url = f"https://api.telegram.org/bot{token}/sendMessage"
    data = json.dumps({"chat_id": chat_id, "text": text}).encode()
    urllib.request.urlopen(urllib.request.Request(
        url, data=data, headers={"Content-Type": "application/json"}), timeout=10)

只用标准库,不用再装 requests。在 alerts 非空时调用 tg_push("\n".join(alerts)) 即可。

下一步可以怎么玩

  • 多台机器:把脚本分发到每台小鸡上跑,告警统一推到同一个 Telegram 频道,频道名就是机器名;
  • 趋势记录:每次运行把四个数字写进 SQLite 或 CSV,磁盘"每天涨 2%"这种慢性问题就能提前发现;
  • 更细的检查:比如 systemd 指定服务是否 active、证书过期天数、特定进程是否存在,psutil 都能覆盖。

便宜 VPS 最大的成本不是那几十美元年费,而是出问题时你没第一时间知道。一个 60 行的脚本加一行 cron,就能把"裸奔"变成"有人值班"。先把它跑起来,再慢慢加料。

相关阅读