1. 项目概述为什么要把 OpenClaw 部署到 K8s这真不是“为了上而上”OpenClaw 是一个面向 AI 原生工作流的智能代理网关Agent Gateway它本身不是大模型而是调度、编排、安全管控和能力扩展的“中枢神经系统”。你看到的openclaw命令行工具、本地 Web UI、技能Skill加载、多模型路由、Control UI 权限控制背后都依赖这个网关服务的稳定运行。而把 OpenClaw 部署进 Kubernetes并非赶时髦或堆砌技术名词——它解决的是三个真实、高频、且本地单机部署根本扛不住的硬痛点。第一个痛点是环境一致性灾难。你在 Ubuntu 24.04 上用nvm切换 Node.js 版本装好openclaw跑通了openclaw --version结果一到测试环境就报错node: /lib64/libstdc.so.6: version GLIBCXX_3.4.21 not found或者在 Windows 11 的 WSL2 里调试好CI/CD 流水线用的是 CentOS 7 的构建镜像又卡在cxxabi_1.3.11缺失。这类问题本质是运行时环境碎片化。K8s 的核心价值之一就是用容器镜像把“OpenClaw 运行时 它所依赖的 libc、libstdc、Node.js 二进制、甚至特定版本的glibc”全部打包固化。你部署的不是一段代码而是一个可验证、可复现、可签名的完整运行单元。ghcr.io/openclaw/openclaw:latest这个镜像背后是官方 CI 在标准化构建节点上生成的产物它天然规避了nvm 切换失败、npm install 依赖冲突、系统级动态库版本不匹配这类让运维半夜爬起来的“玄学错误”。第二个痛点是服务暴露与访问模型的错位。OpenClaw 默认绑定127.0.0.1:18789这是为kubectl port-forward这种本地调试场景设计的安全默认值。但当你需要让前端团队通过https://ai-gateway.dev.company.com访问 Control UI让后端服务通过http://openclaw.default.svc.cluster.local:18789调用 API或者让外部微信机器人回调地址指向你的网关时port-forward就彻底失效了。这时候K8s 的 ServiceClusterIP/NodePort/LoadBalancer和 Ingress 就成了必选项。而网络热词里反复出现的ingress nodeport 无法访问、nginx ingress 调度算法恰恰说明很多人卡在了“如何让集群外流量精准、安全、可控地抵达 OpenClaw Pod”这一环。这不是配置几个 YAML 就能搞定的它牵扯到 Ingress Controller 的 TLS 终止策略、Host 头匹配规则、路径重写逻辑、以及 OpenClaw 自身的bindAddress和allowedOrigins安全配置是否协同。第三个痛点是状态管理与弹性伸缩的缺失。OpenClaw 的AGENTS.md、自定义 Skill 文件、运行时生成的 Agent 状态快照、甚至 Gateway 的 token 白名单都需要持久化。本地部署靠~/.openclaw/目录但在 K8s 环境下Pod 是随时可能被驱逐、重建的“牲畜”。没有 PersistentVolumeClaimPVC一次节点故障或滚动更新所有你精心配置的 Agent 指令、技能参数、用户会话状态就全丢了。而 K8s 的 PVC 抽象让你可以把这些状态托管给 NFS、Ceph、或者云厂商的块存储实现真正的“有状态服务无状态化编排”。更进一步当你的 AI 工作流并发量激增kubectl scale deployment/openclaw --replicas3一行命令就能水平扩容背后的 Service 会自动做负载均衡——这比手动在三台服务器上systemctl start openclaw并配置 Nginx 反向代理要可靠、透明、可审计得多。所以这个项目标题里的“部署到 K8s”和“启动外部 node”本质上是在构建一个生产就绪Production-Ready的 AI 代理基础设施底座。它面向的不是单个开发者而是整个 AI 应用研发团队SRE 要它稳定可靠DevOps 要它可 CI/CD 自动化前端要它有标准 HTTPS 入口后端要它有内部 DNS 可寻址安全团队要它满足最小权限原则readOnlyRootFilesystem,drop: ALL。接下来的所有操作都是围绕这三个核心诉求展开的技术落地。2. 核心架构拆解为什么不用 HelmKustomize 才是 OpenClaw 的“天选之子”OpenClaw 官方文档开宗明义“Why not Helm?” 这个反问背后藏着对技术选型极其务实的判断。很多初学者看到 “K8s 部署”第一反应就是找 Helm Chart仿佛没有helm install就不算正经上云。但 OpenClaw 的架构特性让它和 Helm 的“重量级模板引擎”产生了根本性的不匹配。Helm 的核心价值在于处理高度参数化、多环境差异化、且组件间存在强依赖关系的复杂应用。比如 Harbor 仓库它包含 Chartmuseum、Notary、Trivy、Core、Registry、Database、Redis 等十几个微服务每个服务的副本数、资源限制、存储类、TLS 配置、数据库连接字符串都可能因环境dev/staging/prod而异。Helm 的values.yaml和{{ .Values.xxx }}模板语法就是为了优雅地管理这种爆炸式增长的配置维度。但 OpenClaw 是什么它是一个单体容器Single Container核心就是一个openclaw进程其“复杂性”几乎全部集中在应用层的配置文件openclaw.json,AGENTS.md和密钥API Keys, Gateway Token上而非底层的基础设施拓扑。如果强行套用 Helm你会得到一个只有deployment.yaml,service.yaml,configmap.yaml,secret.yaml四个模板的 Chartvalues.yaml里充斥着image.repository,image.tag,service.type,ingress.enabled这些基础字段。这不仅没有带来任何收益反而引入了额外的抽象层和学习成本。更关键的是Helm 的--set或values.yaml无法优雅地处理 OpenClaw 最核心的定制点Agent 内容的版本化管理。AGENTS.md不是简单的键值对它是一份结构化的 Markdown 文档里面可能包含 YAML Front Matter、代码块、链接、甚至是嵌入的 JSON Schema。你不可能也不应该把它塞进values.yaml里去维护。而 Kustomize 的configMapGenerator和secretGenerator则允许你直接将本地的AGENTS.md和openclaw.json文件作为源通过kustomize build时注入到 ConfigMap 中。这意味着你的 Agent 指令变更和代码提交、CI 触发、K8s 部署可以形成一条完全一致的 GitOps 流水线。再看安全性。OpenClaw 的 K8s manifests 明确启用了securityContextrunAsNonRoot: true,runAsUser: 1000,readOnlyRootFilesystem: true,capabilities: drop: [ALL]。这是一个典型的“零信任”容器安全实践。Helm 的模板渲染过程本质上是在客户端你的笔记本上进行的字符串替换它无法在渲染阶段校验最终生成的 YAML 是否符合你的安全基线。而 Kustomize 的bases和overlays模式让你可以把所有安全加固的securityContext配置定义在一个base/security/目录下然后在dev/,staging/,prod/各个 overlay 中统一继承。任何试图在 overlay 中覆盖掉readOnlyRootFilesystem的尝试都会因为违反了基线而被团队的 CI 流水线例如kustomize build | kubeval直接拦截。这是一种“配置即代码”的治理能力Helm 很难原生提供。最后是调试与可观测性。当你执行./scripts/k8s/deploy.sh它内部调用的是kustomize build生成最终的 YAML然后kubectl apply。这个过程是完全透明的。你可以随时运行kustomize build scripts/k8s/manifests/把生成的 YAML 保存下来用kubectl diff对比线上状态或者用kubectl get -f -o yaml导出当前运行态进行逐行比对。而 Helm 的helm template虽然也能做到但它的输出里混杂了大量# Source:注释和helm.sh/chart标签增加了人工阅读的噪音。对于一个以“快速迭代 Agent 技能”为使命的工具来说部署流程的透明、可追溯、可调试其重要性远超“一键安装”的表面便利。因此“不用 Helm”不是技术偏见而是对 OpenClaw 架构本质的深刻理解后做出的最经济、最可控、最符合 GitOps 理念的选择。Kustomize 就像一把精巧的瑞士军刀而 Helm 则像一台功能繁多的数控机床。当你只需要拧紧一颗螺丝部署一个单体网关瑞士军刀的效率和可靠性远胜于为数控机床预热、编程、校准的整个流程。3. 实操核心从零开始部署 OpenClaw 到 K8s 集群含 Ingress 暴露部署不是一个孤立的动作而是一条清晰的、可重复的流水线。我们将严格遵循官方脚本的逻辑但会深入每一个环节的原理和实操细节确保你不仅能跑通更能理解每一步“为什么必须这样”。3.1 环境准备与前置检查别让kubectl成为第一个拦路虎在敲下第一个kubectl命令之前请务必完成以下四步验证。这四步看似简单却是后续所有步骤成功的基石跳过它们90% 的失败都发生在这里。第一步验证kubectl连接与权限运行kubectl cluster-info。你应该看到类似Kubernetes control plane is running at https://...的输出。如果报错The connection to the server ... was refused说明kubectl没有正确配置kubeconfig。对于kind集群./scripts/k8s/create-kind.sh会自动配置对于云厂商集群EKS/GKE/AKS你需要先执行aws eks update-kubeconfig --name ...或gcloud container clusters get-credentials ...。更重要的是权限运行kubectl auth can-i --list确认你有create,get,list,watch,delete等对pods,services,configmaps,secrets,persistentvolumeclaims的权限。一个常见的坑是集群管理员给你分配了一个view角色它只能get不能create导致deploy.sh在创建 Namespace 时就失败。第二步验证 Node.js 与openclawCLI 的兼容性官方脚本deploy.sh是一个 Bash 脚本但它内部会调用openclawCLI 工具来生成某些配置如 Gateway Token。因此你的本地机器上必须安装与 OpenClaw 镜像内版本兼容的 Node.js。查看ghcr.io/openclaw/openclaw:latest的 Dockerfile通常在 GitHub 仓库的Dockerfile文件中你会发现它基于node:20-alpine或node:20-slim。这意味着你的本地 Node.js 版本也应是 v20.x。如果你用nvm执行nvm use 20如果用fnm执行fnm use 20。然后全局安装 CLInpm install -g openclaw。验证openclaw --version应输出vX.X.X。如果遇到openclaw : 无法将“openclaw”项识别为 cmdlet...Windows PowerShell或command not found: openclawLinux/macOS请检查npm的全局 bin 目录是否已加入PATH。在 Linux/macOS 上通常是$(npm config get prefix)/bin在 Windows 上是C:\Users\user\AppData\Roaming\npm。第三步验证 API Key 的有效性与格式OpenClaw 需要至少一个模型提供商的 API Key 才能启动。最常见的错误是 Key 格式错误或权限不足。例如Anthropic 的 Key 必须是以sk-ant-api03-开头的 52 位字符串OpenAI 的 Key 是sk-proj-开头。不要在 Key 前后加空格也不要加引号。在终端中使用export ANTHROPIC_API_KEYsk-ant-api03-...设置环境变量。然后用echo $ANTHROPIC_API_KEY | wc -c检查长度应为 53因为wc -c包含了结尾的换行符。一个更可靠的验证方法是用curl直接测试提供商的健康端点curl -H x-api-key: $ANTHROPIC_API_KEY https://api.anthropic.com/v1/health。如果返回{status:ok}说明 Key 有效如果返回401 Unauthorized说明 Key 错误或已过期。第四步验证存储类StorageClass可用性OpenClaw 的pvc.yaml申请了一个 10Gi 的 PersistentVolumeClaim。在kind或k3s这样的轻量级集群中通常有默认的standard存储类。但在云厂商集群中你需要确认默认存储类是否存在且可用。运行kubectl get storageclass。你应该看到至少一个DEFAULT标记的存储类。如果没有你需要先创建一个或者修改pvc.yaml中的storageClassName字段指定一个你集群中实际存在的存储类名如gp2for EKS,standard-rwofor GKE。一个经典错误是pvc状态长期卡在Pendingkubectl describe pvc显示no persistent volumes available for this claim and no storage class is set这就是存储类缺失的明确信号。完成这四步后你的环境才算真正准备好。记住K8s 的哲学是“声明式”但声明的前提是“环境可信”。每一次kubectl apply的成功都建立在这四块基石牢固的基础上。3.2 执行部署脚本deploy.sh的内部逻辑与关键参数解析./scripts/k8s/deploy.sh是整个部署流程的“大脑”。它不是一个黑盒而是一个结构清晰、职责分明的 Bash 脚本。理解它的内部逻辑是你掌控部署过程的关键。脚本的核心流程可以分解为四个阶段阶段一参数解析与环境初始化脚本首先解析命令行参数--create-secret,--show-token,--delete,--namespace。--delete是一个特殊开关它会跳过所有部署逻辑直接执行清理。--namespace参数会覆盖环境变量OPENCLAW_NAMESPACE。接着它会检查必需的环境变量ANTHROPIC_API_KEY,OPENAI_API_KEY等。这里有一个精妙的设计脚本不会强制要求你设置所有 Key它只会把你当前export出来的 Key 加入 Secret。这意味着你可以先只设置ANTHROPIC_API_KEY部署后续再export OPENAI_API_KEY... ./deploy.sh --create-secret来追加而不会丢失已有的 Anthropic Key。这是对“渐进式部署”理念的完美支持。阶段二Namespace 与 Secret 的创建脚本首先创建一个 Namespace默认openclaw。这是 K8s 的资源隔离边界所有 OpenClaw 的资源都将在其中运行。然后它进入最关键的 Secret 创建环节。脚本会生成一个随机的 32 字节 Gateway Token用于 Control UI 登录并将所有*_API_KEY环境变量的值连同这个 Token一起写入一个临时的 YAML 文件。这个临时文件的内容类似于apiVersion: v1 kind: Secret metadata: name: openclaw-secrets namespace: openclaw type: Opaque data: ANTHROPIC_API_KEY: c2stYW50LWFwaTAzLX... OPENCLAW_GATEWAY_TOKEN: ZjM0ZTQyYzEt...注意data字段下的值是 Base64 编码的。脚本会调用kubectl apply -f temp-file来创建这个 Secret。为什么是data而不是stringData因为stringData是 K8s 1.12 引入的便捷字段它会在提交时自动 Base64 编码。但deploy.sh选择显式编码是为了最大程度的兼容性确保脚本能在任何 K8s 版本上运行。这也是一个重要的工程实践在不确定目标环境时选择最保守、最兼容的方案。阶段三Kustomize 构建与资源应用这是脚本的主干。它执行kustomize build scripts/k8s/manifests/。kustomization.yaml文件位于scripts/k8s/manifests/目录下其内容定义了resources: 列出了所有要包含的 YAML 文件deployment.yaml,service.yaml,pvc.yaml,configmap.yaml。patchesStrategicMerge: 可选可以在此处添加对 Deployment 的补丁例如修改replicas。configMapGenerator: 指定了configmap.yaml的内容来源即AGENTS.md和openclaw.json文件。secretGenerator: 可选如果需要可以在此处生成其他 Secret。kustomize build的输出是一个完整的、可直接kubectl apply的 YAML 清单。脚本会将此清单通过管道传递给kubectl apply -f -。这个过程是原子的要么所有资源都创建成功要么全部失败回滚K8s 的apply语义保证。阶段四状态验证与 Token 输出部署完成后脚本会执行kubectl wait --forconditionavailable --timeout120s deployment/openclaw -n openclaw等待 Deployment 的所有 Pod 进入Available状态。这是一个健壮性检查确保 OpenClaw 进程真的启动并监听了端口。如果超时脚本会报错。最后如果指定了--show-token脚本会执行kubectl get secret openclaw-secrets -n openclaw -o jsonpath{.data.OPENCLAW_GATEWAY_TOKEN} | base64 -d将 Token 解码并打印出来。这个 Token 就是登录http://localhost:18789的密码。关键参数实战指南./deploy.sh --create-secret: 当你修改了 API Key只想更新 Secret 而不重新部署整个应用时使用。它会删除旧 Secret 并创建新 Secret然后触发 Deployment 的滚动更新因为 Secret 是作为 Volume Mount 进入 Pod 的挂载点变化会触发重启。OPENCLAW_NAMESPACEmy-ai-gateway ./deploy.sh: 将 OpenClaw 部署到名为my-ai-gateway的 Namespace而不是默认的openclaw。这对于多租户环境或避免命名冲突至关重要。./deploy.sh --delete: 彻底清理。它会执行kubectl delete namespace openclaw这会级联删除该 Namespace 下的所有资源包括 PVC。注意PVC 删除意味着所有 Agent 状态数据将永久丢失生产环境中应先备份 PVC 中的数据。3.3 暴露服务从port-forward到 Ingress 的完整链路打通kubectl port-forward svc/openclaw 18789:18789 -n openclaw是开发者的“生命线”但它只是一个临时的、单向的隧道。要让 OpenClaw 真正成为一个可被外部系统集成的网关我们必须走完从 Service 到 Ingress 的完整链路。第一步理解 OpenClaw 的绑定模式Bind Address这是整个暴露过程的“总开关”。OpenClaw 的openclaw.json配置文件中有一个关键字段bindAddress。官方默认值是127.0.0.1:18789。这意味着OpenClaw 进程只监听 Pod 内部的回环地址拒绝来自 Pod 外部包括同一 Node 上的其他 Pod更不用说集群外的任何连接。这是极致的安全默认值但也是Ingress无法工作的根本原因。IngressController如 Nginx需要能够通过 ClusterIP Service 访问到 OpenClaw Pod 的 IP 和端口而127.0.0.1是无法被外部访问的。解决方案修改bindAddress你需要编辑scripts/k8s/manifests/configmap.yaml文件中的openclaw.json部分。将bindAddress: 127.0.0.1:18789改为bindAddress: 0.0.0.0:18789。0.0.0.0表示监听所有网络接口。但这还不够仅仅改bindAddress会让 OpenClaw 暴露在不设防的状态下。你还必须配置allowedOrigins这是一个白名单用于防止跨站请求伪造CSRF。例如如果你的 Ingress Host 是ai-gateway.dev.company.com那么allowedOrigins应设置为[https://ai-gateway.dev.company.com]。完整的openclaw.json片段如下{ bindAddress: 0.0.0.0:18789, allowedOrigins: [https://ai-gateway.dev.company.com], gatewayToken: ${GATEWAY_TOKEN}, ... }注意${GATEWAY_TOKEN}是一个占位符kustomize会在构建时将其替换为 Secret 中的实际 Token。第二步创建 Ingress 资源在scripts/k8s/manifests/目录下创建一个新的文件ingress.yamlapiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: openclaw-ingress namespace: openclaw annotations: nginx.ingress.kubernetes.io/ssl-redirect: true nginx.ingress.kubernetes.io/force-ssl-redirect: true spec: ingressClassName: nginx tls: - hosts: - ai-gateway.dev.company.com secretName: openclaw-tls-secret rules: - host: ai-gateway.dev.company.com http: paths: - path: / pathType: Prefix backend: service: name: openclaw port: number: 18789这个 Ingress 资源告诉 Nginx Ingress Controller当收到Host: ai-gateway.dev.company.com的 HTTPS 请求时将流量转发到openclawService 的18789端口。tls部分指定了一个名为openclaw-tls-secret的 Secret它必须包含你的 TLS 证书和私钥。你可以用kubectl create secret tls openclaw-tls-secret --certtls.crt --keytls.key -n openclaw来创建它。第三步更新kustomization.yaml并重新部署将新创建的ingress.yaml添加到kustomization.yaml的resources列表中resources: - deployment.yaml - service.yaml - pvc.yaml - configmap.yaml - ingress.yaml # -- 新增这一行然后执行./scripts/k8s/deploy.sh。脚本会重新构建所有 YAML并应用新的 Ingress 资源。第四步DNS 与最终验证最后一步是将你的域名ai-gateway.dev.company.com解析到 Ingress Controller 的 LoadBalancer IP云环境或 NodePort IP裸机环境。你可以通过kubectl get svc -n ingress-nginx ingress-nginx-controller获取其外部 IP。然后在浏览器中访问https://ai-gateway.dev.company.com。如果一切顺利你应该看到 OpenClaw 的 Control UI 登录页面并能用kubectl get secret openclaw-secrets -n openclaw -o jsonpath{.data.OPENCLAW_GATEWAY_TOKEN} | base64 -d获取的 Token 成功登录。提示如果访问时遇到502 Bad Gateway首要排查点是allowedOrigins配置。打开浏览器开发者工具的 Network 标签页查看请求的Origin头确保它精确匹配allowedOrigins数组中的某一项。https://ai-gateway.dev.company.com和https://ai-gateway.dev.company.com/是不同的 Origin。4. 深度避坑指南那些官方文档没写的“血泪教训”在数十次不同环境kind,k3s,EKS,GKE的 OpenClaw 部署实践中我总结了以下几条“踩过坑之后才懂”的经验。它们不像kubectl apply那样写在文档里但却是决定项目成败的关键。4.1 “Node.js 版本地狱”的终极解法永远相信镜像而非本地网络热词中反复出现的node: /lib64/libstdc.so.6: version cxxabi_1.3.11 not found其根源在于Node.js 二进制与其运行时依赖的 C 标准库libstdc版本不匹配。这个问题在混合环境如 CentOS 7 构建Ubuntu 24.04 运行中尤为突出。很多人的第一反应是升级系统libstdc但这在生产服务器上是高危操作可能破坏其他关键服务。我的解法是彻底放弃在宿主机上运行openclawCLI转而使用官方镜像内的 CLI。OpenClaw 的官方镜像ghcr.io/openclaw/openclaw:latest不仅包含了运行网关的二进制也包含了openclawCLI 工具。你可以这样使用它# 不再依赖本地 Node.js直接用容器内的 CLI docker run --rm -v $(pwd):/workspace -w /workspace \ ghcr.io/openclaw/openclaw:latest \ openclaw generate-config --output openclaw.json这个命令会挂载当前目录到容器内然后在容器内执行openclaw generate-config生成的openclaw.json会直接写入你的本地磁盘。同理你可以用docker run ... openclaw list-skills来验证 Skill 文件的语法。这种方法将 Node.js 运行时的复杂性完全封装在容器内你的宿主机只需要一个docker命令即可彻底规避了所有libstdc、glibc版本冲突的问题。这是 DevOps 领域“不可变基础设施”思想的完美体现环境即镜像而非一堆需要手动维护的软件包。4.2 Ingress “无法访问”的七层排查法从 DNS 到allowedOrigins当ingress nodeport 无法访问时新手往往陷入无头苍蝇式的乱试。我总结了一套从外到内的七层排查法按顺序执行99% 的问题都能定位DNS 层nslookup ai-gateway.dev.company.com。确认域名解析到了正确的 IP 地址Ingress Controller 的 IP。网络层telnet ai-gateway.dev.company.com 443或nc -zv ai-gateway.dev.company.com 443。确认 TCP 连接能建立。如果失败检查防火墙、安全组、Ingress Controller 的 Service 是否是LoadBalancer类型且健康。Ingress Controller 层kubectl logs -n ingress-nginx deploy/ingress-nginx-controller | grep ai-gateway.dev.company.com。查看 Ingress Controller 的日志确认它是否收到了你的请求以及它是否成功匹配到了openclaw-ingress规则。Service 层kubectl get endpoints -n openclaw svc/openclaw。确认 Service 的 Endpoints 列表中有 OpenClaw Pod 的 IP 和端口。如果为空说明 Service 的selector与 Pod 的labels不匹配或者 Pod 本身没有 Running。Pod 层kubectl exec -n openclaw deploy/openclaw -- netstat -tuln | grep 18789。进入 Pod 内部确认openclaw进程确实在0.0.0.0:18789上监听而不是127.0.0.1:18789。应用层HTTPkubectl exec -n openclaw deploy/openclaw -- curl -v http://localhost:18789/health。在 Pod 内部用curl直接访问自己的健康检查端点确认 OpenClaw 进程本身是健康的。安全层CORS这是最容易被忽略的第七层。打开浏览器开发者工具查看 Network 请求的 Response Headers寻找Access-Control-Allow-Origin。如果不存在或者其值不是你请求的Origin那么问题就出在openclaw.json的allowedOrigins配置上。关键技巧allowedOrigins必须是完整的协议域名端口如果非标准端口且必须是数组形式。[*]是不安全的且 OpenClaw 默认禁用它。4.3 PVC 数据持久化的“隐形杀手”subPath与权限陷阱OpenClaw 的pvc.yaml申请了一个 10Gi 的 PVC用于挂载/app/data目录。但很多用户发现Pod 启动后/app/data目录是空的或者写入文件后重启就丢失了。这通常不是 PVC 本身的问题而是挂载方式的陷阱。在deployment.yaml中PVC 是通过volumeMounts挂载的volumeMounts: - name: data mountPath: /app/data subPath: data # -- 这个 subPath 是罪魁祸首subPath的作用是只挂载 PVC 中的某个子目录而不是整个 PVC。如果 PVC 的底层存储如 NFS中/data目录不存在K8s 不会自动创建它而是挂载失败导致/app/data成为一个空目录。更糟的是如果subPath指向一个文件而非目录挂载也会失败。我的建议是移除subPath直接挂载整个 PVC。修改deployment.yamlvolumeMounts: - name: data mountPath: /app/data # 移除 subPath: data 这一行同时确保你的openclaw.json中的dataDir字段也指向/app/data。这样OpenClaw 会直接在 PVC 的根目录下创建它所需的所有子目录agents,skills,state等由 K8s 自动保证其存在性和持久性。另一个常见陷阱是权限。OpenClaw 以 UID 1000 运行但某些存储后端如 NFS的 root_squash 选项会将 UID 1000 的写入映射为nobody用户导致权限被拒绝。解决方案是在deployment.yaml的securityContext中显式设置fsGroup: 1000securityContext: fsGroup: 1000fsGroup会确保挂载的卷PVC的组所有权被设置为 1000从而让 UID 1000 的进程拥有读写权限。这是一个 K8s 中处理存储权限的黄金法则。4.4 CI/CD 自动化部署如何让deploy.sh安全地跑在流水线里将./scripts/k8s/deploy.sh集成到 CI/CD如 GitLab CI中是实现 GitOps 的关键一步。但直接在流水线中export ANTHROPIC_API_KEY...是极度危险的因为 Key 会出现在流水线日志中。安全的 CI/CD 集成方案使用 CI/CD 的密钥管理功能在 GitLab CI 中将ANTHROPIC_API_KEY定义为一个Protected Variable并勾选Mask variable。这样它在日志中会显示为***。在.gitlab-ci.yml中通过env将其注入脚本deploy-to-dev: stage: deploy image: alpine:latest before_script: - apk add --no-cache bash curl jq kubectl kustomize - export KUBECONFIG/dev/null # 防止干扰 script: - | # 将 CI 变量注入到 deploy.sh 的执行环境中 ANTHROPIC_API_KEY$ANTHROPIC_API_KEY \ OPENCLAW_NAMESPACEdev-openclaw \ ./scripts/k8s/deploy.sh environment: dev**最关键的一点在deploy.sh中
OpenClaw K8s部署指南:环境一致性、Ingress暴露与PVC持久化
发布时间:2026/7/16 3:12:59
1. 项目概述为什么要把 OpenClaw 部署到 K8s这真不是“为了上而上”OpenClaw 是一个面向 AI 原生工作流的智能代理网关Agent Gateway它本身不是大模型而是调度、编排、安全管控和能力扩展的“中枢神经系统”。你看到的openclaw命令行工具、本地 Web UI、技能Skill加载、多模型路由、Control UI 权限控制背后都依赖这个网关服务的稳定运行。而把 OpenClaw 部署进 Kubernetes并非赶时髦或堆砌技术名词——它解决的是三个真实、高频、且本地单机部署根本扛不住的硬痛点。第一个痛点是环境一致性灾难。你在 Ubuntu 24.04 上用nvm切换 Node.js 版本装好openclaw跑通了openclaw --version结果一到测试环境就报错node: /lib64/libstdc.so.6: version GLIBCXX_3.4.21 not found或者在 Windows 11 的 WSL2 里调试好CI/CD 流水线用的是 CentOS 7 的构建镜像又卡在cxxabi_1.3.11缺失。这类问题本质是运行时环境碎片化。K8s 的核心价值之一就是用容器镜像把“OpenClaw 运行时 它所依赖的 libc、libstdc、Node.js 二进制、甚至特定版本的glibc”全部打包固化。你部署的不是一段代码而是一个可验证、可复现、可签名的完整运行单元。ghcr.io/openclaw/openclaw:latest这个镜像背后是官方 CI 在标准化构建节点上生成的产物它天然规避了nvm 切换失败、npm install 依赖冲突、系统级动态库版本不匹配这类让运维半夜爬起来的“玄学错误”。第二个痛点是服务暴露与访问模型的错位。OpenClaw 默认绑定127.0.0.1:18789这是为kubectl port-forward这种本地调试场景设计的安全默认值。但当你需要让前端团队通过https://ai-gateway.dev.company.com访问 Control UI让后端服务通过http://openclaw.default.svc.cluster.local:18789调用 API或者让外部微信机器人回调地址指向你的网关时port-forward就彻底失效了。这时候K8s 的 ServiceClusterIP/NodePort/LoadBalancer和 Ingress 就成了必选项。而网络热词里反复出现的ingress nodeport 无法访问、nginx ingress 调度算法恰恰说明很多人卡在了“如何让集群外流量精准、安全、可控地抵达 OpenClaw Pod”这一环。这不是配置几个 YAML 就能搞定的它牵扯到 Ingress Controller 的 TLS 终止策略、Host 头匹配规则、路径重写逻辑、以及 OpenClaw 自身的bindAddress和allowedOrigins安全配置是否协同。第三个痛点是状态管理与弹性伸缩的缺失。OpenClaw 的AGENTS.md、自定义 Skill 文件、运行时生成的 Agent 状态快照、甚至 Gateway 的 token 白名单都需要持久化。本地部署靠~/.openclaw/目录但在 K8s 环境下Pod 是随时可能被驱逐、重建的“牲畜”。没有 PersistentVolumeClaimPVC一次节点故障或滚动更新所有你精心配置的 Agent 指令、技能参数、用户会话状态就全丢了。而 K8s 的 PVC 抽象让你可以把这些状态托管给 NFS、Ceph、或者云厂商的块存储实现真正的“有状态服务无状态化编排”。更进一步当你的 AI 工作流并发量激增kubectl scale deployment/openclaw --replicas3一行命令就能水平扩容背后的 Service 会自动做负载均衡——这比手动在三台服务器上systemctl start openclaw并配置 Nginx 反向代理要可靠、透明、可审计得多。所以这个项目标题里的“部署到 K8s”和“启动外部 node”本质上是在构建一个生产就绪Production-Ready的 AI 代理基础设施底座。它面向的不是单个开发者而是整个 AI 应用研发团队SRE 要它稳定可靠DevOps 要它可 CI/CD 自动化前端要它有标准 HTTPS 入口后端要它有内部 DNS 可寻址安全团队要它满足最小权限原则readOnlyRootFilesystem,drop: ALL。接下来的所有操作都是围绕这三个核心诉求展开的技术落地。2. 核心架构拆解为什么不用 HelmKustomize 才是 OpenClaw 的“天选之子”OpenClaw 官方文档开宗明义“Why not Helm?” 这个反问背后藏着对技术选型极其务实的判断。很多初学者看到 “K8s 部署”第一反应就是找 Helm Chart仿佛没有helm install就不算正经上云。但 OpenClaw 的架构特性让它和 Helm 的“重量级模板引擎”产生了根本性的不匹配。Helm 的核心价值在于处理高度参数化、多环境差异化、且组件间存在强依赖关系的复杂应用。比如 Harbor 仓库它包含 Chartmuseum、Notary、Trivy、Core、Registry、Database、Redis 等十几个微服务每个服务的副本数、资源限制、存储类、TLS 配置、数据库连接字符串都可能因环境dev/staging/prod而异。Helm 的values.yaml和{{ .Values.xxx }}模板语法就是为了优雅地管理这种爆炸式增长的配置维度。但 OpenClaw 是什么它是一个单体容器Single Container核心就是一个openclaw进程其“复杂性”几乎全部集中在应用层的配置文件openclaw.json,AGENTS.md和密钥API Keys, Gateway Token上而非底层的基础设施拓扑。如果强行套用 Helm你会得到一个只有deployment.yaml,service.yaml,configmap.yaml,secret.yaml四个模板的 Chartvalues.yaml里充斥着image.repository,image.tag,service.type,ingress.enabled这些基础字段。这不仅没有带来任何收益反而引入了额外的抽象层和学习成本。更关键的是Helm 的--set或values.yaml无法优雅地处理 OpenClaw 最核心的定制点Agent 内容的版本化管理。AGENTS.md不是简单的键值对它是一份结构化的 Markdown 文档里面可能包含 YAML Front Matter、代码块、链接、甚至是嵌入的 JSON Schema。你不可能也不应该把它塞进values.yaml里去维护。而 Kustomize 的configMapGenerator和secretGenerator则允许你直接将本地的AGENTS.md和openclaw.json文件作为源通过kustomize build时注入到 ConfigMap 中。这意味着你的 Agent 指令变更和代码提交、CI 触发、K8s 部署可以形成一条完全一致的 GitOps 流水线。再看安全性。OpenClaw 的 K8s manifests 明确启用了securityContextrunAsNonRoot: true,runAsUser: 1000,readOnlyRootFilesystem: true,capabilities: drop: [ALL]。这是一个典型的“零信任”容器安全实践。Helm 的模板渲染过程本质上是在客户端你的笔记本上进行的字符串替换它无法在渲染阶段校验最终生成的 YAML 是否符合你的安全基线。而 Kustomize 的bases和overlays模式让你可以把所有安全加固的securityContext配置定义在一个base/security/目录下然后在dev/,staging/,prod/各个 overlay 中统一继承。任何试图在 overlay 中覆盖掉readOnlyRootFilesystem的尝试都会因为违反了基线而被团队的 CI 流水线例如kustomize build | kubeval直接拦截。这是一种“配置即代码”的治理能力Helm 很难原生提供。最后是调试与可观测性。当你执行./scripts/k8s/deploy.sh它内部调用的是kustomize build生成最终的 YAML然后kubectl apply。这个过程是完全透明的。你可以随时运行kustomize build scripts/k8s/manifests/把生成的 YAML 保存下来用kubectl diff对比线上状态或者用kubectl get -f -o yaml导出当前运行态进行逐行比对。而 Helm 的helm template虽然也能做到但它的输出里混杂了大量# Source:注释和helm.sh/chart标签增加了人工阅读的噪音。对于一个以“快速迭代 Agent 技能”为使命的工具来说部署流程的透明、可追溯、可调试其重要性远超“一键安装”的表面便利。因此“不用 Helm”不是技术偏见而是对 OpenClaw 架构本质的深刻理解后做出的最经济、最可控、最符合 GitOps 理念的选择。Kustomize 就像一把精巧的瑞士军刀而 Helm 则像一台功能繁多的数控机床。当你只需要拧紧一颗螺丝部署一个单体网关瑞士军刀的效率和可靠性远胜于为数控机床预热、编程、校准的整个流程。3. 实操核心从零开始部署 OpenClaw 到 K8s 集群含 Ingress 暴露部署不是一个孤立的动作而是一条清晰的、可重复的流水线。我们将严格遵循官方脚本的逻辑但会深入每一个环节的原理和实操细节确保你不仅能跑通更能理解每一步“为什么必须这样”。3.1 环境准备与前置检查别让kubectl成为第一个拦路虎在敲下第一个kubectl命令之前请务必完成以下四步验证。这四步看似简单却是后续所有步骤成功的基石跳过它们90% 的失败都发生在这里。第一步验证kubectl连接与权限运行kubectl cluster-info。你应该看到类似Kubernetes control plane is running at https://...的输出。如果报错The connection to the server ... was refused说明kubectl没有正确配置kubeconfig。对于kind集群./scripts/k8s/create-kind.sh会自动配置对于云厂商集群EKS/GKE/AKS你需要先执行aws eks update-kubeconfig --name ...或gcloud container clusters get-credentials ...。更重要的是权限运行kubectl auth can-i --list确认你有create,get,list,watch,delete等对pods,services,configmaps,secrets,persistentvolumeclaims的权限。一个常见的坑是集群管理员给你分配了一个view角色它只能get不能create导致deploy.sh在创建 Namespace 时就失败。第二步验证 Node.js 与openclawCLI 的兼容性官方脚本deploy.sh是一个 Bash 脚本但它内部会调用openclawCLI 工具来生成某些配置如 Gateway Token。因此你的本地机器上必须安装与 OpenClaw 镜像内版本兼容的 Node.js。查看ghcr.io/openclaw/openclaw:latest的 Dockerfile通常在 GitHub 仓库的Dockerfile文件中你会发现它基于node:20-alpine或node:20-slim。这意味着你的本地 Node.js 版本也应是 v20.x。如果你用nvm执行nvm use 20如果用fnm执行fnm use 20。然后全局安装 CLInpm install -g openclaw。验证openclaw --version应输出vX.X.X。如果遇到openclaw : 无法将“openclaw”项识别为 cmdlet...Windows PowerShell或command not found: openclawLinux/macOS请检查npm的全局 bin 目录是否已加入PATH。在 Linux/macOS 上通常是$(npm config get prefix)/bin在 Windows 上是C:\Users\user\AppData\Roaming\npm。第三步验证 API Key 的有效性与格式OpenClaw 需要至少一个模型提供商的 API Key 才能启动。最常见的错误是 Key 格式错误或权限不足。例如Anthropic 的 Key 必须是以sk-ant-api03-开头的 52 位字符串OpenAI 的 Key 是sk-proj-开头。不要在 Key 前后加空格也不要加引号。在终端中使用export ANTHROPIC_API_KEYsk-ant-api03-...设置环境变量。然后用echo $ANTHROPIC_API_KEY | wc -c检查长度应为 53因为wc -c包含了结尾的换行符。一个更可靠的验证方法是用curl直接测试提供商的健康端点curl -H x-api-key: $ANTHROPIC_API_KEY https://api.anthropic.com/v1/health。如果返回{status:ok}说明 Key 有效如果返回401 Unauthorized说明 Key 错误或已过期。第四步验证存储类StorageClass可用性OpenClaw 的pvc.yaml申请了一个 10Gi 的 PersistentVolumeClaim。在kind或k3s这样的轻量级集群中通常有默认的standard存储类。但在云厂商集群中你需要确认默认存储类是否存在且可用。运行kubectl get storageclass。你应该看到至少一个DEFAULT标记的存储类。如果没有你需要先创建一个或者修改pvc.yaml中的storageClassName字段指定一个你集群中实际存在的存储类名如gp2for EKS,standard-rwofor GKE。一个经典错误是pvc状态长期卡在Pendingkubectl describe pvc显示no persistent volumes available for this claim and no storage class is set这就是存储类缺失的明确信号。完成这四步后你的环境才算真正准备好。记住K8s 的哲学是“声明式”但声明的前提是“环境可信”。每一次kubectl apply的成功都建立在这四块基石牢固的基础上。3.2 执行部署脚本deploy.sh的内部逻辑与关键参数解析./scripts/k8s/deploy.sh是整个部署流程的“大脑”。它不是一个黑盒而是一个结构清晰、职责分明的 Bash 脚本。理解它的内部逻辑是你掌控部署过程的关键。脚本的核心流程可以分解为四个阶段阶段一参数解析与环境初始化脚本首先解析命令行参数--create-secret,--show-token,--delete,--namespace。--delete是一个特殊开关它会跳过所有部署逻辑直接执行清理。--namespace参数会覆盖环境变量OPENCLAW_NAMESPACE。接着它会检查必需的环境变量ANTHROPIC_API_KEY,OPENAI_API_KEY等。这里有一个精妙的设计脚本不会强制要求你设置所有 Key它只会把你当前export出来的 Key 加入 Secret。这意味着你可以先只设置ANTHROPIC_API_KEY部署后续再export OPENAI_API_KEY... ./deploy.sh --create-secret来追加而不会丢失已有的 Anthropic Key。这是对“渐进式部署”理念的完美支持。阶段二Namespace 与 Secret 的创建脚本首先创建一个 Namespace默认openclaw。这是 K8s 的资源隔离边界所有 OpenClaw 的资源都将在其中运行。然后它进入最关键的 Secret 创建环节。脚本会生成一个随机的 32 字节 Gateway Token用于 Control UI 登录并将所有*_API_KEY环境变量的值连同这个 Token一起写入一个临时的 YAML 文件。这个临时文件的内容类似于apiVersion: v1 kind: Secret metadata: name: openclaw-secrets namespace: openclaw type: Opaque data: ANTHROPIC_API_KEY: c2stYW50LWFwaTAzLX... OPENCLAW_GATEWAY_TOKEN: ZjM0ZTQyYzEt...注意data字段下的值是 Base64 编码的。脚本会调用kubectl apply -f temp-file来创建这个 Secret。为什么是data而不是stringData因为stringData是 K8s 1.12 引入的便捷字段它会在提交时自动 Base64 编码。但deploy.sh选择显式编码是为了最大程度的兼容性确保脚本能在任何 K8s 版本上运行。这也是一个重要的工程实践在不确定目标环境时选择最保守、最兼容的方案。阶段三Kustomize 构建与资源应用这是脚本的主干。它执行kustomize build scripts/k8s/manifests/。kustomization.yaml文件位于scripts/k8s/manifests/目录下其内容定义了resources: 列出了所有要包含的 YAML 文件deployment.yaml,service.yaml,pvc.yaml,configmap.yaml。patchesStrategicMerge: 可选可以在此处添加对 Deployment 的补丁例如修改replicas。configMapGenerator: 指定了configmap.yaml的内容来源即AGENTS.md和openclaw.json文件。secretGenerator: 可选如果需要可以在此处生成其他 Secret。kustomize build的输出是一个完整的、可直接kubectl apply的 YAML 清单。脚本会将此清单通过管道传递给kubectl apply -f -。这个过程是原子的要么所有资源都创建成功要么全部失败回滚K8s 的apply语义保证。阶段四状态验证与 Token 输出部署完成后脚本会执行kubectl wait --forconditionavailable --timeout120s deployment/openclaw -n openclaw等待 Deployment 的所有 Pod 进入Available状态。这是一个健壮性检查确保 OpenClaw 进程真的启动并监听了端口。如果超时脚本会报错。最后如果指定了--show-token脚本会执行kubectl get secret openclaw-secrets -n openclaw -o jsonpath{.data.OPENCLAW_GATEWAY_TOKEN} | base64 -d将 Token 解码并打印出来。这个 Token 就是登录http://localhost:18789的密码。关键参数实战指南./deploy.sh --create-secret: 当你修改了 API Key只想更新 Secret 而不重新部署整个应用时使用。它会删除旧 Secret 并创建新 Secret然后触发 Deployment 的滚动更新因为 Secret 是作为 Volume Mount 进入 Pod 的挂载点变化会触发重启。OPENCLAW_NAMESPACEmy-ai-gateway ./deploy.sh: 将 OpenClaw 部署到名为my-ai-gateway的 Namespace而不是默认的openclaw。这对于多租户环境或避免命名冲突至关重要。./deploy.sh --delete: 彻底清理。它会执行kubectl delete namespace openclaw这会级联删除该 Namespace 下的所有资源包括 PVC。注意PVC 删除意味着所有 Agent 状态数据将永久丢失生产环境中应先备份 PVC 中的数据。3.3 暴露服务从port-forward到 Ingress 的完整链路打通kubectl port-forward svc/openclaw 18789:18789 -n openclaw是开发者的“生命线”但它只是一个临时的、单向的隧道。要让 OpenClaw 真正成为一个可被外部系统集成的网关我们必须走完从 Service 到 Ingress 的完整链路。第一步理解 OpenClaw 的绑定模式Bind Address这是整个暴露过程的“总开关”。OpenClaw 的openclaw.json配置文件中有一个关键字段bindAddress。官方默认值是127.0.0.1:18789。这意味着OpenClaw 进程只监听 Pod 内部的回环地址拒绝来自 Pod 外部包括同一 Node 上的其他 Pod更不用说集群外的任何连接。这是极致的安全默认值但也是Ingress无法工作的根本原因。IngressController如 Nginx需要能够通过 ClusterIP Service 访问到 OpenClaw Pod 的 IP 和端口而127.0.0.1是无法被外部访问的。解决方案修改bindAddress你需要编辑scripts/k8s/manifests/configmap.yaml文件中的openclaw.json部分。将bindAddress: 127.0.0.1:18789改为bindAddress: 0.0.0.0:18789。0.0.0.0表示监听所有网络接口。但这还不够仅仅改bindAddress会让 OpenClaw 暴露在不设防的状态下。你还必须配置allowedOrigins这是一个白名单用于防止跨站请求伪造CSRF。例如如果你的 Ingress Host 是ai-gateway.dev.company.com那么allowedOrigins应设置为[https://ai-gateway.dev.company.com]。完整的openclaw.json片段如下{ bindAddress: 0.0.0.0:18789, allowedOrigins: [https://ai-gateway.dev.company.com], gatewayToken: ${GATEWAY_TOKEN}, ... }注意${GATEWAY_TOKEN}是一个占位符kustomize会在构建时将其替换为 Secret 中的实际 Token。第二步创建 Ingress 资源在scripts/k8s/manifests/目录下创建一个新的文件ingress.yamlapiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: openclaw-ingress namespace: openclaw annotations: nginx.ingress.kubernetes.io/ssl-redirect: true nginx.ingress.kubernetes.io/force-ssl-redirect: true spec: ingressClassName: nginx tls: - hosts: - ai-gateway.dev.company.com secretName: openclaw-tls-secret rules: - host: ai-gateway.dev.company.com http: paths: - path: / pathType: Prefix backend: service: name: openclaw port: number: 18789这个 Ingress 资源告诉 Nginx Ingress Controller当收到Host: ai-gateway.dev.company.com的 HTTPS 请求时将流量转发到openclawService 的18789端口。tls部分指定了一个名为openclaw-tls-secret的 Secret它必须包含你的 TLS 证书和私钥。你可以用kubectl create secret tls openclaw-tls-secret --certtls.crt --keytls.key -n openclaw来创建它。第三步更新kustomization.yaml并重新部署将新创建的ingress.yaml添加到kustomization.yaml的resources列表中resources: - deployment.yaml - service.yaml - pvc.yaml - configmap.yaml - ingress.yaml # -- 新增这一行然后执行./scripts/k8s/deploy.sh。脚本会重新构建所有 YAML并应用新的 Ingress 资源。第四步DNS 与最终验证最后一步是将你的域名ai-gateway.dev.company.com解析到 Ingress Controller 的 LoadBalancer IP云环境或 NodePort IP裸机环境。你可以通过kubectl get svc -n ingress-nginx ingress-nginx-controller获取其外部 IP。然后在浏览器中访问https://ai-gateway.dev.company.com。如果一切顺利你应该看到 OpenClaw 的 Control UI 登录页面并能用kubectl get secret openclaw-secrets -n openclaw -o jsonpath{.data.OPENCLAW_GATEWAY_TOKEN} | base64 -d获取的 Token 成功登录。提示如果访问时遇到502 Bad Gateway首要排查点是allowedOrigins配置。打开浏览器开发者工具的 Network 标签页查看请求的Origin头确保它精确匹配allowedOrigins数组中的某一项。https://ai-gateway.dev.company.com和https://ai-gateway.dev.company.com/是不同的 Origin。4. 深度避坑指南那些官方文档没写的“血泪教训”在数十次不同环境kind,k3s,EKS,GKE的 OpenClaw 部署实践中我总结了以下几条“踩过坑之后才懂”的经验。它们不像kubectl apply那样写在文档里但却是决定项目成败的关键。4.1 “Node.js 版本地狱”的终极解法永远相信镜像而非本地网络热词中反复出现的node: /lib64/libstdc.so.6: version cxxabi_1.3.11 not found其根源在于Node.js 二进制与其运行时依赖的 C 标准库libstdc版本不匹配。这个问题在混合环境如 CentOS 7 构建Ubuntu 24.04 运行中尤为突出。很多人的第一反应是升级系统libstdc但这在生产服务器上是高危操作可能破坏其他关键服务。我的解法是彻底放弃在宿主机上运行openclawCLI转而使用官方镜像内的 CLI。OpenClaw 的官方镜像ghcr.io/openclaw/openclaw:latest不仅包含了运行网关的二进制也包含了openclawCLI 工具。你可以这样使用它# 不再依赖本地 Node.js直接用容器内的 CLI docker run --rm -v $(pwd):/workspace -w /workspace \ ghcr.io/openclaw/openclaw:latest \ openclaw generate-config --output openclaw.json这个命令会挂载当前目录到容器内然后在容器内执行openclaw generate-config生成的openclaw.json会直接写入你的本地磁盘。同理你可以用docker run ... openclaw list-skills来验证 Skill 文件的语法。这种方法将 Node.js 运行时的复杂性完全封装在容器内你的宿主机只需要一个docker命令即可彻底规避了所有libstdc、glibc版本冲突的问题。这是 DevOps 领域“不可变基础设施”思想的完美体现环境即镜像而非一堆需要手动维护的软件包。4.2 Ingress “无法访问”的七层排查法从 DNS 到allowedOrigins当ingress nodeport 无法访问时新手往往陷入无头苍蝇式的乱试。我总结了一套从外到内的七层排查法按顺序执行99% 的问题都能定位DNS 层nslookup ai-gateway.dev.company.com。确认域名解析到了正确的 IP 地址Ingress Controller 的 IP。网络层telnet ai-gateway.dev.company.com 443或nc -zv ai-gateway.dev.company.com 443。确认 TCP 连接能建立。如果失败检查防火墙、安全组、Ingress Controller 的 Service 是否是LoadBalancer类型且健康。Ingress Controller 层kubectl logs -n ingress-nginx deploy/ingress-nginx-controller | grep ai-gateway.dev.company.com。查看 Ingress Controller 的日志确认它是否收到了你的请求以及它是否成功匹配到了openclaw-ingress规则。Service 层kubectl get endpoints -n openclaw svc/openclaw。确认 Service 的 Endpoints 列表中有 OpenClaw Pod 的 IP 和端口。如果为空说明 Service 的selector与 Pod 的labels不匹配或者 Pod 本身没有 Running。Pod 层kubectl exec -n openclaw deploy/openclaw -- netstat -tuln | grep 18789。进入 Pod 内部确认openclaw进程确实在0.0.0.0:18789上监听而不是127.0.0.1:18789。应用层HTTPkubectl exec -n openclaw deploy/openclaw -- curl -v http://localhost:18789/health。在 Pod 内部用curl直接访问自己的健康检查端点确认 OpenClaw 进程本身是健康的。安全层CORS这是最容易被忽略的第七层。打开浏览器开发者工具查看 Network 请求的 Response Headers寻找Access-Control-Allow-Origin。如果不存在或者其值不是你请求的Origin那么问题就出在openclaw.json的allowedOrigins配置上。关键技巧allowedOrigins必须是完整的协议域名端口如果非标准端口且必须是数组形式。[*]是不安全的且 OpenClaw 默认禁用它。4.3 PVC 数据持久化的“隐形杀手”subPath与权限陷阱OpenClaw 的pvc.yaml申请了一个 10Gi 的 PVC用于挂载/app/data目录。但很多用户发现Pod 启动后/app/data目录是空的或者写入文件后重启就丢失了。这通常不是 PVC 本身的问题而是挂载方式的陷阱。在deployment.yaml中PVC 是通过volumeMounts挂载的volumeMounts: - name: data mountPath: /app/data subPath: data # -- 这个 subPath 是罪魁祸首subPath的作用是只挂载 PVC 中的某个子目录而不是整个 PVC。如果 PVC 的底层存储如 NFS中/data目录不存在K8s 不会自动创建它而是挂载失败导致/app/data成为一个空目录。更糟的是如果subPath指向一个文件而非目录挂载也会失败。我的建议是移除subPath直接挂载整个 PVC。修改deployment.yamlvolumeMounts: - name: data mountPath: /app/data # 移除 subPath: data 这一行同时确保你的openclaw.json中的dataDir字段也指向/app/data。这样OpenClaw 会直接在 PVC 的根目录下创建它所需的所有子目录agents,skills,state等由 K8s 自动保证其存在性和持久性。另一个常见陷阱是权限。OpenClaw 以 UID 1000 运行但某些存储后端如 NFS的 root_squash 选项会将 UID 1000 的写入映射为nobody用户导致权限被拒绝。解决方案是在deployment.yaml的securityContext中显式设置fsGroup: 1000securityContext: fsGroup: 1000fsGroup会确保挂载的卷PVC的组所有权被设置为 1000从而让 UID 1000 的进程拥有读写权限。这是一个 K8s 中处理存储权限的黄金法则。4.4 CI/CD 自动化部署如何让deploy.sh安全地跑在流水线里将./scripts/k8s/deploy.sh集成到 CI/CD如 GitLab CI中是实现 GitOps 的关键一步。但直接在流水线中export ANTHROPIC_API_KEY...是极度危险的因为 Key 会出现在流水线日志中。安全的 CI/CD 集成方案使用 CI/CD 的密钥管理功能在 GitLab CI 中将ANTHROPIC_API_KEY定义为一个Protected Variable并勾选Mask variable。这样它在日志中会显示为***。在.gitlab-ci.yml中通过env将其注入脚本deploy-to-dev: stage: deploy image: alpine:latest before_script: - apk add --no-cache bash curl jq kubectl kustomize - export KUBECONFIG/dev/null # 防止干扰 script: - | # 将 CI 变量注入到 deploy.sh 的执行环境中 ANTHROPIC_API_KEY$ANTHROPIC_API_KEY \ OPENCLAW_NAMESPACEdev-openclaw \ ./scripts/k8s/deploy.sh environment: dev**最关键的一点在deploy.sh中