1. 从单机脚本到云端运维:OpenClaw 多服务调用大模型的 Key 管理困局
OpenClaw 是一个面向 Agent 场景的开源编排框架,能让你把大模型能力接入到自己的工作流里,适合做自动化任务、多轮对话服务、内部工具集成。它本身不绑定某一家模型,你可以接 OpenAI 兼容接口、接 Anthropic 风格接口,也可以接自建网关。问题恰恰出在“能接很多家”这件事上——当 OpenClaw 从本地单进程跑成云端多容器集群,Key 就开始失控了。
我见过最典型的场景是这样的:本地开发时,.env里塞一个OPENCLAW_API_KEY就完事。上了云,OpenClaw 主服务要调模型,Prometheus 旁边的 exporter 要调模型做异常摘要,Grafana 告警通道里又挂了一个小脚本调模型生成告警描述,再加上 CI 里跑回归测试的容器也要调模型。四个地方四份 Key,有的写在 Compose 的environment里,有的挂在宿主机的~/.bashrc,有的干脆硬编码在 Python 脚本里。结果就是:某家模型厂商调整了配额策略,你只换了主服务的 Key,另外三个服务开始间歇性 401;或者某个 Key 泄露了,你根本不知道是哪个容器在用它。
这就是本文要解决的核心问题:用 TaoToken 统一 Key 和 API 通道,把 OpenClaw 云端部署从“能跑”推进到“可观测、可复现、可排障”的运维基线。整条链路分三段:Docker Compose 容器化起步,TaoToken 统一鉴权接入,Prometheus + Grafana 监控落地。每一段我都给出可直接复制的配置,不讲空话。
先说清楚 TaoToken 在这里扮演什么角色。它是一个统一的模型 API 网关,对外暴露 OpenAI 兼容的/v1/chat/completions等标准端点,对内帮你把不同模型厂商的鉴权、路由、配额收敛到一个 Key 上。对 OpenClaw 来说,它只需要认一个 Base URL 和一个 Key,至于背后实际走的是哪家模型,由 TaoToken 侧配置决定。这样你的 Compose 文件里只出现一个TAOTOKEN_API_KEY,所有容器共享同一个环境变量来源,换 Key 只改一处。
官网入口在 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 端点是 https://taotoken.net/api ,注意 API 地址不带 UTM 参数,配置里写干净的这个就行。下面进入实操。
2. TaoToken 前置准备:拿到统一 Key 并确认 OpenClaw 的接入点
在写 Compose 之前,你得先把 TaoToken 这边的“账号侧”准备好。这一步不复杂,但顺序错了后面会反复返工。
第一步,注册并登录后进入控制台,地址是 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite 。控制台里你能看到当前账号下的模型通道配置和用量概览。对于 OpenClaw 这种多服务场景,我建议你在这里先规划好“一个 Key 对应一个环境”,比如openclaw-prod、openclaw-staging分开建,而不是所有环境共用一个 Key。原因很简单:监控告警里如果发现某个 Key 的调用量异常飙升,你能立刻定位到是哪个环境。
第二步,创建 API Key。入口在 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite 。创建时给它起一个能自解释的名字,比如openclaw-cloud-prod。生成的 Key 通常以sk-开头,只显示一次,复制后立刻存进你的密码管理器或云厂商的 Secrets Manager。不要把它直接写进docker-compose.yml提交到 Git,后面我会讲怎么用.env文件隔离。
第三步,确认你要用的模型 ID。OpenClaw 的模型配置项一般叫model或model_id,它需要和 TaoToken 侧支持的模型标识对齐。你可以在模型对话页面 https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=chat&utm_campaign=rewrite 里先手动发一条测试消息,确认通道可用,同时记下你选的模型标识。这一步很关键——很多人跳过它,结果 Compose 起来后 OpenClaw 报model not found,回头查半天以为是网络问题。
第四步,如果你打算用 Claude Code 或类似的编码 Agent 配合 OpenClaw 做开发调试,可以顺带看一下 Coding Plan 页面 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite ,它解决的是长期编码场景下的额度与通道问题,和本文的运维主线是互补的。接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite ,遇到端点路径不确定时以文档为准。
到这里,你手里应该有三样东西:一个 Base URL(https://taotoken.net/api)、一个 API Key、一个确认可用的 Model ID。这三件套就是后面所有配置的核心。我把它总结成一张对照表,方便你配置时逐项核对:
| 配置项 | 值 | 出现位置 |
|---|---|---|
| Base URL | https://taotoken.net/api | OpenClaw 环境变量、监控 exporter |
| API Key | sk-xxxx(控制台生成) | .env文件,不提交 Git |
| Model ID | 控制台/对话页确认 | OpenClaw 模型配置、告警脚本 |
注意:Base URL 写
https://taotoken.net/api即可,OpenClaw 的 OpenAI 兼容客户端通常会自动拼接/v1/chat/completions。如果你的客户端要求写全路径,就补成https://taotoken.net/api/v1,以接入文档为准。
前置准备做完,接下来进入容器化编排。这里的原则是:所有需要调模型的服务,都从同一个.env读取TAOTOKEN_API_KEY和TAOTOKEN_BASE_URL,不允许任何服务自己硬编码。这条原则是后面监控能对得上账的前提。
3. 可复制配置:Docker Compose 编排 OpenClaw 与统一 Key 注入
这一节是全文的技术核心,我给出一份可以直接落地的docker-compose.yml,包含 OpenClaw 主服务、PostgreSQL、Redis、Prometheus、Grafana 五个服务,以及一个独立的.env文件。你把它放到服务器上,改几个值就能up。
先建目录结构:
mkdir -p /opt/openclaw/{data,prometheus,grafana} cd /opt/openclaw然后创建.env文件,这是所有密钥的唯一来源:
# /opt/openclaw/.env TAOTOKEN_API_KEY=sk-你的真实Key TAOTOKEN_BASE_URL=https://taotoken.net/api OPENCLAW_MODEL_ID=你的模型ID POSTGRES_PASSWORD=换一个强密码 REDIS_PASSWORD=换一个强密码 GRAFANA_ADMIN_PASSWORD=换一个强密码.env文件权限收紧,并且加进.gitignore:
chmod 600 /opt/openclaw/.env echo ".env" >> /opt/openclaw/.gitignore接着是docker-compose.yml。注意 OpenClaw 服务的环境变量注入方式,以及 Prometheus 和 Grafana 的挂载路径:
# /opt/openclaw/docker-compose.yml version: "3.9" services: openclaw: image: openclaw/openclaw:latest container_name: openclaw restart: always ports: - "8000:8000" environment: - OPENCLAW_ENV=production - OPENCLAW_API_BASE=${TAOTOKEN_BASE_URL} - OPENCLAW_API_KEY=${TAOTOKEN_API_KEY} - OPENCLAW_MODEL_ID=${OPENCLAW_MODEL_ID} - DATABASE_URL=postgresql://openclaw:${POSTGRES_PASSWORD}@postgres:5432/openclaw - REDIS_URL=redis://:${REDIS_PASSWORD}@redis:6379/0 volumes: - ./data:/app/data depends_on: postgres: condition: service_healthy redis: condition: service_healthy healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8000/health"] interval: 30s timeout: 5s retries: 3 start_period: 40s postgres: image: postgres:16-alpine container_name: openclaw-postgres restart: always environment: - POSTGRES_DB=openclaw - POSTGRES_USER=openclaw - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} volumes: - ./data/pg:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U openclaw"] interval: 10s timeout: 5s retries: 5 redis: image: redis:7-alpine container_name: openclaw-redis restart: always command: redis-server --requirepass ${REDIS_PASSWORD} volumes: - ./data/redis:/data healthcheck: test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"] interval: 10s timeout: 5s retries: 5 prometheus: image: prom/prometheus:latest container_name: openclaw-prometheus restart: always ports: - "9090:9090" volumes: - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro - ./data/prometheus:/prometheus command: - "--config.file=/etc/prometheus/prometheus.yml" - "--storage.tsdb.retention.time=15d" grafana: image: grafana/grafana:latest container_name: openclaw-grafana restart: always ports: - "3000:3000" environment: - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD} volumes: - ./data/grafana:/var/lib/grafana depends_on: - prometheus这份 Compose 里有几个设计点值得说明。第一,OpenClaw 的OPENCLAW_API_BASE和OPENCLAW_API_KEY都从.env注入,容器内部拿到的就是 TaoToken 的统一地址和 Key,它不感知背后是哪家模型。第二,depends_on配合condition: service_healthy,保证数据库和缓存真正就绪后 OpenClaw 才启动,避免启动顺序导致的连接失败。第三,Prometheus 和 Grafana 的数据都挂到宿主机./data下,容器重建不丢监控历史。
然后是 Prometheus 的采集配置prometheus/prometheus.yml:
# /opt/openclaw/prometheus/prometheus.yml global: scrape_interval: 15s evaluation_interval: 15s scrape_configs: - job_name: "openclaw" metrics_path: "/metrics" static_configs: - targets: ["openclaw:8000"] - job_name: "prometheus" static_configs: - targets: ["localhost:9090"]这里targets写的是openclaw:8000,因为 Compose 默认创建了一个共享网络,服务名可以直接当主机名解析。如果你把 Prometheus 部署在 Compose 之外,就要换成宿主机 IP 或容器网络别名。
启动整套服务:
cd /opt/openclaw docker compose up -d --build查看状态:
docker compose ps正常的话你会看到五个容器都是running,其中openclaw和postgres、redis显示healthy。如果 OpenClaw 一直卡在starting,先看日志:
docker compose logs -f openclaw到这一步,容器化编排和统一 Key 注入就完成了。下一节验证请求是否真的打通了 TaoToken 通道。
4. 验证请求与成功结果:从容器内打到模型端点
配置写完不代表通了。运维的基本素养是:每一步都要有可观测的验证动作。这一节我给你三个层次的验证,从容器内到监控端点,逐层确认。
第一层,验证 OpenClaw 容器能读到正确的环境变量。进入容器打印一下(注意不要echo完整 Key,只看前缀):
docker compose exec openclaw sh -c 'echo $OPENCLAW_API_BASE; echo ${OPENCLAW_API_KEY:0:8}'预期输出是https://taotoken.net/api和sk-xxxxx的前八位。如果 Base URL 是空的,说明.env没被 Compose 读到,检查文件是否在docker-compose.yml同目录、变量名是否拼错。
第二层,从容器内直接调 TaoToken 的模型端点,确认鉴权和模型 ID 都对:
docker compose exec openclaw sh -c 'curl -s -o /dev/null -w "%{http_code}" \ -X POST "$OPENCLAW_API_BASE/v1/chat/completions" \ -H "Authorization: Bearer $OPENCLAW_API_KEY" \ -H "Content-Type: application/json" \ -d "{\"model\":\"$OPENCLAW_MODEL_ID\",\"messages\":[{\"role\":\"user\",\"content\":\"ping\"}]}"'预期返回200。如果返回401,说明 Key 无效或没带上;返回404,多半是 Base URL 路径拼错或模型 ID 不存在;返回429,是配额或频率限制,去控制台看用量。这个命令的好处是它完全走容器内的网络和环境变量,能排除宿主机环境的干扰。
第三层,验证 OpenClaw 自身的健康端点和指标端点:
curl -s http://localhost:8000/health curl -s http://localhost:8000/metrics | head -20/health应该返回类似{"status":"ok"}的 JSON。/metrics应该能看到 Prometheus 格式的指标,比如openclaw_requests_total、openclaw_request_duration_seconds。如果/metrics返回 404,说明你的 OpenClaw 版本没开指标暴露,需要在配置里启用,或者用 sidecar exporter 采集。
第四层,验证 Prometheus 已经抓到目标。打开http://你的服务器IP:9090,进入Status -> Targets,你应该看到openclaw这个 job 的状态是UP。如果显示DOWN,点进去看错误信息,常见的是连接被拒绝或路径不对。
第五层,验证 Grafana 能出图。打开http://你的服务器IP:3000,用.env里的GRAFANA_ADMIN_PASSWORD登录,添加 Prometheus 数据源,地址填http://prometheus:9090(同在 Compose 网络内)。然后新建一个 Panel,查询rate(openclaw_requests_total[5m]),如果能看到曲线,说明整条监控链路通了。
我实测下来,最容易出问题的是第二层和第四层。第二层失败通常是 Key 或模型 ID 的问题,第四层失败通常是网络或路径的问题。把这两层盯住,基本就不会有大坑。
成功的结果应该是:docker compose ps五个容器健康,/health返回 ok,Prometheus Targets 里 openclaw 是 UP,Grafana 能画出请求速率曲线。到这一步,你的 OpenClaw 云端运维基线就立起来了。
5. 本篇常见错排查:401、local proxy failed、reading choices、OAuth
这一节我把实际部署中最常撞到的几类报错摊开讲,每个都给出定位思路和修复动作。这些报错在 OpenClaw + TaoToken 的组合里出现频率很高,提前知道能省很多时间。
报错一:401 Unauthorized。这是最高频的。表现是 OpenClaw 日志里出现401或invalid api key。排查顺序:先在容器内用第 4 节的 curl 命令直接打 TaoToken 端点,如果 curl 也 401,说明 Key 本身有问题——去 API Keys 页面确认 Key 没被删除、没被禁用、复制时没带多余空格。如果 curl 通了但 OpenClaw 还 401,说明 OpenClaw 读到的 Key 和你以为的不一样,用docker compose exec openclaw env | grep -i key看实际值(注意脱敏)。还有一种情况是 Key 正确但请求头格式不对,OpenClaw 某些版本要求Authorization: Bearer sk-xxx,如果你在配置里只填了sk-xxx而没加Bearer前缀,就会 401。
报错二:local proxy failed。这个报错通常出现在 OpenClaw 尝试通过本地代理转发请求时。表现是日志里local proxy failed或connection refused。根因一般是 OpenClaw 配置了一个本地代理地址(比如http://127.0.0.1:7890),但容器内根本没有这个代理进程。修复动作:检查 OpenClaw 的代理相关环境变量,比如HTTP_PROXY、HTTPS_PROXY、ALL_PROXY,把它们从 Compose 里去掉或置空。容器内直连 TaoToken 的https://taotoken.net/api即可,不需要额外代理层。如果你确实需要走网络中间层,那也应该在宿主机层面解决,而不是在容器里配一个不存在的本地地址。
报错三:reading choices 相关错误。典型信息是error reading choices或cannot read property 'choices' of undefined。这说明 OpenClaw 收到了一个不符合 OpenAI 响应结构的返回体。可能原因有三个:一是 TaoToken 侧返回了错误 JSON(比如配额不足的提示),但 OpenClaw 没做错误分支处理;二是模型 ID 写错,端点返回了非预期结构;三是响应被中间层截断。排查动作:先用 curl 拿到原始响应体,看choices字段是否存在。如果返回的是{"error": {...}},那就是鉴权或配额问题,回到报错一处理。如果choices存在但 OpenClaw 还报错,检查 OpenClaw 版本是否过旧,升级到最新镜像。
报错四:OAuth 相关错误。如果你在 OpenClaw 里配置了某些需要 OAuth 流程的模型通道,可能会看到OAuth token expired或invalid_grant。但本文的场景是用 TaoToken 统一 Key,理论上不应该出现 OAuth 流程。如果你撞到了,说明 OpenClaw 的某个插件或子服务还在走旧的 OAuth 配置。修复动作:检查 OpenClaw 的配置文件里是否有残留的 OAuth 相关字段,比如oauth_client_id、refresh_token,把它们清掉,统一改成 API Key 模式。TaoToken 的接入方式就是 Base URL + Key + Model ID 三件套,不需要 OAuth。
报错五:Prometheus target DOWN。这个不算应用报错,但属于运维必查项。表现是 Targets 页面 openclaw 显示 DOWN,错误信息connection refused或context deadline exceeded。前者说明 Prometheus 连不上openclaw:8000,检查两个服务是否在同一 Compose 网络、OpenClaw 是否真的在监听 8000;后者说明网络通但响应超时,可能是 OpenClaw 负载过高或/metrics端点处理慢。修复动作:先docker compose exec prometheus wget -qO- http://openclaw:8000/metrics手动验证连通性,再根据结果调整。
为了让你排查时有个对照,我把这几类报错整理成表:
| 报错关键词 | 最可能根因 | 首选修复动作 |
|---|---|---|
| 401 Unauthorized | Key 错误或请求头格式不对 | 容器内 curl 验证,检查 Bearer 前缀 |
| local proxy failed | 容器内配了不存在的本地代理 | 清空 HTTP_PROXY 等变量 |
| reading choices | 响应结构非预期 | curl 看原始响应,检查模型 ID |
| OAuth invalid_grant | 残留 OAuth 配置 | 清除 OAuth 字段,改 API Key |
| Target DOWN | 网络或端点问题 | 容器内手动 curl /metrics |
注意:排查时优先用容器内的命令,而不是宿主机。因为环境变量和网络命名空间都在容器里,宿主机的结果可能误导你。
6. 语义一致 CTA:把统一 Key 和监控基线固化下来
走到这里,你的 OpenClaw 已经跑在云端,Key 收敛到 TaoToken 一处,Prometheus 和 Grafana 也在采集指标。接下来要做的不是加更多功能,而是把这条基线固化下来,让它可复现、可交接。
第一件事,把.env的管理规范化。生产环境的 Key 不要放在服务器本地文件里裸奔,建议接入云厂商的 Secrets Manager 或至少用 Docker Secrets。如果你团队规模小,至少保证.env权限是 600,并且有备份。换 Key 的流程应该是:在控制台新建 Key,更新.env,docker compose up -d重建 OpenClaw 容器,验证/health和一次模型调用,再删除旧 Key。这个流程写进你的运维手册。
第二件事,把监控告警配上。Prometheus 的告警规则可以写在prometheus/alerts.yml里,然后在prometheus.yml中引用。一个最小可用的告警规则是:当up{job="openclaw"} == 0持续 1 分钟时触发OpenClawDown。Grafana 侧可以配置告警通道,把通知发到你的团队群。告警的价值不在于多,而在于每一条都有人响应。
第三件事,把部署文档写下来。文档里至少包含:服务器规格、Compose 文件位置、.env变量清单(不含真实值)、启动命令、验证命令、常见报错处理。这样下次换人维护,或者你自己三个月后回来看,能快速恢复上下文。
如果你在接入过程中需要确认端点路径、请求头格式、模型 ID 写法,接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite ,遇到不确定的先查文档再改配置。如果你要管理多个环境的 Key,API Keys 页面 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite 可以按环境建 Key,配合监控能快速定位异常来源。如果你打算把 OpenClaw 用在长期编码或 Agent 场景,Coding Plan 页面 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite 里有对应的额度方案,可以按需了解。
最后给你一个我踩过的坑作为收尾:有一次我把 OpenClaw 的OPENCLAW_API_KEY直接写在了docker-compose.yml的environment里,后来换 Key 时只改了.env,忘了 Compose 文件里的硬编码值优先级更高,结果容器一直用旧 Key,排查了半小时才反应过来。从那以后我定了一条规矩:Compose 文件里只出现变量引用,绝不出现真实值。这条规矩帮我省了很多次返工。你把这条守住,统一 Key 的价值才能真正落地。