跳到主要内容
生长的档案
← 返回期刊架
NO.2026-0062026.10.2323 MIN READ

从回环到公网:LiteLLM 配置、Caddy 反代与运维台账

技术实战API反代运维

快照时间:2026-10-22 | 系统:Debian GNU/Linux 12 (bookworm)、x86_64 VPS 链路:客户端 → Cloudflare → Caddy → LiteLLM → Groq / ChatGPT 说明:不含任何密钥、连接串或密码,只记变量名;文中 <...> 为脱敏占位符。

LiteLLM 启动后只监听 127.0.0.1:4000。

这个地址在服务器外面没有任何意义。它只保证一件事:没有人能从公网直接碰到模型网关。

真正让它变成一个可调用 API 的,是 Caddy 的两行配置,加上 Cloudflare 那边的一条 A 记录。

从客户端发出一次请求,到 Groq 的回复返回,中间要走过四跳。这篇记录的是这四跳怎么配通、怎么验证,以及跑起来之后那张运维台账上还没兑现的几行。

0. 起点:从「能跑」到「说得清」

素材是一份 429 行的运维笔记,由部署验证记录与运维快照合并而成。合并时做过一次剪辑——只保留已验证配置,不含排错阶段的错误配置。

这句话值得停一下。一份自觉做过剪辑的笔记,它的空白并不等于没发生,只是没被写进来。

第 003 期写过从零部署的完整流程。这一期不复述安装步骤,只讲三件事:现在配的是什么、为什么这样配、怎么验证它确实在按预期工作。

读状态时用一个四档的尺子:

  • A 已实测:有命令输出或返回码,可以复读。
  • B 可复读:数据在快照里,但没有复测过。
  • C 已配置未验证:配置写下了,效果没验。
  • D 无证据:有现象,没有解释。

纪律只有一条:状态词后面必须能指到一个证据位置。写「正常」不够,要能回答它是 A 还是 C。

1. 架构:一次调用要走过四跳

客户端 ──HTTPS 443──▶ Caddy ┄┄▶ LiteLLM(127.0.0.1:4000,Docker,host 网络)
                      ┄┄ 虚线 = 部署后未再核对  ├─▶ Groq            GROQ_API_KEY
                                          ├─▶ ChatGPT Codex   OAuth 令牌(chatgpt/auth.json)
                                          └─▶ Neon Postgres   DATABASE_URL

四跳分别是:

  1. 客户端把请求发到 https://llm.example.com/v1/chat/completions,DNS 解析到 Cloudflare;
  2. Cloudflare 转发到 VPS 的 443,Caddy 接住并完成 TLS 终止;
  3. Caddy 按域名规则,把请求转给 127.0.0.1:4000;
  4. LiteLLM 按 config.yaml 里的 model_name 选中上游,用对应的密钥发出去。

第二跳和第三跳之间是这台机器上唯一的公网入口。也就是说,Caddy 配置的那几行,决定了整个网关对外暴露什么。

图里那条虚线不是排版选择。实线表示部署当时验证过,虚线表示从那以后没人再看过——它对应后面要说的 P0-4。

2. LiteLLM 配置:config.yaml 里定义了什么

所有文件都在 /opt/litellm:

mkdir -p /opt/litellm && cd /opt/litellm
/opt/litellm/
├── docker-compose.yml
├── config.yaml
└── .env

配置文件 config.yaml 只读挂载进容器。它只有三层结构:

model_list:

  - model_name: groq-gpt-oss-120b        # 对外暴露的名字,客户端用它调用
    litellm_params:
      model: groq/openai/gpt-oss-120b    # 上游真实模型 ID
      api_key: "os.environ/GROQ_API_KEY" # 从环境变量取,不落明文

  # groq-gpt-oss-20b / groq-qwen3.8-27b / groq-qwen3.6-27b /
  # groq-compound / groq-compound-mini 同构,共 6 个

model_name 是给客户端看的,litellm_params.model 是给上游看的。两者可以完全不同——这正是这层网关的价值:换供应商时改下面一行,客户端的调用代码不动。

当前挂了 8 个模型,分两类:

来源 模型名 说明
Groq groq-gpt-oss-120b、groq-gpt-oss-20b、groq-qwen3.8-27b、groq-qwen3.6-27b、groq-compound、groq-compound-mini 6 个,用 GROQ_API_KEY
ChatGPT Codex chatgpt/gpt-6.1-sol、chatgpt/gpt-6-luna 2 个,用 OAuth 令牌,调用时须写 chatgpt/ 前缀全名

