-- 部署至k8s:TaoToken统一Key通道下的deployment.yaml与Secret骨架)
1. 从镜像到 Podone-api 在 k8s 里到底卡在哪上一篇我们把 one-api 打包成了镜像这一步真正把它推进 k8s 集群时问题往往不在镜像本身而在三件事密钥怎么注入、端口怎么对齐、健康探针怎么判断“真的活了”。one-api 是一个开源的 OpenAI 接口聚合与分发网关它能把多个上游渠道统一成一套 Key 对外提供服务适合自建 AI 网关、团队内部统一计费、给多个应用共享一个出口的场景。把它容器化部署到 k8s本质就是让这套网关以 Deployment 的形式常驻用 Secret 托管数据库、Redis、会话密钥用探针保证滚动更新时不把流量打到半成品 Pod 上。我试过直接拿明文环境变量跑结果一次kubectl describe pod里SQL_DSN明晃晃躺在 Events 里排查完赶紧改成 Secret。所以这篇的重点不是“能不能跑”而是“跑得干净、跑得可复制”。下面会交付一份可直接套用的deployment.yaml、Secret和config.toml骨架配合 TaoToken 统一 Key 通道完成密钥注入与服务暴露最后用kubectl apply和就绪验证动作一次性跑通。需要先明确一点one-api 自身监听端口默认是 3000但很多团队会改成 5175 之类避免和集群里其他服务撞车。端口一旦改探针、Service、args 三处必须同步否则 Pod 会一直CrashLoopBackOff或者永远不 Ready。这是本篇最容易踩的坑后面会单独拆开讲。2. 前置准备TaoToken 统一 Key 通道与集群侧检查在写 YAML 之前先把“密钥从哪来”这件事定下来。one-api 需要连接 MySQL 存渠道和额度、连接 Redis 做缓存和会话还需要一个SESSION_SECRET给会话签名。这些都属于敏感信息不该写进镜像也不该写进 ConfigMap。TaoToken 在这里扮演的是统一 Key/API 通道的角色你可以在它的控制台里集中管理上游模型的访问凭证one-api 通过配置引用这套通道避免把多个上游 Key 散落在各个环境变量里。具体操作上先到 TaoToken 控制台创建 API Key地址是https://taotoken.net/api-keys这个页面会生成后续 one-api 渠道配置要用的凭证。如果你还没决定用哪种接入方式可以先看接入文档https://taotoken.net/doc确认 base_url 和鉴权头的写法。对于长期跑编码类 Agent 或需要稳定额度的场景Coding Plan 页面https://taotoken.net/coding-plan里有对应的套餐说明按需选择即可。想先验证模型通不通用模型对话页https://taotoken.net/chat发一条测试请求最直接。集群侧要确认三件事一是kubectl能正常连上目标 namespace二是集群里已经有可用的 MySQL 和 Redis可以是集群内的 Service也可以是外部地址三是镜像已经推到集群能拉取的仓库。可以用下面几条命令快速自检kubectl config current-context kubectl get ns kubectl get svc -A | grep -E mysql|redis如果 MySQL 和 Redis 是集群内的记下它们的 Service 名和端口比如mysql.default.svc.cluster.local:3306、redis.default.svc.cluster.local:6379。这两个地址会写进 Secret 的SQL_DSN和REDIS_CONN_STRING。注意 DSN 的格式是用户:密码tcp(主机:端口)/库名Redis 是redis://主机:端口格式写错 Pod 起不来但日志不一定直白后面排障章节会讲怎么定位。3. 可复制骨架Secret、config.toml 与 deployment.yaml这一节是全文核心三份骨架按顺序创建即可。先建 Secret再建 ConfigMap 挂 config.toml最后 apply Deployment。顺序反了 Pod 会因为找不到 Secret 卡在CreateContainerConfigError。3.1 Secret把敏感信息从 YAML 里赶出去Secret 用stringData写明文kubectl 会自动做 base64 编码比手动echo -n | base64省事且不容易出错。把下面内容存成one-api-secret.yamlapiVersion: v1 kind: Secret metadata: name: one-api-conf namespace: default type: Opaque stringData: sql-dsn: oneapi:YourStrongPasstcp(mysql.default.svc.cluster.local:3306)/one-api redis-conn-string: redis://redis.default.svc.cluster.local:6379 session-secret: replace-with-a-long-random-string三个 key 分别对应数据库连接、Redis 连接、会话密钥。session-secret建议用openssl rand -hex 32生成别用random_string这种。生产环境如果 MySQL 有独立账号把oneapi:YourStrongPass换成实际账号密码。3.2 config.toml端口与日志路径的集中声明one-api 支持用配置文件覆盖部分启动参数。把下面内容存成one-api-configmap.yaml挂到容器里作为/data/config.tomlapiVersion: v1 kind: ConfigMap metadata: name: one-api-config namespace: default data: config.toml: | port 5175 log-dir /data/logs这里把端口定成 5175日志目录定成/data/logs。端口这个值必须和 Deployment 里的args、探针、Service 三处完全一致。日志目录建议挂一个 emptyDir 或 PVC否则 Pod 重建日志就没了排查阶段用 emptyDir 够用长期运行换 PVC。3.3 deployment.yaml探针、args 与 Secret 引用这是最完整的一份直接存成one-api-deployment.yamlapiVersion: apps/v1 kind: Deployment metadata: name: one-api namespace: default labels: app: one-api spec: replicas: 1 selector: matchLabels: app: one-api template: metadata: labels: app: one-api spec: containers: - name: one-api image: your-registry/one-api:1.0.0 args: - --port - 5175 ports: - containerPort: 5175 env: - name: SQL_DSN valueFrom: secretKeyRef: name: one-api-conf key: sql-dsn - name: REDIS_CONN_STRING valueFrom: secretKeyRef: name: one-api-conf key: redis-conn-string - name: SESSION_SECRET valueFrom: secretKeyRef: name: one-api-conf key: session-secret volumeMounts: - name: config mountPath: /data/config.toml subPath: config.toml - name: logs mountPath: /data/logs startupProbe: exec: command: - sh - -c - wget -q -O - http://127.0.0.1:5175/api/status | grep -o \success\:\\s*true initialDelaySeconds: 15 periodSeconds: 10 failureThreshold: 22 readinessProbe: exec: command: - sh - -c - wget -q -O - http://127.0.0.1:5175/api/status | grep -o \success\:\\s*true initialDelaySeconds: 5 periodSeconds: 10 timeoutSeconds: 3 failureThreshold: 3 livenessProbe: exec: command: - sh - -c - wget -q -O - http://127.0.0.1:5175/api/status | grep -o \success\:\\s*true initialDelaySeconds: 30 periodSeconds: 20 timeoutSeconds: 3 failureThreshold: 3 volumes: - name: config configMap: name: one-api-config - name: logs emptyDir: {}几个关键点解释一下。args里的--port 5175是程序启动参数和 config.toml 里的port保持一致双保险。探针用wget请求/api/status这个接口返回的 JSON 里有success:true用grep -o抓出来判断比单纯看 HTTP 200 更准因为 one-api 在数据库没连上时也可能返回 200 但 success 为 false。startupProbe的failureThreshold给到 22是因为首次启动要跑数据库迁移慢的时候几十秒很正常给足时间避免被误杀。readinessProbe和livenessProbe分开前者控制流量接入后者控制重启职责不混。3.4 Service把 5175 暴露出去存成one-api-service.yamlapiVersion: v1 kind: Service metadata: name: one-api namespace: default spec: selector: app: one-api ports: - name: http port: 80 targetPort: 5175 type: ClusterIP集群内访问用http://one-api.default.svc.cluster.local对外暴露再按需加 Ingress 或改type: NodePort。targetPort必须等于容器实际监听端口 5175。4. 验证请求apply 之后怎么确认真的跑通四份 YAML 准备好后按 Secret → ConfigMap → Deployment → Service 的顺序 applykubectl apply -f one-api-secret.yaml kubectl apply -f one-api-configmap.yaml kubectl apply -f one-api-deployment.yaml kubectl apply -f one-api-service.yaml然后盯 Pod 状态kubectl get pods -l appone-api -w正常会看到0/1 Running一段时间后变成1/1 Running。如果一直0/1说明 readinessProbe 没过先别急着删用下面命令看探针失败原因kubectl describe pod -l appone-api | tail -30 kubectl logs -l appone-api --tail100Pod Ready 之后直接在集群内验证接口。起一个临时 Pod 发请求kubectl run curl-test --rm -it --imagecurlimages/curl -- \ curl -s http://one-api.default.svc.cluster.local/api/status返回的 JSON 里应该能看到success:true同时system_name是One APIversion有值。这一步通了说明数据库连接、Redis 连接、会话密钥三样都生效了。接着验证端口转发到本地kubectl port-forward svc/one-api 8080:80浏览器打开http://localhost:8080能看到 one-api 的登录页就说明服务暴露没问题。首次登录用默认管理员账号进去后到渠道页配置 TaoToken 的 API Key 和 base_url具体字段参考接入文档https://taotoken.net/doc。配好渠道后在模型对话页https://taotoken.net/chat发一条测试消息确认整条链路从 one-api 到上游是通的。如果你打算把 one-api 作为长期编码 Agent 的统一出口建议在 TaoToken 的 Coding Plan 页面https://taotoken.net/coding-plan看一下额度方案避免高峰期被限流。控制台https://taotoken.net/console里能看调用量和余额方便和 one-api 的计费对账。5. 本篇常见错排查从 CrashLoop 到探针误杀部署 one-api 到 k8s报错基本集中在下面几类按出现频率排。第一类是CreateContainerConfigErrorPod 起不来describe里写secret one-api-conf not found。原因是 Secret 没建或者 namespace 对不上。确认kubectl get secret -n default里有one-api-conf且 Deployment 的 namespace 和 Secret 一致。如果 Secret 建在别的 namespace要么改 Deployment 的 namespace要么在secretKeyRef里没法跨 namespace 引用只能重建。第二类是CrashLoopBackOff日志里出现dial tcp: lookup mysql on 10.96.0.10:53: no such host。这是SQL_DSN里的主机名解析不了。集群内 Service 的完整域名是服务名.namespace.svc.cluster.local只写mysql在部分 DNS 配置下能解析但跨 namespace 就不行。统一写全限定名最稳。如果 MySQL 在集群外直接写 IP 或外部域名。第三类是探针一直失败但日志显示程序正常。常见原因是端口不一致args写 5175探针却请求 3000或者反过来。三处端口必须完全一致——args、containerPort、探针 URL、Service 的targetPort。改端口时用grep -rn 5175把四份 YAML 全扫一遍别漏。第四类是startupProbe误杀。首次启动数据库迁移慢如果failureThreshold给太小比如 3Pod 会在迁移完成前被重启然后陷入循环。给到 20 以上periodSeconds10 秒等于给 200 秒以上启动窗口足够。等稳定运行后可以适当调小。第五类是日志目录权限问题。如果挂载的 PVC 权限不对one-api 写/data/logs失败会退出。排查阶段先用emptyDir确认程序能跑再换 PVC。emptyDir的日志在 Pod 重建后丢失但排查够用。第六类是 Redis 连接串格式错。redis://redis.default.svc.cluster.local:6379是对的写成redis://redis:6379/0带库号也行但写成redis:6379缺协议头会解析失败。日志里会报invalid redis URL对着改即可。6. 后续动作把 Key 通道和部署串成一条线到这里one-api 在 k8s 里的 Deployment、Secret、config.toml 骨架已经跑通Pod 就绪、接口返回success:true、端口转发能打开登录页。接下来要做的是把 TaoToken 的 Key 通道真正接进 one-api 的渠道配置里让上游调用走统一出口。这一步在 one-api 后台的渠道管理页完成填入 TaoToken 控制台生成的 API Key 和接入文档里给的 base_url保存后发一条测试请求验证。如果后续要扩副本注意 one-api 的会话和额度依赖 Redis 和 MySQL多副本时SESSION_SECRET必须所有副本一致否则会话会串。滚动更新时readinessProbe保证新 Pod 就绪才接流量startupProbe保证慢启动不被误杀这两条已经写在骨架里扩副本直接改replicas即可。密钥轮换时只改 Secret 再kubectl rollout restart deployment one-api不用动镜像。需要长期稳定额度或跑编码 Agent 的话Coding Plan 页面https://taotoken.net/coding-plan有对应方案控制台https://taotoken.net/console可以随时看用量。