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.log | cron 层输出(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 正常工作,可以放心把频率调回每天一次。