api_key 这一行有个容易踩的坑。验证通过的写法只有一种:

api_key: "os.environ/GROQ_API_KEY"

引号不能去,os.environ/ 前缀不能省。改成裸写 os.environ/GROQ_API_KEY(去掉引号)或直接填入密钥,都可能出现 Invalid API Key。这不是风格问题——YAML 在没有引号时对某些字符的处理与 LiteLLM 的解析方式叠加,会让这个值不再是它看起来的样子。

密钥本身放在同一个目录的 .env 里,权限 600。三个变量:

  • GROQ_API_KEY:Groq 的 Key;
  • LITELLM_MASTER_KEY:自设管理密钥,必须以 sk- 开头;
  • DATABASE_URL:Neon 直连串,主机名不含 -pooler,带 sslmode=require。

docker-compose.yml 全文如下:

services:
  litellm:
    image: ghcr.io/berriai/litellm:main-latest
    container_name: litellm
    restart: unless-stopped
    environment:
      - CHATGPT_TOKEN_DIR=/tokens/chatgpt
    env_file:
      - .env
    volumes:
      - ./config.yaml:/app/config.yaml:ro
      - ./chatgpt:/tokens/chatgpt
    network_mode: host
    mem_limit: 1200m
    command:
      - "--config"
      - "/app/config.yaml"
      - "--host"
      - "127.0.0.1"

其中有四处值得单独说:

network_mode: host:容器直接用宿主机网络,所以不需要 ports: 映射。代价是端口直接开在宿主上,好处是 --host 127.0.0.1 能直接生效。

--host 127.0.0.1:让 LiteLLM 只绑定回环地址。部署基线没有这个参数,那时它监听宿主机全部接口——这是后来加的加固。

mem_limit: 1200m:限制容器内存。与它相关的还有一段崩溃史,放在第 6 节。

./chatgpt 不能加 :ro:这个目录里放的是 OAuth 令牌,约 10 天会刷新一次,刷新时要写回 auth.json。挂成只读,第一次刷新就会失败。

改完配置怎么生效,取决于改的是哪个文件:

改了什么 命令
只改了 config.yaml docker restart litellm
改了 .env 或 docker-compose.yml docker compose up -d

docker restart 不会重新读取 .env 与 compose 文件。这条规则看起来琐碎,但它是最常见的「改了没生效」的原因。

三个文件齐了就启动:

cd /opt/litellm
docker compose up -d
docker compose ps
ss -ltnp | grep ':4000'

最后一条应该看到 127.0.0.1:4000 在监听。如果看到 0.0.0.0:4000,说明 --host 参数没生效——那意味着 4000 又回到了对所有接口开放的状态。

3. Caddy 反代:把 127.0.0.1:4000 变成一个域名

Caddy 用系统包安装,自动 HTTPS 是它自带的能力:

apt update && apt install -y caddy

Caddyfile 只有两行:

llm.example.com {
    reverse_proxy 127.0.0.1:4000
}

第一行是域名,第二行是转发目标。Caddy 会自动为这个域名申请并续期证书——不需要单独的 certbot,也没有证书路径要填。

它有两个前提:80 和 443 必须由 Caddy 独占。装之前先查一次:

ss -ltnp | grep -E ':80|:443'

被别的服务占着,就先调整那个服务的端口,否则 Caddy 起不来。

域名这一侧,Cloudflare 需要两件事:一条 A 记录指向 VPS 公网 IP,以及 SSL/TLS 模式设为 Full (strict)。后者要求源站已有有效证书,而 Caddy 正好提供了它。

改完 Caddyfile 的标准动作是三步:

caddy fmt --overwrite /etc/caddy/Caddyfile
caddy validate --config /etc/caddy/Caddyfile
systemctl restart caddy

验证从外到内,三条命令:

curl -I https://llm.example.com
# 期望 HTTP/2 200,且响应头里有 via: 1.1 Caddy

curl https://llm.example.com/v1/models -H "Authorization: Bearer $LITELLM_MASTER_KEY"
# 期望返回模型列表,说明反代 + 鉴权都通了

curl -s http://127.0.0.1:4000/health/liveliness
# 期望 "I'm alive!"

