VPS 自建 LLM 网关实战:one-api + OpenClaw + n8n + 监控面板优化全记录

第 3 页:健康检查自动化

one-api 渠道会随 NVIDIA 的模型上下线而变化。手动检查不现实,需要一个每天自动运行的脚本。

设计原则

  • 慢速:每渠道间隔 5 秒(避免触发 NVIDIA IP 级限流)
  • 只测启用的:已停用的渠道不测,节省配额
  • 只报告,不自动修改:避免因一次超时误删好渠道
  • 区分错误类型:403(权限)/ 429(限流)/ Timeout(网络)/ HTTP5xx(上游过载)分别处理
  • 双重输出:人类可读日志 + 机器可读 JSON

脚本内容

保存为 /root/one-api-health.py:

#!/usr/bin/env python3
"""one-api NVIDIA 渠道健康检查"""
import sqlite3, urllib.request, urllib.error, json, time, sys
from datetime import datetime

DB = '/home/user/one-api/one-api.db'
NVIDIA_CHAT = 'https://integrate.api.nvidia.com/v1/chat/completions'
LOG_FILE = '/root/one-api-health.log'
REPORT_JSON = '/root/one-api-health-report.json'

def test_chat(key: str, model: str, timeout=45) -> tuple:
    payload = json.dumps({
        "model": model,
        "messages": [{"role": "user", "content": "hi"}],
        "max_tokens": 3,
    }).encode()
    req = urllib.request.Request(
        NVIDIA_CHAT, data=payload,
        headers={"Authorization": f"Bearer {key}", "Content-Type": "application/json"}
    )
    try:
        with urllib.request.urlopen(req, timeout=timeout):
            return ("OK", "")
    except urllib.error.HTTPError as e:
        body = ""
        try: body = e.read().decode()[:120]
        except: pass
        return (f"HTTP{e.code}", body)
    except Exception as e:
        return (type(e).__name__, str(e)[:120])

def log(msg: str):
    ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    line = f"[{ts}] {msg}"
    print(line)
    with open(LOG_FILE, "a") as f:
        f.write(line + "\n")

def main():
    log("=" * 60)
    log("one-api NVIDIA health check START")

    c = sqlite3.connect(DB)
    c.row_factory = sqlite3.Row
    cur = c.cursor()
    cur.execute("""
        SELECT id, name, key, models FROM channels
        WHERE type = 50 AND status = 1 AND models NOT LIKE '%embed%'
        ORDER BY id
    """)
    channels = [dict(r) for r in cur.fetchall()]
    log(f"Testing {len(channels)} enabled channels (5s interval)")

    results = []
    ok = fail = 0
    for i, ch in enumerate(channels, 1):
        status, err = test_chat(ch["key"], ch["models"])
        results.append({"id": ch["id"], "name": ch["name"],
                       "model": ch["models"], "status": status, "error": err})
        if status == "OK":
            ok += 1
            log(f"  [{i}/{len(channels)}] OK   ch{ch['id']} {ch['models']}")
        else:
            fail += 1
            log(f"  [{i}/{len(channels)}] {status}  ch{ch['id']} {ch['models']}  {err[:60]}")
        time.sleep(5)

    report = {
        "tested_at": datetime.now().isoformat(),
        "total": len(channels), "ok": ok, "fail": fail,
        "results": results,
    }
    with open(REPORT_JSON, "w") as f:
        json.dump(report, f, indent=2, ensure_ascii=False)

    log(f"DONE: {ok} OK / {fail} FAIL / {len(channels)} total")
    log("=" * 60)
    c.close()
    sys.exit(0 if fail == 0 else 1)

if __name__ == "__main__":
    main()

cron 配置(完整)

1. 添加 cron 任务

用 crontab -l 保留现有任务,追加新任务(一行命令):

# 保留现有 crontab + 追加新任务
(crontab -l 2>/dev/null; echo "0 4 * * * /usr/bin/python3 /root/one-api-health.py >> /root/one-api-health-cron.log 2>&1") | crontab -

# 验证是否添加成功
crontab -l | grep one-api-health

2. 手动跑一次(立即生成报告)

cron 是定时任务,不会立即执行。想马上验证效果,手动跑一次:

python3 /root/one-api-health.py

约 60-70 秒完成(12 渠道 × 5 秒间隔)。跑完后 /root/one-api-health-report.json 会更新,VPS Monitor 的 Realtime tab 会在 30 秒内刷新显示。

3. 为什么必须用绝对路径 /usr/bin/python3?

cron 执行环境的 PATH 与登录 shell 完全不同。cron 默认只有 /usr/bin:/bin,写 python3 可能找不到命令。必须用绝对路径:

# 1. 先确认 python3 的绝对路径
which python3
# → /usr/bin/python3

# 2. cron 里用绝对路径
0 4 * * * /usr/bin/python3 /root/one-api-health.py >> /root/one-api-health-cron.log 2>&1

4. 排查 cron 不执行

常见问题与排查顺序:

# 1. cron 服务是否运行
systemctl status cron
# 期望: Active: active (running)

# 2. crontab 里是否真的包含任务
crontab -l | grep one-api-health
# 期望: 显示那行 0 4 * * * ...

# 3. cron 系统日志(看任务是否被触发)
grep CRON /var/log/syslog | tail -10
# 或
journalctl -u cron --no-pager -n 10

# 4. 脚本自己的日志(看任务是否真的跑了)
cat /root/one-api-health-cron.log
cat /root/one-api-health.log

真实踩坑:cron 服务正常运行,其他任务(备份、监控)都在跑,但 one-api-health.py 从未执行——原因是添加任务的命令当时没被执行(可能终端复制粘贴时漏了,或命令报错被忽略)。

排查第一步就发现:

$ crontab -l | grep one-api-health
(空)

# 说明 crontab 里根本没有这个任务,不是 cron 服务的问题。

重新执行添加命令即可解决。

5. 三个日志文件的区别

文件用途写入者内容示例
/root/one-api-health.log人类可读日志Python 脚本[2026-09-28 17:59:41] 12 OK / 1 FAIL
/root/one-api-health-report.json机器可读报告Python 脚本{"tested_at": "...", "ok": 12, ...}
/root/one-api-health-cron.logcron 层输出(stdout+stderr)cron 重定向脚本的 print 输出 + 任何错误

6. 验证 cron 真的会执行(临时测试法)

把任务临时改成「每分钟跑一次」,等 70 秒观察,证明 cron 确实在执行:

# 1. 备份当前 crontab
crontab -l > /root/crontab.backup

# 2. 临时改成每分钟
(crontab -l | grep -v "one-api-health.py"; \
 echo "* * * * * /usr/bin/python3 /root/one-api-health.py >> /root/one-api-health-cron.log 2>&1") | crontab -

# 3. 等 70 秒
sleep 70

# 4. 看日志(有内容说明 cron 工作正常)
cat /root/one-api-health-cron.log | tail -5

# 5. 恢复原 crontab
crontab /root/crontab.backup

# 6. 确认已恢复
crontab -l | grep one-api-health

如果第 4 步的 -cron.log 有新增内容,说明 cron 正常工作,可以放心把频率调回每天一次。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注