只需要看一眼项目标题,大概就能脑补出这支团队踩过的坑了:“OpenClaw:应用与协同”这个系列做到第22篇,前21篇八成都在讲单机跑通、多机试跑、评测调优这些事儿,到了这一篇突然转向“企业级部署”,而且一上来就是“Kubernetes + GPU集群的混合架构”,说明什么?说明OpenClaw已经从实验室原型走向了生产环境,开始被正经业务团队盯上了。
我在多个项目里都经历过这种“从开发机到生产集群”的跨越,最深的感受是:这不是把Docker镜像搬到K8s里的问题,而是整套调度模型、资源模型、故障域设计都要推倒重来。尤其是OpenClaw这种本身就是做“应用协同”的框架——它既要跑多个AI代理实例,又要让这些代理之间高效协同,天然对GPU资源分配和节点间通信极其敏感。拿单机那种“一张卡跑到底”的思路去搞企业级部署,大概率翻车。这篇就从实际落地的角度,把这套混合架构的来龙去脉、设计取舍和实操细节完整捋一遍。
1. 企业级部署为什么绕不开Kubernetes + GPU集群的混合架构
1.1 单纯K8s部署解决不了OpenClaw的“双重资源诉求”
先理清OpenClaw的部署难点到底在哪。它有两个维度的工作负载:一是控制面服务,负责代理调度、任务编排、状态同步、API网关,这些组件对CPU和内存敏感,对GPU基本没需求;二是推理与执行负载,也就是真正跑大模型、跑多模态推理、跑代理决策的那部分进程,必须吃到GPU红利,但它的资源消耗又是爆发性的、不均衡的。
如果只用普通的K8s集群,会遇到一个尴尬的问题:GPU节点既跑控制面又跑推理负载,CPU节点反而闲着,而且控制面服务会被某个高GPU消耗的推理任务拖垮,节点抖动直接引发雪崩。如果只用纯GPU节点组网,成本又完全不可控——你不可能为每个控制面Pod都配一张A100。
混合架构的本质,是把这两种诉求拆到不同的资源池里,用K8s的调度器把它们粘合起来。控制面跑在通用计算节点上,追求稳定;推理负载跑在GPU节点上,追求性能。这个拆分看着简单,但直接影响后面的节点规划、污点策略、资源配额设计,是整个部署的“第一性原理”。
1.2 混合架构的分层逻辑,本质是“控制与计算分离”
这套架构在设计上参考了业界主流的“控制面/数据面分离”思路,但针对OpenClaw的场景做了特殊处理。控制面负责代理的注册发现、会话路由、任务队列、群组通信,这些组件的特点是:请求频繁、单请求轻量、状态敏感。数据面负责模型推理、上下文编码、多步规划的计算,它的特点是:单个任务重、延迟要求高、GPU显存占用大。
把两者混在同一个节点池,后果就是控制面的抖动直接影响推理质量——某个群聊任务正在做多代理协同,突然控制面Pod被驱逐重建,所有代理的会话上下文全部断掉。把两者物理隔离后,控制面可以放心做HPA,推理节点可以放心做GPU资源超卖或预留,互不干扰。
1.3 什么样的规模和场景适合这套架构
不是说一上来就要上K8s + GPU集群,你在开发机上docker run就挺好的。根据我的落地经验,出现下面几个信号时,就说明该上这套混合架构了:
- 代理实例数量超过30个,手工管理镜像和启动参数开始出错;
- 多个业务团队共用同一批GPU资源,需要配额隔离和成本核算;
- 推理负载有明显的峰谷特征,比如白天业务高峰、夜间批量预计算,需要弹性伸缩;
- 对故障恢复有明确要求,某个代理崩溃后能在其他节点自动拉起,不能靠运维手工重启。
如果只是自己一个人做研究,跑两三个代理,那确实没必要折腾。但一旦跨过这条线,K8s这套体系带来的收益是指数级的——它不光是容器编排,更是一套资源治理和故障域管理的框架。
2. 基础设施选型与准备工作
2.1 软件栈选型:版本匹配是最大的暗坑
在混合架构里,软件栈的选型可以直接参考下面这张表,这是我多个项目验证过的组合:
| 组件 | 推荐选型 | 版本要求 | 备注 |
|---|---|---|---|
| Kubernetes | kubeadm或托管K8s | 1.28以上 | 1.26以下对设备插件的动态资源支持不够友好 |
| 容器运行时 | containerd | 1.7.x以上 | 不要用Docker Shim,K8s 1.24后已移除 |
| GPU驱动 | NVIDIA驱动 | 535.xx以上 | 驱动版本决定CUDA兼容性上限 |
| 容器GPU工具 | NVIDIA Container Toolkit | 1.14以上 | 替代旧版nvidia-docker2 |
| GPU设备插件 | k8s-device-plugin | 0.14以上 | 与K8s版本有对应关系 |
| 监控 | DCGM Exporter + Prometheus | 最新稳定版 | NVIDIA官方出品的GPU监控方案 |
这里最容易踩坑的是“驱动版本、CUDA版本、容器工具包版本”三者的三角关系。很多团队直接把宿主机驱动升到最新,结果容器里用的CUDA镜像版本太老,导致nvidia-smi在宿主机正常、进了容器就报CUDA driver version is insufficient。我的建议是:先定CUDA镜像版本,再反推驱动版本,最后选择对应的Container Toolkit。比如镜像里锁了CUDA 12.2,驱动至少要535.104,再往上装到545或550都没问题,但千万别倒过来先升级驱动再发现镜像不兼容。
2.2 节点角色规划:给每个节点贴明白“身份标签”
混合架构的节点规划我通常分成三类:
控制面节点:跑K8s控制组件和OpenClaw的控制面服务。一般是2~4台高主频CPU机型,内存给到64GB以上,磁盘用SSD,不配GPU。这个池子要求稳定,最好加NoSchedule污点,避免推理负载跑进来抢占资源。
GPU推理节点:跑OpenClaw的模型推理与代理执行负载。按业务需求上A100/H100/L40S这类卡,每节点插4~8张,内存给到512GB以上,网络要用25GbE以上。这类节点除了给K8s打上GPU标签,还要给每张卡做拓扑检测,为后面的“显存亲和性调度”做准备。
弹性节点组:用自动伸缩组管理,平时缩容到0,业务高峰期动态扩容。这个池子可以混合CPU和GPU节点,专门承接批量离线计算任务,比如夜里跑数据清洗或代理预计算。
节点标签的命名要规范,我自己习惯用node-role.kubernetes.io/control-plane、gpu.nvidia.com/class这类语义明确的关键字,配合污点使用。标签如果乱起,后面写调度策略时语义不清晰,排查问题非常痛苦。
2.3 驱动与容器运行时的三层安装次序
GPU节点初始化时,安装顺序不能乱,严格按照这个来:
先装驱动。apt install nvidia-driver-535或者用runfile装都行,装完重启,nvidia-smi验证能看见卡。此时宿主机是可以识别GPU的,但容器里还不行。
再装Container Toolkit。官方源加上后,apt install nvidia-container-toolkit,然后编辑/etc/docker/daemon.json或/etc/containerd/config.toml,把nvidia runtime注册进去。如果用的是containerd,关键配置是这样:
version = 2 [plugins."io.containerd.grpc.v1.cri"] [plugins."io.containerd.grpc.v1.cri".containerd] default_runtime_name = "nvidia" [plugins."io.containerd.grpc.v1.cri".containerd.runtimes] [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.nvidia] privileged_without_host_devices = false runtime_engine = "" runtime_root = "" runtime_type = "io.containerd.runc.v2"之后用ctr run起一个临时容器,容器里面执行nvidia-smi,能看见GPU就说明宿主机到容器的GPU透传已经打通。这个验证特别重要,它把“驱动问题”和“容器运行时问题”隔离开来,后面K8s层面的问题就不会跟这层混在一起。
最后再部署K8s设备插件,这一步其实是把“宿主机GPU”注册成“K8s可调度的资源”,也就是nvidia.com/gpu。三层链路全部通了,Pod里才能声明并拿到GPU。
3. 核心配置与调度策略细节
3.1 GPU资源接入K8s:Device Plugin的工作机制
k8s-device-plugin本质上是一个DaemonSet,每个GPU节点跑一个Pod,它通过gRPC和kubelet通信,上报本机的GPU资源列表。Kubelet把这些资源注册到API Server,之后调度器就知道这台节点有多少nvidia.com/gpu可用。
它的配置文件支持很多实用选项,我常用的有几个:
--pass-device-specs:把设备规格传给容器运行时,对某些特殊场景有用;--device-list-strategy=env-vars:把GPU设备ID通过环境变量传给容器,默认策略是挂载设备文件;--mig-strategy=single或mixed:启用MIG时指定切分策略。--fail-on-init-error=false:节点上驱动异常时,设备插件不要崩溃,方便排查。
部署命令很简单:
kubectl apply -f https://raw.githubusercontent.com/NVIDIA/k8s-device-plugin/main/deployments/static/nvidia-device-plugin.yml部署后检查每个GPU节点的Pod状态:
kubectl get pods -n kube-system -o wide | grep nvidia-device-plugin正常情况下每个GPU节点都有一个Running状态的Pod。如果节点上驱动没装好,这个Pod会一直CrashLoopBackOff,且kubectl describe里会明确提示failed to initialize NVML。
3.2 节点分区与Pod调度约束的落地写法
混合架构的精髓在于“该去GPU节点的必须去,不该去的坚决不让他去”。这靠的不是人肉约束,而是K8s的三件套:节点选择器、节点亲和性、污点容忍。
先把GPU节点打上标签:
kubectl label node gpu-node-01 gpu.nvidia.com/class=A100 kubectl taint node gpu-node-01 nvidia.com/gpu=true:NoSchedule控制面节点打标签并加污点:
kubectl label node ctl-node-01 node-role.kubernetes.io/control-plane=true kubectl taint node ctl-node-01 node-role.kubernetes.io/control-plane=true:NoSchedule然后给OpenClaw的推理负载写调度约束,这里用节点亲和性比较合适,因为它支持软约束和硬约束两种模式:
affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: gpu.nvidia.com/class operator: In values: - A100 podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: labelSelector: matchLabels: app: openclaw-inference topologyKey: kubernetes.io/hostname这个配置的意思很明确:推理Pod必须跑到A100节点上(硬约束),并且尽量不要把多个推理Pod挤在同一个节点上(软约束)。为什么要用PodAntiAffinity?因为OpenClaw跑的是多代理协同任务,一个节点上如果堆了太多推理Pod,显存虽然够,但GPU算力会被争抢,导致代理决策延迟飙升,协同效果肉眼可见地变差。让每个节点保留余量,是高负载场景下保体验的关键。
3.3 控制面服务的高可用设计:必须打破“单实例思维”
很多人部署OpenClaw控制面时,直接一个Deployment replica=1就上了,这在开发环境没问题,但在企业级部署里就是定时炸弹。控制面一旦挂掉,所有代理的会话全部失联,任务队列原地卡死。
正确做法是至少三副本,并配置PodDisruptionBudget,保证任意时刻至少有两个副本存活。同时要把会话状态从内存搬到Redis或ETCD里,否则Pod重建后内存里的会话上下文全丢,副本再多也是白搭。
控制面服务还要处理好“会话路由”的问题。OpenClaw里一个任务可能涉及多个代理协同,用户在网关层建立会话,后续的每个子请求都要路由到同一个代理实例。这个亲和性不能靠K8s默认的Service负载均衡来做——它会把请求随机分发到不同副本上。我通常改用有状态的Sts部署控制面,或者在Service前面加一层基于会话ID的哈希路由,确保同一会话的请求永远打到同一个后端Pod。
4. 部署流程与配置示例
4.1 搭建基础集群:kubeadm方式完整示列
以三台控制面节点加三台GPU节点的标准配置为例,控制面节点走kubeadm初始化,GPU节点用kubeadm join加入。初始化命令需要指定Pod网段和Service网段,注意网段不能跟宿主机网段冲突:
kubeadm init \ --control-plane-endpoint=10.0.10.10:6443 \ --pod-network-cidr=192.168.0.0/16 \ --service-cidr=10.96.0.0/12网络插件用Calico,因为OpenClaw的分布式协同对网络策略有要求,Calico对NetworkPolicy支持更完整。装完后把KUBECONFIG配置好,kubectl get nodes能看到所有节点Ready。
4.2 部署NVIDIA Device Plugin并验证调度
GPU节点人员,集群建好后,直接部署设备插件。验证方式不只有看Pod状态,更直接的是提交一个测试Pod,申请一张GPU:
apiVersion: v1 kind: Pod metadata: name: gpu-test spec: restartPolicy: OnFailure containers: - name: cuda-test image: nvidia/cuda:12.2.0-base-ubuntu22.04 command: ["nvidia-smi"] resources: limits: nvidia.com/gpu: 1执行kubectl logs gpu-test,如果输出里能看到GPU型号和驱动版本,说明整条链路是通的。这里要重点检查Pod里显示的显存大小和宿主机是否一致,如果容器里显示的是整卡显存而宿主机因为MIG切过卡,说明设备插件没有正确感知MIG配置。
4.3 编写OpenClaw推理工作负载的详细YAML
下面是我实际用过的OpenClaw推理服务部署配置,去掉敏感信息后大致长这样:
apiVersion: apps/v1 kind: Deployment metadata: name: openclaw-inference namespace: openclaw spec: replicas: 4 strategy: type: RollingUpdate rollingUpdate: maxSurge: 1 maxUnavailable: 0 selector: matchLabels: app: openclaw-inference template: metadata: labels: app: openclaw-inference spec: nodeSelector: gpu.nvidia.com/class: A100 tolerations: - key: nvidia.com/gpu operator: Exists effect: NoSchedule containers: - name: inference image: openclaw/inference:2.4.0 ports: - containerPort: 8080 resources: requests: cpu: 8 memory: 32Gi nvidia.com/gpu: 1 limits: cpu: 16 memory: 48Gi nvidia.com/gpu: 1 env: - name: NVIDIA_VISIBLE_DEVICES value: "all" - name: OPENCLAW_NODE_ID valueFrom: fieldRef: fieldPath: spec.nodeName - name: OPENCLAW_SESSION_BACKEND value: "redis://redis-master.openclaw.svc:6379"其中maxSurge: 1和maxUnavailable: 0是滚动更新时的安全策略:先起一个新Pod,等它Ready了再停掉旧Pod,保证整个更新过程中始终有服务可用。OPENCLAW_NODE_ID这个环境变量是从Pod调度结果注入的,OpenClaw内部会用它来标记当前推理实例落在哪个物理节点上,方便做节点级的多代理协同调度决策。
4.4 GPU显存与算力的三种共享策略选型
GPU节点上跑多个推理实例时,显存怎么切是个大学问。我按实际场景总结出三种策略,各有利弊:
整卡独占:每个Pod申领一整张卡,资源隔离性最好,性能最稳定,但成本浪费严重。适合大模型推理或训练任务,适合对延迟极其敏感的场景。
MIG切分:以A100为例,一张80GB的卡可以切成7个计算实例,每个实例拿到独立的显存和算力切片,隔离性接近整卡独占。适合2~4个中等规模的推理任务共用一张卡。配置方式是在设备插件里设置--mig-strategy=single,然后在宿主机上预先用nvidia-smi mig -cgi切好。
Time-Slicing时间片共享:多个Pod共用一张卡,通过时间片轮转调度GPU算力,显存不做隔离。优点是利用率拉满,缺点是任务间互相干扰,一个高算力任务会把其他任务拖慢。只适合开发测试环境,生产环境慎用。
我个人在生产环境的建议是:如果业务模型单一、负载可控,优先整卡独占;如果模型小而多,MIG切分是性价比最高的方案,但要注意MIG切分后显存和算力都变小了,模型能不能装得下需要提前实测,不能按理论值拍脑袋。
4.5 混合架构下服务发现与Ingress暴露的注意事项
OpenClaw对外要暴露API网关、内部代理间要通信,服务发现这块如果不设计好,整个协同链路跑不起来。
内部代理通信走K8s Service是没问题的,但要注意Service的sessionAffinity不能解决“多代理协作里同一个会话必须回同一个实例”的问题——Service的亲和性只是把同一个客户端IP的请求固定到同一个后端,如果代理之间互相调用,源IP会变,亲和性就失效了。所以我在OpenClaw内部设计里引入了基于会话ID的哈希路由层,而不是依赖K8s Service的负载均衡。
对外暴露建议用Ingress Controller,我在生产环境里用的比较多的是Ingress-Nginx,配合TLS证书管理。这里有个细节:OpenClaw的推理接口通常是大体量请求,比如多模态输入或长上下文,Nginx默认的proxy-read-timeout是60秒,必须调大到300秒甚至更长,否则一个长任务分析请求会被网关掐断,表现为“客户端超时但服务端还在算”。
5. 常见问题与排查技巧实录
5.1 GPU资源无法调度,Pod一直Pending
这是部署后遇到最多的一个问题。排查顺序我从经验里固定成了这套:
先看Pod事件:kubectl describe pod xxx,如果事件里有0/3 nodes are available: 3 Insufficient nvidia.com/gpu,说明GPU资源确实不够或标签不匹配。再检查节点上的可分配资源:kubectl describe node gpu-node-01 | grep -A5 "Allocatable",确认nvidia.com/gpu是否为4这样的数值而非0。
如果节点上根本没有nvidia.com/gpu这一项,问题基本出在设备插件没跑起来或驱动没装好。此时最关键的一步是kubectl logs -n kube-system nvidia-device-plugin-xxxxx,看能不能输出GPU的型号列表。
还有一个冷门但高频的原因:设备插件版本和K8s版本不兼容,老版本设备插件在新版K8s上报资源时字段格式变了,导致调度器识别不到。不兼容时升级设备插件往往比升级K8s集群成本低得多。
5.2 显存利用率偏低,GPU算力闲置但任务还在排队
这个问题的排查方向常常被人搞反。先看监控:如果显存利用率不到30%,但SM利用率已经到90%以上,说明瓶颈在算力不在显存,加显存没用,得优化模型推理逻辑或减少并发。反过来,如果显存已经顶到95%,SM利用率才50%,那就是显存瓶颈,该考虑模型量化、梯度检查点或换更大显存的卡。
OpenClaw这种多代理协同场景还有一个隐蔽的显存浪费点:每个代理实例如果都加载同一套大模型,显存是线性翻倍的。优化思路是把模型加载层抽出来做成“模型池”,多个代理共享同一份模型权重,用显存映射的方式复用,而不是每实例独立加载。这个优化在某些场景下能把显存占用从12GB降到3GB,效果非常显著。
5.3 多代理协同时的节点间通信延迟居高不下
OpenClaw的多代理协同对节点间通信延迟很敏感,尤其是需要高频交换上下文或多个代理做群组讨论时。排查延迟问题,第一个要看网络拓扑:GPU节点的跨交换机通信通常比同交换机慢数倍,所以调度时最好把同一任务相关的代理Pod尽量调度到同一个节点或同一个机架。
除了网络,还有一个在K8s环境里特别容易被忽视的点:Service Mesh会把代理间的每个请求都经过sidecar代理转发,增加一跳延迟和CPU开销。我的实践是:OpenClaw内部代理间通信走直连Service,不挂Sidecar;只有对外API网关才走完整的链路。这个“内外分离”设计能显著降低通信延迟,有两到三倍的提升。
5.4 OOMKilled导致代理会话频繁中断
GPU节点上OOM问题,表象是Pod被OOMKilled,但根因往往不是内存不足,而是配置了不合理的limits。有些团队为了给Pod“抢资源”把limits设得特别大,结果宿主机内存超卖严重,内核OOM Killer随机挑进程杀,杀到的正好是OpenClaw推理进程。
排查时用kubectl describe pod看Last State的退出码,如果是137,基本就是被OOM杀了。此时要重新评估requests和limits的比值,我建议limits不超过requests的1.5倍,且节点总requests不能超过节点内存的80%,这给页缓存和系统进程留出余量。
另外要分清OOM和GPU显存溢出的区别:如果推理进程报的是CUDA out of memory,那是显存问题,要看的是模型大小和MIG切分是否匹配;如果进程直接被系统杀,才考虑内存问题。这两个报错在日志里特征不同,别搞混了。
5.5 滚动更新时出现会话中断,业务侧投诉明显
这个问题我踩过好几次。配置了maxUnavailable: 0之后,按理说更新过程中至少有一个旧Pod在服务,为什么还会中断?原因出在“会话绑定”上:OpenClaw的会话状态虽然存在Redis里,但代理之间的长连接或内存中的临时上下文是无法跨Pod迁移的,旧Pod一缩容,所有绑定在它上面的实时协同任务全部断开。
解决方案是我在上面提到过的:给控制面加“优雅停止”逻辑,Pod收到SIGTERM后,先把当前正在处理的请求全部完成,再把内存里的上下文刷到Redis,然后才真正退出。这个逻辑需要在应用代码里实现,K8s层面只能通过terminationGracePeriodSeconds给它留足时间,我一般设到60到90秒。
6. 监控体系与容量规划经验
6.1 GPU监控的“三看”原则:显存、算力、温度
部署完一切正常后,很多人就以为大功告成了,其实真正的企业级运维才刚开始。GPU监控我总结为“三看”:看显存占用,防止OOM;看SM利用率,判断算力是否打满;看温度与功耗,防止降频影响性能。
监控方案用NVIDIA官方推荐的组合:DCGM Exporter采集GPU指标,Prometheus存储,Grafana出图。核心指标推荐这几个:
DCGM_FI_DEV_MEMORY_USED:显存使用量;DCGM_FI_DEV_GPU_UTIL:SM利用率;DCGM_FI_DEV_GPU_TEMP:核心温度;DCGM_FI_DEV_POWER_USAGE:实时功耗;DCGM_FI_DEV_XID_ERRORS:XID报错,这是ECC错误、显存故障等的指示灯,一旦有XID报错,基本预示着这张卡要出问题。
Grafana的告警规则我的经验是:温度超过85度持续5分钟告警;XID错误出现一次立刻告警;显存使用率超过95%持续10分钟告警。这里的XID尤其重要,很多GPU卡故障的前兆就是先出现XID错误,提前介入能避免一次生产事故。
6.2 容量规划:从“按卡数估算”升级为“按任务画像估算”
OpenClaw的容量规划不能按“模型参数量除以单卡显存”这种粗放的方式算,要按“任务画像”来算。不同任务的GPU消耗差异可能达到十倍:一个多代理群聊任务可能只跑一个中等模型,一个批量知识图谱推理任务可能要用70B大模型跑数小时。
我的规划思路是分两步走。第一步,给每类OpenClaw任务打标,分“轻量推理、中量推理、重量推理、批量计算”四档,每档跑一次基准测试,测出平均显存占用、平均推理耗时、平均SM利用率。第二步,根据历史任务量和高峰并发数,推算每档任务需要多少个GPU实例。
基于这些数据,可以做出一个比较合理的节点池规划:常驻一组基础GPU节点兜底日常负载,弹性节点组承接高峰增量,这样既能保证服务质量,又不会让GPU资源在凌晨空转烧钱。
6.3 多团队共用GPU的租户隔离策略
企业级部署里难免碰到多个团队共用同一批GPU的情况。这时候不做隔离,A团队一个跑满显存的任务会把B团队的所有任务全部拖死,排障时还会互相甩锅。
K8s的ResourceQuota和LimitRange是基础操作:每个团队一个Namespace,配额写在ResourceQuota里,比如nvidia.com/gpu: 8,Namespace内的Pod总申请量不能超过8张卡。LimitRange则把单个Pod的GPU申请限制在1到2之间,防止有人一个Pod申请4张卡挤占别人空间。
更细的隔离靠节点池和污点:重负载团队用A类GPU节点,轻负载团队用B类GPU节点,节点间物理隔离,互不影响。配合前面的NetworkPolicy限制跨Namespace的网络访问,基本能做到租户级别的资源隔离和安全隔离。
7. 性能调优与扩展方向
7.1 推理延迟的硬优化:从OS层面到容器层面的完整链路
K8s环境下的GPU推理延迟优化,不是光调YAML就够的,要从整个I/O链路来排查。宿主机层面要确认是否启用了GPU的持久模式(nvidia-smi -pm 1),否则每次任务启动都有额外的初始化开销。还要确认GPU所在的PCIe链路是否被其他设备占用,必要时设置PCIe ACS禁用,避免IO延迟抖动。
容器层面,对延迟敏感的任务建议设置resources.limits.cpu和resources.limits.memory时不要超过节点容量的80%,避免CPU throttling造成的隐性延迟。这里有个很常见的现象:任务运行初期Pod的CPU用量接近limits上限,被内核CFS调度器节流,表现出来就是推理偶尔变慢两三倍。用kubectl top pod能看到CPU使用率接近上限,此时就应该调高limits或减少同类Pod数量。
7.2 OpenClaw任务的弹性伸缩:HPA无法解决的场景
OpenClaw的弹性伸缩比普通Web服务复杂,因为任务之间存在会话关联性,不能简单地按CPU使用率做HPA。如果单纯用custom-metrics-api里的CPU指标扩容,经常会遇到“A任务正在跑关键协同,B任务被扩容到新Pod上没法跟A通信”的问题。
我实践下来更有效的方式是“队列长度驱动伸缩”:OpenClaw的控制面里维护任务队列,用Prometheus监控队列深度,队列积压超过阈值时,自动创建新的推理Worker Pod,队列空了就缩容。这个逻辑用KEDA的ScaledObject就能实现,关键点是让伸缩行为基于业务指标而非基础设施指标。
弹性伸缩还要设置冷却时间。GPU Pod的启动时间通常比普通Pod长得多,镜像拉取加模型加载可能要两分钟,冷却时间设太短会导致刚拉起就要被缩掉。我的经验是扩容冷却30秒、缩容冷却10分钟:业务高峰来了快速扩容,高峰过去后让多余的Worker继续服务已有任务,等任务自然结束再回收资源。
7.3 从混合架构到多集群的演进路径
这套混合架构跑顺之后,下一步就是多集群。当OpenClaw的业务范围扩展到多个可用区、甚至多个地区时,单集群的故障域和网络延迟会成为瓶颈。多集群的路径我建议从“中心-边缘”模式切入:中心集群跑控制面,边缘集群跑推理,控制面通过联邦机制把任务下发给最近的边缘推理节点。
多集群部署的复杂度主要在三个方面:跨集群的网络互通、全局任务调度、统一监控。网络互通用CNI的跨集群插件可以解决;任务调度建议引入“就近路由+负载均衡”的双层策略;监控则要在每套集群独立采集的基础上,用聚合层把指标做全局视图。每一步的投入都不小,但如果业务确实有地域化需求,这套架构带来的用户体验提升是很值的。
我个人在实际操作中的一个体会是:这套混合架构最难的其实不是K8s本身,而是对“GPU资源语义”的深刻理解。K8s把CPU和内存当同质化资源处理,但GPU有显存、算力、拓扑、MIG切片这些额外维度,粒度细得多。把OpenClaw跑起来只需要半天,但把调度策略、资源模型、故障域边界打磨好,需要几个版本迭代才能稳定。如果你们团队正在做相同的事,我的建议是先把节点池划分和调度策略写进“部署约定”,让每一次新增节点、卸载GPU、切MIG都走标准流程,这些细节会决定这套架构到底能撑多久。