第一条命令里 via: 1.1 Caddy 这个头是关键判据。它证明响应确实经过了 Caddy,而不是被别的东西接走了——同一台机器上如果有多个服务,这个头能省掉很多猜测。

4. 调用:curl、SDK 与 virtual key

链路通了之后,调用本身是标准的 OpenAI 兼容格式。本机验证前先把变量读进当前 shell:

cd /opt/litellm && set -a && source .env && set +a

本机验证:

curl http://127.0.0.1:4000/v1/models \
  -H "Authorization: Bearer $LITELLM_MASTER_KEY"

curl http://127.0.0.1:4000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $LITELLM_MASTER_KEY" \
  -d '{"model":"groq-gpt-oss-20b","messages":[{"role":"user","content":"Reply with OK"}],"max_tokens":100}'

公网调用把地址换成 https://llm.example.com 即可。客户端用 SDK 时:

from openai import OpenAI

client = OpenAI(
    api_key="你的_virtual_key",
    base_url="https://llm.example.com/v1"
)

response = client.chat.completions.create(
    model="groq-gpt-oss-20b",
    messages=[{"role": "user", "content": "Reply with OK"}]
)

print(response.choices[0].message.content)

注意 api_key 那一栏填的是 virtual key,不是 master key。

这条分工值得写死:master key 只用来管理和测试,客户端一律用 virtual key。原因很直接——master key 能创建、停用、删除所有 key,一旦发出去就收不回来;virtual key 可以单独限定模型、限速、设过期时间,废掉一个不影响其他设备。

创建 key 的命令:

export MK=$(grep '^LITELLM_MASTER_KEY=' /opt/litellm/.env | cut -d= -f2- | tr -d '"')

# 长期设备 key:限定模型、限制并发
curl -s http://localhost:4000/key/generate \
  -H "Authorization: Bearer $MK" -H 'Content-Type: application/json' \
  -d '{"key_alias":"laptop","models":["chatgpt/gpt-6.1-sol","chatgpt/gpt-6-luna"],"rpm_limit":20,"max_parallel_requests":2}'

# 临时 key:到期自动失效
curl -s http://localhost:4000/key/generate \
  -H "Authorization: Bearer $MK" -H 'Content-Type: application/json' \
  -d '{"key_alias":"temp-24h","models":["chatgpt/gpt-6-luna"],"duration":"24h","rpm_limit":5}'

管理操作有四个:查看、停用、恢复、删除。

curl -s "http://localhost:4000/key/info?key=sk-xxx" -H "Authorization: Bearer $MK"
curl -s -X POST http://localhost:4000/key/block   -H "Authorization: Bearer $MK" -H 'Content-Type: application/json' -d '{"key":"sk-xxx"}'
curl -s -X POST http://localhost:4000/key/unblock -H "Authorization: Bearer $MK" -H 'Content-Type: application/json' -d '{"key":"sk-xxx"}'
curl -s -X POST http://localhost:4000/key/delete  -H "Authorization: Bearer $MK" -H 'Content-Type: application/json' -d '{"keys":["sk-xxx"]}'

三条纪律:

  • 新 key 的完整值只在创建时显示一次,当场保存,之后只能看到掩码;
  • chatgpt 模型要在 models 字段里写 chatgpt/ 前缀的完整名字,写短名会被拒绝;
  • /key/delete 的字段名以 http://localhost:4000/docs 的 Swagger 为准,不同版本有过差异。

5. 收紧:端口层两道锁,域名层零道锁

上面的配置跑通之后,有一个不对称的地方需要看清。

端口层有两道锁。LiteLLM 用 --host 127.0.0.1 只听回环;ufw 对 4000 显式 DENY。任何来自公网的直连都到不了。

域名层一道都没有。Caddy 那条 reverse_proxy 127.0.0.1:4000 是全量转发——https://llm.example.com/ 下面所有路径都会送给 LiteLLM。

