OpenClaw 跑 Docker+K8s 万级并发压测:Key 用 TaoToken
发布时间:2026/9/18 21:38:48 作者:尧图编辑部 阅读量:1,286

大促直播开始后的第七分钟告警群开始刷屏单节点上跑着 OpenClaw 的 Docker 容器 CPU 打满AI 调用超时率从 2% 冲到 30%任务队列堆到几万条最后被 OOM Killer 收走进程整场直播的智能客服直接掉线。要把这套服务改造成 Docker K8s 集群来撑住万级并发副本数、探针和 HPA 只是表面功夫底下还有一个必答题模型调用的 Key 和出口怎么统一。这次的做法是先在 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 创建一把 API KeyBase URL 填 https://taotoken.net/api通过 Secret 注入 Deployment再用 JMeter 分布式压测跑一遍用 Pod 日志、监控面板和后台用量三处交叉验证通道到底通没通。节奏上先复盘单机为什么塌再给能直接粘贴的 Deployment 与环境变量接着进 JMeter 的线程组设置最后落到怎么看日志、看用量、怎么在滚动升级时确认模型调用没断。中间所有模型 ID 一律以模型广场当时的列表为准不硬写某个特定串Key 一律用 YOUR_API_KEY 占位别把真 Key 贴在 YAML 里进 Git。1. 直播大促把 OpenClaw 单节点压垮的现场回放1.1 超时、堆积、宕机是三步走不是一步到位那天的曲线很有代表性。开场前 5 分钟 QPS 只有几百一切正常直播间挂上福袋、瞬时进人之后OpenClaw 的请求量在 90 秒内翻了十倍。最先出问题的是响应时间从 400ms 爬到 3s前端开始出现AI 正在思考转圈不结束紧接着是任务队列Redis 里的待处理任务从几百涨到三万消费速度完全跟不上生产速度。第三个阶段才最致命。容器内存被堆在内存里的会话上下文撑满Docker 的 --memory 限制触发 OOM进程被杀K8s 那边如果只有一台宿主机就只能看着服务反复重启。事后复盘这三个症状其实是同一条因果链模型调用变慢 → 工作线程被占住 → 请求堆积 → 内存膨胀 → 进程被杀。所以后面做集群化时重点不是多开几个进程而是把这条链上的每一环拆开处理。1.2 单节点的三个真瓶颈线程池、内存、模型出口第一个瓶颈是线程池。OpenClaw 的 worker 是同步调模型的一个请求占一个线程线程池打满之后新请求只能排队排队的请求在前端看来就是超时。第二个瓶颈是内存它不只是业务数据还有每轮对话的上下文缓存单机内存一旦到顶GC 时间会指数级上升响应时间跟着恶化。第三个瓶颈最容易被忽略就是模型出口。单机直连一家供应商的时候出口的限流、抖动、偶发 5xx 都会原样传导到你的服务上一旦被打满重试又会把请求量翻倍形成放大效应。把出口收敛到一个统一的 API 通道配合 Key 级别的用量观测才能在压测和故障时看清楚到底是自己扛不住还是出口被打回来。这也是后面在 K8s 里做配置注入的直接原因。1.3 上集群之前先定三件事无状态、配置外置、出口统一无状态化是第一件事。会话上下文、任务进度这些必须从容器本地内存挪到 Redis 或数据库否则 Pod 一重建状态就丢滚动升级等于主动制造故障。做法是把本地文件缓存和内存 Map 换成外部存储容器里只留只读的镜像内容。配置外置是第二件事。模型名、Base URL、并发数、超时时间全部进 ConfigMap 或环境变量不要写死在代码里否则每次换模型都要重新构建镜像。出口统一是第三件事也是本篇最想讲透的一件所有 Pod 的模型调用都指向同一个 Base URLKey 从 Secret 注入换 Key 不动镜像扩副本不动配置。2. 物料清单镜像、存储、压测机还有那把 YOUR_API_KEY2.1 集群侧要准备的东西先把清单列清楚不然部署到一半发现缺东西最耗时间。镜像仓库一个能推能拉K8s 集群至少三个可调度节点机器规格按 worker 的 CPU 请求量算通常 4C8G 起步Redis 或 PostgreSQL 一个实例用来放会话和任务队列建议用云上的托管版省得自己维护高可用。压测侧需要几台和集群同网段的机器来跑 JMeter单机线程数不是越多越好2000 线程左右一台比较稳再准备一个域名和 Ingress让压测流量走真实入口而不是 Pod IP否则你测不出 Ingress、Service 这一层的损耗。最后是监控Prometheus Grafana 或云监控都行重点是能看到 QPS、P99、错误率和 Pod 重启次数。2.2 打开 TaoToken 创建 API Key并把 Base URL 记成不带 /v1 的形式这件事本身不复杂但有两个细节特别容易踩。第一Key 要在 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 的落地页注册后进控制台创建创建完立刻复制页面刷新后再想看完整 Key 往往就看不到了。第二填进容器的 Base URL 是 https://taotoken.net/api末尾不带 /v1很多 SDK 会自己拼 /v1/chat/completions你再手写一个 /v1 就变成 /v1/v1/...服务端只会回你 404 或者参数错误。至于模型 ID不要凭记忆写。模型广场里的列表会更新某个日期后缀的型号今天有、下个月可能就下线了。正确的做法是部署前打开控制台看一眼当前可用的模型名复制进 ConfigMap然后在压测日志里确认它被真实调用过。Key、Base URL、模型 ID 这三样凑齐集群侧的准备工作才算有底。2.3 用 Secret 存 Key别写进 Deployment YAMLKey 属于凭证绝不能硬编码在 Deployment 里一旦提交进代码仓库回滚成本远高于多写一行命令。用 kubectl 直接建一个 Secret把值从命令行塞进去YAML 文件里只留引用kubectl create namespace openclaw kubectl -n openclaw create secret generic openclaw-model-secret \ --from-literalapi-keyYOUR_API_KEY之后所有需要 Key 的地方都通过 secretKeyRef 引用包括 Deployment、Job、CronJob。如果团队用 Helm 或者 Kustomize就把 Secret 放到单独的加密层里别和应用清单混在一个目录。这样后面换 Key 只需要改 Secret 再滚动重启镜像、代码、配置都不动。3. openclaw-deployment.yaml让每个 Pod 都从统一通道取模型3.1 三副本起步滚动更新策略选 maxUnavailable: 0副本数是可用性和成本的平衡点。压测阶段建议从 3 副本起步滚动更新时 maxSurge: 1、maxUnavailable: 0这样升级过程中始终有 3 个 Pod 在提供服务不会出现新 Pod 还没起来、旧 Pod 已经停了的空窗。生产环境再根据 HPA 的实测曲线决定最小副本数别一上来就写 20。探针也要配。readinessProbe 决定 Pod 什么时候接流量配错了会出现进程在跑但请求全 503的假活状态。建议暴露一个 /healthz 接口里面至少检查一次到 Redis 的连接和一次轻量的模型连通性探测——注意是轻量探测不是真发一次完整对话否则探针本身就把出口打满了。3.2 一份可直接改的 Deployment 示例下面这份模板里Base URL 和模型 ID 走环境变量Key 走 Secret路径都是真实可用的写法apiVersion: apps/v1 kind: Deployment metadata: name: openclaw-worker namespace: openclaw spec: replicas: 3 strategy: type: RollingUpdate rollingUpdate: maxSurge: 1 maxUnavailable: 0 selector: matchLabels: app: openclaw-worker template: metadata: labels: app: openclaw-worker spec: containers: - name: worker image: registry.example.com/openclaw/worker:1.4.0 ports: - containerPort: 8080 env: - name: OPENCLAW_MODEL_BASE_URL value: https://taotoken.net/api - name: OPENCLAW_MODEL_ID value: YOUR_MODEL_ID - name: OPENCLAW_MODEL_API_KEY valueFrom: secretKeyRef: name: openclaw-model-secret key: api-key - name: OPENCLAW_REQUEST_TIMEOUT_MS value: 30000 readinessProbe: httpGet: path: /healthz port: 8080 initialDelaySeconds: 10 periodSeconds: 5 resources: requests: cpu: 500m memory: 512Mi limits: cpu: 2 memory: 2GiOPENCLAW_MODEL_ID 先留占位部署前换成控制台模型广场里实际存在的那个。资源限制别省requests 决定调度limits 决定它被 OOM 时会不会连累同节点上的其他 Pod。3.3 kubectl apply 之后先看 rollout再看日志命令不多但顺序要对kubectl apply -f openclaw-deployment.yaml kubectl -n openclaw rollout status deploy/openclaw-worker --timeout180s kubectl -n openclaw get pods -o widerollout status 返回 successfully rolled out 只说明 Pod 起来了不代表模型通道通了。接着看日志kubectl -n openclaw logs -f deploy/openclaw-worker --tail200日志里应该出现模型调用的请求记录包含目标地址、模型名、耗时和返回状态。如果看到大量 401 或 timeout先别急着扩副本回到 Key、Base URL、模型 ID 这三样上排查扩容只会把错误放大。4. JMeter 分布式压测万级并发从线程组参数开始4.1 五台压测机各 2000 线程用 jmeter-server 拼出万级并发单台机器跑一万线程基本不现实线程数上去之后压测机自己先趴下测出来的数字没意义。常规做法是分布式压测一台 master 负责汇总四到五台 slave 各跑 2000 线程总并发凑到万级。启动顺序是先在各 slave 上跑 jmeter-server再在 master 上通过 -R 指定远端机器。参数化的目的是一次脚本多组数据复用。把线程数、爬坡时间、持续时间、目标域名都抽成属性命令行覆盖即可jmeter -n -t openclaw-load.jmx \ -R 10.0.1.21,10.0.1.22,10.0.1.23,10.0.1.24,10.0.1.25 \ -Jthreads2000 \ -Jrampup120 \ -Jduration600 \ -Jhostopenclaw.example.com \ -l result.jtl \ -e -o report/爬坡时间别设成 0真实的直播流量是几秒钟冲上来的但压测太陡会把连接建立本身变成瓶颈120 秒爬坡更贴近实际也更容易观察到 HPA 的扩容反应。4.2 压测目标打在 Ingress 上不要直接打 Pod IP这一步和原文第五节的路子一致压测的入口应该是域名或者 Ingress 地址走完整的 DNS → Ingress → Service → Pod 链路。直接打 Pod IP 只测了容器本身Service 的负载均衡、Ingress 的连接复用、K8s 的 kube-proxy 规则全都绕过去了压出来的结论在生产里不成立。同时把 JMeter 的 HTTP 连接超时和响应超时设得比 OpenClaw 的服务端超时略长一点比如服务端 30s压测端设 35s否则服务端还没超时压测端先判失败错误率会虚高排查方向也会被带偏。4.3 压测前先做一次滚动更新让新 Key 生效最容易翻车的地方在这里。很多人改了 Secret 就直接开压测结果 Pod 里的进程用的还是启动时加载的旧环境变量——环境变量在容器启动那一刻就固定了改 Secret 不会自动生效。正确做法是改完 Secret 之后触发一次滚动重启kubectl -n openclaw rollout restart deploy/openclaw-worker kubectl -n openclaw rollout status deploy/openclaw-worker --timeout180s等所有 Pod 都是新版本、且 Ready 之后再启动 JMeter。压测中途不要同时改配置否则日志里新旧两种行为混在一起你分不清哪条错误是新配置造成的。5. 压测中三处交叉验证日志、监控、用量5.1 Pod 日志该看到请求被发出去不该看到 401 和 gap日志是最近的一手证据。压测跑起来之后盯三样一是模型调用的目标地址是否都是统一通道的域名二是返回码分布正常应该是清一色的 200偶尔的 429 可以接受但要记录比例三是耗时分布P99 应该稳定在一个区间里如果随时间持续恶化说明要么出口被限流要么 Pod 已经接近资源上限。如果出现 401几乎可以肯定是 Key 的问题Secret 没更新、Key 复制时带了空格、或者环境变量名和代码里读的不一致。如果出现形如 404 或者 invalid path 的错误先检查 Base URL 后面是不是被人手动补了 /v1。日志里还有一种隐蔽情况请求根本没发出去日志只有任务入队记录没有出站记录那要看线程池和队列配置不是通道的问题。5.2 监控面板盯四条曲线QPS、P99、错误率、Pod 重启次数Grafana 上不用铺满仪表盘四条就够。QPS 反映压测是否按预期打进来P99 反映用户体验错误率反映通道和服务健康度Pod 重启次数反映有没有隐藏的 OOM。这四条一起看判断会快很多。如果 QPS 上不去但压测端显示已发满说明前端链路有阻塞如果 QPS 上去了但 P99 同步飙升、错误率也涨看 HPA 有没有扩容副本数不动就说明指标阈值设高了或者资源不够调度。重启次数不为 0 时立刻去看 Pod 的 describe 和上一次容器的日志别等到压测结束再回头查。5.3 用量对账这次压测到底消耗了多少 Token压测跑完最重要的一件事是对账。回到 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 的控制台看这半小时里通道上的请求量和 Token 消耗和 JMeter 汇总出来的请求数、平均输入输出长度对一下。数量级对得上说明所有 Pod 的模型调用确实都从这个通道走了没有漏网的老配置对不上就去找那个还在用旧地址的 Pod。想更快确认 Key 本身没问题可以在本地用命令行先自测一次注意接口地址同样不带 UTM、不带 /v1npm install -g taotoken/taotoken taotoken cc -k YOUR_API_KEY -u https://taotoken.net/api -m YOUR_MODEL_ID本地能正常返回再去怀疑集群里的 Secret 和环境变量排查路径会短很多。6. 报错对照表与滚动升级、故障转移的断流检查6.1 401 与 Base URL 结尾多了 /v1现象常见根因处理方式401 UnauthorizedSecret 未更新、Key 带空格、环境变量名写错重新 rollout restart打印环境变量名核对404 / invalid pathBase URL 结尾被写成带 /v1 的形式改成 https://taotoken.net/api末尾不加 /v1请求超时无返回出口慢或线程被占满看 P99 和线程池等待队列长度429 比例偏高瞬时并发超过通道侧限制降低爬坡速度配合退避重试这张表覆盖了压测期九成以上的报错。要注意的是401 和 404 都是配置错误扩副本、调内存对它们没有任何帮助方向错了会白白浪费一轮压测。6.2 超时、429 与副本数、HPA 的关系超时不全是通道的锅。当 Pod 的 CPU 接近 limits进程被限流处理单个请求的时间会显著变长表现出来也是超时。判断方法很简单看 Pod 的 CPU 使用率是不是贴着 limits 走。是的话先调 requests 和 limits再看 HPA 是否及时扩容。429 通常来自出口侧的并发保护处理方式是加指数退避重试而不是无脑重试。一个实用技巧是把重试次数限制在 2 次以内并给重试加随机抖动否则所有 Pod 在同一时刻重试会把被限流的瞬间变成雪崩。6.3 CrashLoopBackOff 多半来自探针和资源限制Pod 反复重启先看 describe 的 Events 和上一次容器日志。常见两种原因readinessProbe 的 initialDelaySeconds 太小进程还没初始化完就被判定失败memory limits 给得太紧会话缓存一涨就被 OOM。前者调大初始延迟后者要么调大上限要么把上下文挪到 Redis。如果排查过程中要查数据库里的慢查询让 AI 工具生成诊断 SQL 是没问题的但执行必须由你在本地或者跳板机上用自己的客户端跑再把执行结果贴回对话里分析。让模型直接连生产库执行语句无论从权限还是风险上看都不合适这条线要守住。6.4 滚动升级和删 Pod 时模型调用不断流的验证方法高可用的真正验收场景不是压测峰值而是升级和故障。压测进行中另开一个窗口执行 rollout restart观察 JMeter 的错误率曲线如果配置正确最多出现几条因为连接被切断的瞬时错误整体曲线应该平滑。再狠一点随机删掉一个 Podkubectl -n openclaw delete pod -l appopenclaw-worker --field-selector status.phaseRunning新 Pod 起来接管期间日志里不应该出现 401也不应该出现长时间的空窗。原因是无状态化和统一通道这两件事已经做完了状态在 RedisKey 在 Secret新 Pod 起来就能直接继续消耗 Token 干活不需要任何人工干预。7. 压测跑完后去控制台对一下这次消耗配置跑通不等于可以收工最后一步是对账和固化。压测结束后回到 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 看这次调用是否都记上了请求量、Token 消耗和 JMeter 的汇总数字能不能对上如果差得离谱就回去翻 Pod 日志多半有 Pod 还在用旧地址。日常写代码或者调试压测脚本时可以在 TaoToken 模型对话 里用同一把 Key 发一条测试消息快速确认模型 ID 和 Base URL 没写错。长期跑集群建议看一眼 Coding Plan 的额度是否够压测期的消耗需要给不同环境发不同的 Key就在 控制台 API Keys 里分开创建。要把这套配置写进团队的部署文档环境变量部分可以直接对照 Claude Code 接入文档 里的字段说明改字段名的时候不至于漏项。最后提醒一句压测机上跑出来的万级并发数字只说明你的集群在那一刻扛住了真正的大促流量带着用户行为的随机性建议把这次压测参数固化成定时任务每两周跑一次把 P99 和错误率的变化当成发布前的准入门槛。