单独看,每个组件都正常:数据库连上了,Caddy 在跑,/key/* 接口可用。连起来看:

  1. .env 里写入 DATABASE_URL;
  2. LiteLLM 检测到数据库,启用 virtual key 管理,/key/generate、/key/info、/key/delete 一并可用;
  3. Caddy 仍然是部署时那条全量反代;
  4. 于是管理接口在公网上可达,唯一的门槛是 LITELLM_MASTER_KEY。

没有任何一个组件发生故障。这是一次纯粹的组合风险:三个都正常的东西拼在一起,得到一个不安全的整体。

这里要把能说和不能说分清楚。

能说:暴露面存在,且只有一层口令。 不能说:它已经被利用了。 也不能说:没人试过——快照里没有访问日志,这两个方向都没有证据。

最便宜的验证方式是:

cat /etc/caddy/Caddyfile

如果它仍然是 reverse_proxy 127.0.0.1:4000,就收紧成按路径放行:

llm.example.com {
    @public path /v1/* /health/liveliness
    handle @public {
        reverse_proxy 127.0.0.1:4000
    }
    handle {
        respond 404
    }
}

管理接口只在服务器本机用——通过 localhost:4000 直接访问,不经过域名。这样公网只剩调用路径。

改完照旧三步:caddy fmt、caddy validate、systemctl reload caddy。

6. 运行时:十一次崩溃与一次归因不能

首次接入数据库前后,容器反复重启 11 次。日志是空的。

日志为空有一个技术解释:强杀时 Python 来不及 flush,缓冲区跟着进程一起没了。所以「没有日志」不等于「没有发生」。

当时 mem_limit 与 memswap_limit 都设成 1200m。去掉 memswap_limit 之后恢复正常。

到这一步,最容易写下的句子是「原因是内存不足,去掉 swap 限制解决了」。但这句是错的,至少是没有证据的。

因为在那段时间里,还有另一件事发生了:首次数据库迁移跑完了。

两个变量同时改变,一个结果恢复正常——这在方法上叫归因不能。到底是内存限制、还是迁移完成,或者两者共同作用,现有材料区分不出来。

要补的证据是三样:

  • dmesg -T | grep -iE 'oom|killed process',看有没有 OOM kill 记录
  • docker inspect 的 OOMKilled 字段
  • 前台带 PYTHONUNBUFFERED=1 跑一次,让日志在崩溃前有机会落盘

还有一个残留风险容易被忽略:去掉 memswap_limit 之后,容器实际可用的是 1200m 内存加 2400m swap。真逼近上限时,它不会干脆地 OOM,而是先进入 swap 抖动——症状更隐蔽,也更难与「这个模型有点慢」区分开。

决策规则写在笔记里:稳态在 900MB 以内,可以考虑把 memswap_limit 加回去;逼近 1200MB,就把 mem_limit 调到 1400m。用一周的观察换一个决定,而不是在崩溃当天拍脑袋。

7. 备份与回滚:803 字节与一分钟

/opt/litellm 下有四个备份文件。

它们的身份全是推测:docker-compose.yml.bak 与 .bak2 被推定为「加内存限制前」和「加 --host 前」,config.yaml.bak 被推定为「加入 chatgpt 模型之前」。没有一处能证明。

最典型的是 config.yaml.bak:它和 config.yaml 同为 803 字节,时间只差一分钟。

803 字节相等,意味着内容很可能也相等;时间只差一分钟,意味着它大概率是改完之后立刻复制的。把它当成「回到加 chatgpt 之前」的回滚点,会退回一个并不存在的中间态。

备份的价值等于时间戳乘以内容可证明性。缺后一项,它就只是另一个文件。

安全层面还有两个细节,都不报错、不告警:

第一个,.env.bak 可能带着 DATABASE_URL。用一条命令确认:

grep -c '^DATABASE_URL=' .env.bak    # 应输出 0

输出 0,才说明「退回无数据库状态」这个回滚方案成立。如果输出 1,回滚之后数据库仍然连着,只是你以为它断了。

第二个,cp 出来的文件默认权限是 644,而原件是 600。密钥副本比原件更松——这类漏洞不会让任何服务出问题,只会让某一天的事故后果变重。

所以第一件事是做一份带日期的基线:

cd /opt/litellm && B=$(date +%Y%m%d)
cp -p docker-compose.yml docker-compose.yml.good-$B
cp -p config.yaml config.yaml.good-$B
cp -p .env .env.good-$B && chmod 600 .env.good-$B

不是因为今天特殊,而是因为「今天正常」这件事本身也会过期:再过两周,你同样说不清那份 .bak 是什么时候的。

回滚本身有四条路径:退回无数据库状态(cp .env.bak .env 后 docker compose up -d --force-recreate,前提是上一条命令输出 0)、退回基线备份、临时停用容器、彻底取消自启。退回无数据库不会影响 Neon 里已创建的项目,之后可以随时再接。

8. 会到期的东西:令牌、额度、时区、告警

配置里最贵的东西不是已经坏掉的,是那些写着「以后会自己坏掉」的。

令牌。ChatGPT 的访问令牌约 10 天有效,登录后会自动刷新。但刷新令牌只能用一次——如果把 auth.json 复制到第二个地方,第一次刷新之后两份副本就会分叉,只有一个能继续用。

所以在这里,「备份即安全」是反的。给令牌做备份,等于给自己埋一个 10 天后才响的故障。笔记里那句「只保留这一份」,是一条比它看起来更硬的纪律。

额度。Neon 免费计划是每项目 100 CU 小时、500MB 存储、空闲 5 分钟休眠。问题在于 LiteLLM 的后台查询可能让它一直不休眠——一个空闲的数据库被自己的监控叫醒,然后把额度用完。额度耗尽后计算资源会被暂停,表现出来就是「数据库忽然连不上」。

这件事的麻烦之处在于:它不是故障,是计费。监控里看不到红色。

计量。chatgpt 系列模型没有定价,所以花费记录恒为 0。max_budget 这个保护装置装在了不导电的线路上——它永远不会触发。这条链上真正起作用的限流只剩 rpm_limit 与 tpm_limit。

时区。cron 按服务器本地时间执行,而本地时间与 UTC 有时差。一条 timedatectl 就能把这个变量消掉,代价是几秒钟。

告警。sshd 的 fail2ban jail 在运行,累计失败数百次,封禁 0 次。这句话有两种读法:要么攻击确实弱,要么 jail 没作用在 <ssh端口> 上。失败数百次说明有人在敲门,如果规则生效,总该有人被拦在外面。

最可疑的是端口迁移留下的缝:jail 匹配 ssh.service,ufw 删掉了 22 的规则,sshd 改听 <ssh端口>。服务名与端口解耦之后,封禁可能落在一个没人经过的位置。

验证不需要等攻击者:

sudo fail2ban-client set sshd banip 192.0.2.1      # TEST-NET-1,不会误伤真实地址
sudo iptables -S | grep 192.0.2.1                  # 或 sudo nft list ruleset
sudo fail2ban-client set sshd unbanip 192.0.2.1

结论现在只能停在「机制未验证」。不写「已失效」——那需要证据,而证据只需要一次 banip 就能拿到,只是还没拿。

一个从不报警的告警器,比没有告警器更危险。因为它提供的是「已经被保护了」的错觉。

9. 运维台账:按「关闭缺口的成本」排序

日常要看的几样东西,都在 /opt/litellm 下执行:

docker inspect litellm --format 'Status={{.State.Status}} RestartCount={{.RestartCount}} OOMKilled={{.State.OOMKilled}}'
docker stats --no-stream litellm
docker logs --tail 100 litellm 2>&1
free -h && swapon --show

健康检查两条:

curl -s http://localhost:4000/health/liveliness     # 应返回 "I'm alive!"
curl -s http://localhost:4000/health/readiness      # 应能看到数据库已连接

启停与重载的几条:

docker compose ps                 # 容器状态
docker compose restart litellm    # 只重启,不重读 .env
docker compose up -d              # 应用 .env / compose 的改动
docker compose stop litellm       # 停止,保留容器
docker start litellm              # 再启动

防火墙只放行三个入口:

ufw allow <ssh端口>/tcp
ufw allow 80/tcp
ufw allow 443/tcp
ufw enable
ufw status numbered

4000 不在列表里。它只听回环,公网本来也到不了;显式 DENY 是为了在规则被误改时留下痕迹。

待办原本按 P0 / P1 / P2 分:今天、本周、持续观察。这个分法按的是紧急度。换一个轴——关闭一个缺口需要多少成本。

待办 关闭哪个缺口 成本 不做的后果
核对 Caddyfile 管理面是否暴露(D 级 → A 级) 一条 cat 继续在公网可达,且不知道
基线备份 「正常」没有可回滚点 四条 cp 下一次变更没有退路
收紧权限 密钥副本 644 两次 chmod 事故后果变重
验证数据库 readiness 是否真连上(C → A) 一次 curl 加日志 以为接了,其实没接
实测 chatgpt 模型 模型上架 ≠ 可用 两次 /v1/responses 客户端拿到 not supported
验证 fail2ban 告警器是否真会响 一次 banip 测试 继续提供「已保护」的错觉

排序之后会发现一件事:最靠前的几项,成本都是一条命令。它们之所以一直没做,不是因为难,是因为做了也不会让任何东西变好——只会让「不知道」变成「知道」。而后者在平时没有反馈。

常见故障的入口也在这张台账上:

现象 先查什么
改了 .env 不生效 是不是用了 docker restart,应改用 docker compose up -d
返回 There are no healthy deployments for this model docker exec litellm ls -l /tokens/chatgpt,确认令牌挂载与 CHATGPT_TOKEN_DIR
日志出现 refresh token failed 刷新令牌失效,需要重新走一次设备码登录
数据库连不上 连接串是否带了 -pooler、Neon 额度是否耗尽
公网域名不通 DNS、ufw 443、Cloudflare SSL 模式、journalctl -u caddy -f

还有这一期与第 003 期的三处分歧。

第一,客户端凭据。003 的正文写「客户端只使用 LiteLLM Master Key」,文末建议却是「为不同客户端创建独立 API Key」。同一篇里两个口径。现在统一为:master key 只做管理和测试,客户端一律用 virtual key。

第二,镜像更新。003 建议「定期更新 Docker 镜像」,现在写的是「不要随意 docker pull,升级单独做,先备份,升级后核对数据库迁移与模型」。

第三,防火墙。003 的步骤是 ufw allow 22/80/443,现在是放行 <ssh端口>、80、443,22 已经没有服务。

这三处不是纠错清单。它们说明的是一件事:部署教程会过期,台账不会。教程写的是某个时刻的正确动作,而系统在之后每一天都在变——变到某个时刻,那份教程里的每条命令都还能执行,但已经不是当下该做的事。

10. 复盘:一套配置能证明什么

  1. 运行时长不是稳定性证据。RestartCount=0 只说明到第 12 分钟还没死过。判断稳定需要的是时间跨度,不是某个瞬间的计数。
  2. 状态表是断言,不是证据。「正常」「运行中」「已配置」都是结论的措辞,除非后面能指到一个 A 级证据,否则它只是把「我不知道」换了个说法。
  3. 风险在组合处生成。数据库、Caddy、LiteLLM 各自正常,合起来打开了管理面。单组件巡检发现不了这类问题,因为没有任何一个组件出错。
  4. 沉默的告警等于没有告警。失败数百次而封禁 0 次,要么规则没生效,要么规则作用在了错误的位置。两种都要求同一件事:测一次。
  5. 同期变更会消灭归因。11 次崩溃的两种可能发生在同一时段,事后再也分不开。要保留归因能力,一次只改一个变量。
  6. 备份的可靠性由来源决定。同为 803 字节、相差一分钟的两个文件,证明不了任何关于「之前」的事。
  7. 会到期的东西要写进日历。令牌 10 天、额度按月、时区跟着机器。它们不会报错,只会在某天变成故障。
  8. 教程与台账的差别在视角。教程回答「怎么做到」,台账回答「现在是什么」。前者会过期,后者要一直更新。

11. 现状与下一步

已确证:LiteLLM 在第 12 分钟存活;4000 只监听回环;ufw 对 4000 是 DENY;Groq 链路在部署阶段从本机、容器、公网三处验证通过;Caddy 当时成功签发证书并返回 via: 1.1 Caddy。

未确证:readiness 与 Neon 是否真正连通;两个 chatgpt 模型能否调用;Caddy 当前内容;fail2ban 的封禁机制;11 次崩溃的根因;ufw 的默认策略;云安全组是否与主机一致。

顺序按依赖排,不按紧急度:

  1. 核对 Caddyfile——它决定后面所有事是否紧急
  2. 做基线——先有可回滚点,再动任何配置
  3. 验证数据库与 chatgpt 模型,把 C 级证据升成 A 级
  4. 验证告警真的会响
  5. 用一周的数据再决定 memswap_limit 与定时重启

配置能跑起来是一件事,知道它为什么能跑、在什么条件下会不跑,是另一件事。这篇记的是后者。


参考

  • 第 003 期《Debian 12 VPS 部署 Groq → LiteLLM → Caddy API 反代全流程》
  • LiteLLM 官方文档:/key/* 接口字段以实例的 /docs Swagger 为准

本文所有状态与数字取自 /opt/litellm 下的运维笔记与配置文件,快照时间为 2026-10-22;不含密钥、连接串与密码。文中 <...> 为脱敏占位符。

COMMENTS / 评论

评论系统尚未开放。你可以通过 来访登记 直接与我联系。