2026-06-03 04:00:00

KubeClipper 发布了 1.6.0 版本:支持 Kubernetes 1.36,容器运行时从 Containerd 1.x 升级到 2.x,Calico 更新到 v3.31.5。kcctl 新增了 kcctl set cluster、kcctl operation 等命令,并优化了 Registry 管理体验,修复了大量稳定性问题。
KubeClipper 是一个轻量便捷的 Kubernetes 多集群全生命周期管理工具,旨在提供易使用、易运维、极轻量、生产级的 Kubernetes 多集群管理服务,让运维工程师从繁复的配置和晦涩的命令行中解放出来,实现一站式管理跨区域、跨基础设施的多 K8S 集群。
如果你是第一次接触 KubeClipper,可以通过以下步骤快速上手:
curl -sfL https://oss.kubeclipper.io/get-kubeclipper.sh | KC_REGION=cn bash -
kcctl deploy
kcctl create cluster --name demo --master YOUR_IP --untaint-master
http://YOUR_IP:8080,账号 admin/Thinkbig1
四条命令,从零到一个跑起来的 K8s 集群。
1.6.0 的主要变化集中在三块:K8s 最新版本支持、Containerd v2 适配、kcctl 命令行改进。
本次更新的主要亮点:
| Kubernetes | Calico | Containerd |
|---|---|---|
| v1.36.1(默认) | v3.31.5 | v2.2.4 |
| v1.35.0 | v3.29.6 | v1.7.29 |
| v1.34.2 | v3.29.6 | v1.7.29 |
Containerd v2:容器运行时大版本跃迁
config.toml version=3:CRI v1alpha2 移除,仅保留 CRI v1Calico v3.31:eBPF 数据面走向成熟
kcctl set cluster
新增 kcctl set cluster 命令,支持在集群创建后修改外部访问配置:
|
|
同时 kcctl create cluster 也新增了 --external-port 和 --external-domain-port 参数,支持在创建集群时直接指定外部访问端口。
kcctl operation 命令
1.6.0 重写了 kcctl operation 命令,支持按集群名称查看操作记录,还加了 TUI 交互模式:
|
|
还有三个新子命令:
|
|
旧的 kcctl logs 命令已标记为废弃,后续版本将移除。
kcctl registry 优化
kcctl registry deploy 完成后自动将节点信息(IP、端口、SSH 凭据)保存到 ~/.kc/registry-config.yaml,后续执行 list、push、delete 等命令时无需重复指定 --node 和 SSH 参数,CLI 参数优先级高于配置文件kcctl registry push 支持 tar 格式的镜像包另外清理了多个废弃命令,修复了 CLI 参数解析和错误处理等问题。
这一版包含大量的稳定性修复:
registry.configs.{host}.auth 部分,保留用户已有的配置(如 GPU Operator 添加的配置)
|
|
输出示例:
|
|
|
|
看到下面的 banner 就说明部署完成了:
|
|
安装过程中需要去阿里云下载离线安装包,大概 1 分钟即可下载完成。
安装完成后,打开浏览器,访问 http://$IP 即可进入 KubeClipper 控制台。

您可以使用默认帐号密码 admin / Thinkbig1 进行登录。
用 kcctl 创建一个集群试试:
查看当前 agent 节点
|
|
创建集群:
|
|
大概两分钟装完,用以下命令看实时日志:
|
|
新版还加了 TUI 日志查看器:
|
|

集群装好了,用 kubectl 看一下状态:
|
|
几条命令,一个单节点 K8s 集群就起来了。
1.5.0 版本引入的工作负载管理功能,可以直接在 Web UI 中管理 Deployment、StatefulSet 等工作负载。

这一版主要干了三件事:跟上 K8s 最新版本、适配 Containerd v2、把 kcctl 顺手打磨了一遍。
组件升级:
命令行优化:
稳定性提升:
2026-06-02 04:00:00

随着 Vibe Coding 的流行,开源社区也是涌现出了大量的 AI 编程工作流,例如: Superpowers、Everything Claude Code、Spec Kit、OpenSpec、gstack、Get Shit Done 等等。 今天给大家分享的是 OpenSpec + Superpowers 的协同工作流。
官方的描述是:Spec-driven development (SDD) for AI coding assistants.
即:OpenSpec 就是为 AI 编程助手打造的 Spec 驱动开发(SDD)框架。
在 Vibe Coding 时代,Spec 是很重要的,在开始之前很有必要和 AI 在需求上达成一致。
使用 OpenSpec 之后每个变更(Change)对应一个文件夹,包含以下四类结构化文档:
proposal.md:变更意图,定义为什么做specs/:具体 Spec,定义做什么design.md:具体技术实现方案,定义怎么做tasks.md:具体实现顺序,将变更拆分为一个个小任务简而言之:OpenSpec 可以让 AI 在写代码之前先和人对齐需求,并提供完整的 Spec 生命周期管理,便于追踪。
官方的描述是:An agentic skills framework & software development methodology that works.
即:Superpowers 是一套 AI 编程 Agent 的技能体系 + 开发方法论。
它的核心不是生成代码,而是通过一套可组合的"技能"系统,强制 AI 像资深工程师一样工作,遵循测试驱动开发、代码审查等最佳实践。
简而言之:Superpowers 可以让 AI 像资深工程师一样干活。
在 Vibe Coding 时代,文档的重要性大幅提高。古法编程时代,需求对齐主要靠开会,Vibe Coding 时代,则需要通过具体的 Spec 变更文档来对齐。而 OpenSpec 则是提供了很好的 Spec 生命周期管理功能,可以通过 Spec 追溯到具体变更细节。
AI 生成代码是很容易堆砌成屎💩山的(当然,古法编程也一样哈哈哈)。
因为 AI 上下文有限,很难掌控全局,因此每次 Vibe Coding 产出的或许都是局部最优解,但是放到整体看就不一定合适,另外 AI 生成的代码都不够精简,代码量是很大的,毕竟 Vibe Coding 主打一个快。
这也是为什么 Github 上很多开源项目都吐槽遭受到了 PR 攻击。Agent 批量扫描代码提交 PR,可能整个过程都没有人介入,但是维护者要挨个 review,面对成堆的 PR 直摇头。
Superpowers 通过强制的 TDD、YAGNI、DRY 等工程原则,能很大程度上能够提升代码质量。
协同工作的核心思想是:用 OpenSpec 生成和管理 Spec,然后用 Superpowers 这个"资深工程师"来执行 Spec。
单独使用各有短板,二者结合刚好形成 “规格驱动开发(SDD) + 测试驱动开发(TDD)” 的完整工作流。
单独使用 OpenSpec 时工作流是这样的:
流程简单,文档驱动,但代码实现阶段(apply)的质量保障较弱。
单独使用 Superpowers 时的工作流是这样的:
产出代码质量高,但前期的需求输入和最终的设计决策缺乏一个结构化的、可长期保存的载体,容易丢失在聊天历史中。
二者协作后的工作流是这样的:
就是使用 /superpowers:brainstorm → /superpowers:write-plan → /superpowers:subagent-driven-development 来替代比较弱的 /opsx:apply 以提升代码质量。
安装
OpenSpec 直接通过 npm 安装
|
|
然后在项目根目录进行初始化
|
|
需要在项目目录下执行 init 命令初始化,初始化之后就可以在 claude 终端里使用了。
之后,重新打开 claude 就可以用上 OpenSpec 了。
OpenSpec 使用整体流程就是围绕 proposal 的:
创建 proposal
|
|
命令执行完成后会自动生成:openspec/changes/create-todo-app/ 目录、提案文档、设计模板、任务清单
应用 proposal
当 proposal 完全确定下来之后,就可以让 AI 根据 Spec 一步一步实现了。
|
|
归档 proposal
当 proposal 实现完成后,就可以进行归档保存。
|
|
安装
Superpowers Claude Code 插件
|
|
使用
Superpowers 安装后会在 Claude Code 中注册多个技能,可手动触发,部分环节会自动流转,比如最常见的几个 Skill:
brainstorming:头脑风暴,澄清需求和技术细节write-plan:基于 brainstorming 结果生成实现计划,brainstorming 完成后自动进入subagent-driven-development:按计划启动子代理逐任务实现,强制 TDD + 两阶段代码审查,plan 写好后自动开始第一步:使用 OpenSpec 生成初步 Spec
|
|
效果
|
|
第二步:使用 Superpowers 继续细化上一步的 Spec
|
|
输出如下:
|
|
第三步:使用 Superpowers write plan,根据 Design 文档生成 Plan。
|
|
第四步:使用 Superpowers 的 subagent-driven-development 实现
输出
|
|
第五步:OpenSpec + Superpowers 双重验证,分别验证是否完成。
|
|
第六步:OpenSpec 归档 Spec,纯 OpenSpec 操作:
|
|
所有变更将被保存至 openspec/changes/archive/,支持版本追溯与团队共享。
之前按照 Demo 中的流程,通过 Prompt 串联基本实现了 OpenSpec + Superpowers 的协同工作流。
然后刚好最近在 Github 上发现了 Comet 工具,它把整个协作流程自动化了。

Comet 将 OpenSpec、Superpowers 串联为五阶段自动化流水线:
同时 Comet 注册了以下技能,其中 5 个主流程技能对应协同工作流的各个阶段,另外 2 个是快捷路径:
/comet:主入口,自动检测当前阶段并分派到对应子命令/comet-open:阶段 1 — 打开变更(提案、设计、任务分解)/comet-design:阶段 2 — 深度设计(头脑风暴、设计文档)/comet-build:阶段 3 — 规划与构建(实现计划、代码提交)/comet-verify:阶段 4 — 验证与完成(测试、验证报告)/comet-archive:阶段 5 — 归档(delta spec 同步、状态标注)/comet-hotfix:快捷路径 — 快速 bug 修复,跳过头脑风暴,不需要能力设计/comet-tweak:快捷路径 — 小改动,如文案调整、配置调整、文档或 Prompt 优化一般我们只会用到/comet 命令,由 Comet 根据当前状态自动判断下一步该执行什么。
同时为了提升效率,对于一些小改动 comet 也提供了 /comet-hotfix 和 /comet-tweak 命令。
comet 通过 npm 安装
|
|
首先到项目根目录进行初始化
|
|
然后直接使用 comet 命令即可。
|
|
comet 会自动按照 open-design-build-verify-archive五阶段工作流执行。
这里以 kcctl 命令优化为例,展示具体流程:
首先会判断当前处于什么阶段,这里是新需求,所以是重头开始
|
|
然后自动进入第一阶段 Open,也就是 openspec-explore + openspec-propose 生成 Spec。
|
|
阶段完成后会运行阶段守卫(就是一个检测脚本),确保当前阶段真的完成了。当前 open 阶段则是判断 OpenSpec 的 Spec(proposal、specs、design、tasks) 是否正常生成了,如果检测发现没有则不会进入下一阶段。
通过脚本硬性检测,保证 AI 不会漏掉任何一个阶段。
检测通过后自动进入第二阶段,Superpowers brainstorming:
|
|
这个就是 Superpowers 头脑风暴的优点,可以确认很多细节上的需求,避免后续做出来不是自己想要的。
然后进入第三阶段:build,使用 Superpowers 的 subagent-driven-development 开始实现:
|
|
这一阶段耗时会比较长,AI 会根据前面的计划挨个实现,用上 Superpowers 之后,代码质量比直接 opsx:apply 会好不少。
Build 完成后会自动进入 Verify 阶段,对照 Spec 以及代码,确认所有功能都已经实现完成。
|
|
发现问题也会自动进行修复
最后就是归档了,Verify 之后自动流转到 Archive:
|
|
至此,整个流程就完成了。
OpenSpec + Superpowers 组合的核心思想是:用 OpenSpec 生成和管理 Spec,然后用 Superpowers 这个"资深工程师"来执行 Spec。
单独使用各有短板,二者结合刚好形成 “规格驱动开发(SDD) + 测试驱动开发(TDD)” 的完整工作流。

如果觉得手动切换两个工具比较繁琐,可以尝试 Comet 将整个流程自动化。
2026-05-27 04:00:00

前三篇我们完成了 DRA 的部署实战、核心概念拆解和工作流程分析。但一直使用的是 NVIDIA 官方的 DRA Driver,如果我们的硬件不是 GPU,或者只是想暴露自定义资源给 Pod 使用,该怎么办?
本文从零实现一个自定义 DRA Driver —— i-dra-driver,沿用之前 Device Plugin 文章 中的 “gopher” 资源隐喻,将节点上的文件作为设备暴露给 Pod。通过对比同一个功能在 DevicePlugin 和 DRA 两种框架下的实现差异,加深对 DRA 机制的理解。
完整源码: github.com/lixd/i-dra-driver
在开始编码之前,先通过一张表直观对比两种框架在实现同一个功能时的差异:
| 维度 | DevicePlugin | DRA |
|---|---|---|
| 设备发现 |
ListAndWatch gRPC 上报设备列表 |
创建 ResourceSlice API 对象 |
| 资源可见性 | Node capacity 字段(只有数量) |
ResourceSlice 对象(含完整属性) |
| 调度参与 | 调度器不参与,kubelet 本地分配 | 调度器参与,PreFilter/Filter/Reserve |
| 设备选择 | 只能按数量申请 | CEL 表达式精确匹配属性 |
| 设备注入 |
Allocate 返回 env/mounts |
CDI spec 定义注入规则 |
| 注册方式 | kubelet Registration gRPC | plugins_registry socket |
| 核心接口 |
ListAndWatch + Allocate
|
PrepareResourceClaims + UnprepareResourceClaims
|
| 辅助库 | 无(直接实现 gRPC) | k8s.io/dynamic-resource-allocation/kubeletplugin |
一句话总结:DevicePlugin 是 “kubelet 本地管理”,DRA 是 “API Server 全局调度 + CDI 标准注入”。

DevicePlugin 通过 ListAndWatch 流式上报设备,而 DRA Driver 需要将设备信息写入 ResourceSlice API 对象。
pkg/device/discovery.go:
|
|
与 DevicePlugin 的区别:
ListAndWatch 是一个长连接流,设备变化时主动推送Discover 是一个普通函数,调用后返回当前设备列表,变化通过定期 rescan 或后续更新 ResourceSlice 来体现pkg/device/resourceslice.go:
|
|
关键点:
Attributes 存放字符串/布尔/版本等设备属性,可用于 CEL 选择器筛选Capacity 存放数值型容量信息(如文件大小、GPU 显存)nvidia.com/gpu: "4")强大得多这是 DRA Driver 的核心:实现 kubeletplugin.DRAPlugin 接口的三个方法。
|
|
当 kubelet 需要为 Pod 准备设备时调用。我们需要:
status.allocation 中提取已分配的设备
|
|
与 DevicePlugin Allocate 的区别:
DevicePlugin Allocate
|
DRA PrepareResourceClaims
|
|
|---|---|---|
| 输入 | 容器维度(ContainerAllocateRequest) | ResourceClaim 维度(可能跨容器) |
| 设备确定 | kubelet 本地决定 | 调度器已分配,status.allocation 指定 |
| 注入方式 | 返回 env/mounts/devices | 返回 CDI device ID |
| 幂等性 | 非强制 | 必须幂等 |
Pod 删除时调用,清理 CDI spec:
|
|
注意:此方法只收到 NamespacedObject(UID + Name + Namespace),拿不到完整的 ResourceClaim 对象,因为 claim 可能已被删除。必须幂等。
辅助库的后台错误回调:
|
|
可恢复错误(如 ResourceSlice 更新暂时失败)只记录日志,致命错误则退出进程。
CDI (Container Device Interface) 是 DRA 的设备注入机制。Driver 创建 CDI spec 文件,容器运行时读取后按 spec 将设备注入容器。
对于我们的 “gopher” 设备,CDI spec 主要负责注入环境变量。文件挂载通过 Pod 的 hostPath volume 实现(因为普通文件的 bind mount 通过 CDI 注入时存在兼容性问题,hostPath volume 是更可靠的方式)。
pkg/cdi/handler.go:
|
|
与 DevicePlugin Allocate 的注入方式对比:
| DevicePlugin | DRA + CDI | |
|---|---|---|
| 环境变量 |
Allocate 返回 Envs map
|
CDI spec 的 ContainerEdits.Env
|
| 文件挂载 |
Allocate 返回 Mounts
|
CDI spec 的 ContainerEdits.Mounts 或 Pod hostPath volume |
| 设备节点 |
Allocate 返回 Devices
|
CDI spec 的 ContainerEdits.DeviceNodes
|
| 标准 | Kubernetes 私有 | OCI 标准规范 |
CDI 是开放容器标准,不绑定 Kubernetes,容器运行时原生支持。对于 GPU 等真实硬件设备,CDI 的 DeviceNodes 和 Mounts 是首选注入方式;对于文件类的简单设备,环境变量 + hostPath volume 的组合更实用。
cmd/main.go:
|
|
与 DevicePlugin 启动流程对比:
| 步骤 | DevicePlugin | DRA Driver |
|---|---|---|
| 启动 gRPC Server | 手动创建 grpc.Server + 监听 unix socket |
kubeletplugin.Start 自动处理 |
| 注册到 Kubelet | 手动连接 KubeletSocket 发送 RegisterRequest
|
kubeletplugin.Start 自动处理 |
| 发布设备信息 |
ListAndWatch 流式推送 |
helper.PublishResources() 创建 ResourceSlice |
| 监听 Kubelet 重启 | 手动 fsnotify 监听 socket |
辅助库自动处理 |
DRA 的 kubeletplugin.Start 一行代码就解决了 DevicePlugin 需要几十行代码处理的注册和生命周期管理。
以下在 Kubernetes v1.35.0 + containerd v1.7.29 的单节点集群上实际部署验证。
|
|
在节点上创建测试用的 gopher 设备文件:
|
|
|
|
验证 Driver Pod 已启动:
|
|
查看 Driver 日志,确认设备发现和 ResourceSlice 发布成功:
|
|
|
|
查看详情,确认设备属性和容量信息:
|
|
可以看到,与 DevicePlugin 只能在 Node capacity 上显示 nvidia.com/gpu: "4" 不同,DRA 的 ResourceSlice 记录了每个设备的完整属性(type=gopher)和容量(size=20)。
|
|
查看 DeviceClass 详情,确认 CEL 选择器:
|
|
CEL 表达式定义了"什么样的设备属于这个 Class":driver 是 gopher.example.com 且 type 是 gopher。用户申请资源时只需指定 deviceClassName: gopher.example.com,不需要关心具体的设备名。
|
|
验证 Pod 状态:
|
|
查看 ResourceClaim 分配结果:
|
|
状态为 allocated,reserved,说明调度器已成功分配设备并预留。查看分配详情:
|
|
查看 Pod 日志,确认设备已成功注入:
|
|
环境变量 GOPHER=gopher-a 通过 CDI spec 注入,文件内容 hello from gopher-a 通过 hostPath volume 读取。整个 DRA 流程从设备注册到 Pod 使用,全链路打通!
进一步在 Pod 内部验证:
|
|
从 Driver 启动到 Pod 使用设备,完整数据流如下:
|
|
对比 DevicePlugin 的流程:
|
|
核心区别:DRA 的调度器在调度阶段就选好了具体设备并绑定节点,不会出现 DevicePlugin 中"调度到节点后才发现资源不匹配"的问题。
本文通过实现 i-dra-driver,从代码层面对比了 DevicePlugin 和 DRA 的核心差异:
ListAndWatch gRPC 流 → ResourceSlice API 对象kubeletplugin 辅助库一行搞定DRA 的设计理念是让设备信息对调度器可见、让设备注入遵循开放标准、让驱动开发者专注于业务逻辑。虽然目前只在 Kubernetes 1.34 GA,但作为 DevicePlugin 的继任者,DRA 是 Kubernetes 资源管理的未来方向。
系列回顾:
2026-05-21 04:00:00

前两篇我们完成了 DRA 的部署实战和核心概念拆解,知道了 ResourceSlice、DeviceClass、ResourceClaim 各自的职责和协作方式。
但还有一个问题没回答:从 Pod 提交到 GPU 可用,中间到底发生了什么?每个组件具体做了哪些事?
本篇逐阶段拆解 DRA 的端到端工作流,每个阶段结合 NVIDIA DRA Driver 源码分析,然后深入调度器分配算法。
以 P1 的 gpu-test-pod 为例,从 Pod 提交到 GPU 可用,完整流程分为六个阶段:
| 阶段 | 触发者 | 动作 |
|---|---|---|
| 1. 设备注册 | DRA Driver | NVML 扫描 GPU → 注册到 Kubelet → 发布 ResourceSlice |
| 2. 分类定义 | Admin / Helm | 创建 DeviceClass(CEL 选择器) |
| 3. 用户声明需求 | User | 创建 Pod + ResourceClaimTemplate → Controller 生成 ResourceClaim |
| 4. 调度器分配 | Scheduler | PreFilter 构建分配器 → Filter 选节点 + 选设备 → Reserve → PreBind 持久化 |
| 5. 设备准备与注入 | Kubelet → Driver → CDI | NodePrepareResources → 生成 CDI 描述 → 容器运行时注入设备 |
| 6. Pod 运行与清理 | Kubelet + Controller | NodeUnprepareResources → Controller 清除分配 → 设备回可用 |

下面逐阶段展开,每个阶段结合 NVIDIA DRA Driver 源码(cmd/gpu-kubelet-plugin/)分析。
|
|
和 DevicePlugin 的区别:DevicePlugin 通过 ListAndWatch() gRPC 把设备列表上报给 Kubelet,Kubelet 再更新到 Node 的 capacity 字段,只有数量。DRA Driver 直接创建 ResourceSlice 对象,包含完整的设备属性,调度器可见。
NewDriver() 是整个驱动的初始化入口,核心步骤对应设备注册的全过程(省略了 DynamicMIG 处理、健康检查启动等中间步骤):
|
|
设备发现的逻辑在 nvlib.go 中:
|
|
每个 GPU 的信息封装为 GpuInfo 结构体(deviceinfo.go),包含 UUID、型号、架构、显存、CUDA 计算能力、驱动版本、MIG 能力等完整属性。
对比 DevicePlugin:同样通过 NVML 发现设备,但结果只上报为 nvidia.com/gpu:4 这样一个整数。
ResourceSlice 发布由 publishResources() 完成,有两种模式:
GenerateDriverResources 生成资源,每张物理 GPU 一个 ResourceSlice(K8s 1.35+ 会进一步拆分为 G+1 个)
|
|
管理员创建 DeviceClass,定义"什么样的设备属于这一类":
|
|
CEL 表达式可以组合任意属性条件,例如只包含 A100:
|
|
这一步一般在安装 DRA Driver 时由 Helm Chart 自动创建,用户通常不需要手动操作。
用户创建 ResourceClaimTemplate 和 Pod:
|
|
Pod 提交后,ResourceClaim Controller 根据 Template 自动创建 ResourceClaim(Pod 作为 OwnerReference,垃圾回收自动清理),然后 Pod 进入调度队列。
ResourceClaim Controller 的逻辑:
GenerateName 避免命名冲突,Pod 作为 OwnerReference 保证随 Pod 一起删除ReservedFor 条目ReservedFor 条目;当 ReservedFor 为空时,清除分配并移除 Finalizer;基于模板生成的 Claim 最终由垃圾回收器删除(Controller 也会在确认 Pod 不再运行时主动删除)ResourceClaim 的状态流转和回收机制如下图所示:

这是 DRA 和 DevicePlugin 差异最大的阶段。DRA 的调度器实现了调度框架的多个扩展点:

调度器不仅选节点,还选定了具体设备。分配结果在调度阶段就确定了,写入 ResourceClaim.status.allocation,后续 Driver 和 Kubelet 都基于这个结果工作。
调度器内部有三层 Claim 跟踪机制防止并发冲突:

对比 DevicePlugin:调度器只检查 nvidia.com/gpu 数量够不够,具体用哪张 GPU 由 Kubelet 本地的 Allocate() 决定。
DRA 调度器插件 DynamicResources 实现了调度框架的 9 个扩展点接口:PreEnqueue、PreFilter、Filter、PostFilter、Score、Reserve(含 Unreserve)、EnqueueExtensions、PreBind、SignPlugin。
PreEnqueue — 验证所有 ResourceClaim 存在且未被删除,不满足的 Pod 留在不可调度队列。
PreFilter — 收集所有 Claim、构建分配器,是调度周期中最重的准备工作:
|
|
Filter — 对每个候选节点调用分配器,判断节点是否满足需求:
|
|
PostFilter — 所有节点都不满足时,对未被其他 Pod 预留的 Claim 同时清除 Allocation、ReservedFor 和 Devices 三个字段,清除后 Pod 会在 Claim 状态变更事件触发时重新入队调度。
Reserve — 选定节点后,将 Claim 标记为进行中分配,防止并发冲突:
|
|
PreBind — 将分配结果持久化到 API Server:
|
|
bindClaim 内部通过 retry.RetryOnConflict 将 claim.Status.Allocation 和 claim.Status.ReservedFor 写入 API Server。
Unreserve — 调度失败时回滚:移除进行中分配(MaybeRemoveClaimPendingAllocation),仅在成功移除时恢复 AssumeCache(AssumedClaimRestore),并通过 strategic-merge-patch 移除 Pod 的 ReservedFor 条目。此外还会清理 PodGroup 的 pendingAllocations、扩展资源 Claim 以及 NodeAllocatable 资源状态。
调度器内部通过 claimTracker(dra_manager.go)维护三层跟踪,防止并发调度导致资源冲突:
调度器 Filter 阶段的核心是结构化分配器(Structured Allocator),负责从 ResourceSlice 的设备中找到满足 Claim 需求的设备组合。
分配器分层:
| 层级 | 支持的特性 |
|---|---|
| stable | AdminAccess, PrioritizedList, PartitionableDevices, DeviceTaints |
| incubating | stable + DeviceBindingAndStatus, ConsumableCapacity |
| experimental | incubating + ListTypeAttributes |
调度器按稳定性排序选择:选第一个支持所需 Feature Gate 集的分配器。各层完全独立,当孵化层代码足够成熟时,整体晋升到稳定层。
分配算法分三个阶段:
Phase 1 — 收集池(GatherPools):收集与目标节点相关的 ResourceSlice,按 (Driver, PoolName) 分组,构建候选设备池。
Phase 2 — 验证请求:检查选择器使用 CEL、验证 DeviceClass 引用、确定设备数量或列表。
Phase 3 — 递归搜索:
|
|
CEL 选择器评估两个优化:编译缓存(每表达式编译一次)、设备匹配缓存(缓存每个 (设备, 请求) 对的布尔结果)。
约束检查两种类型:matchAttribute(同值约束,如同型号 GPU)、distinctAttribute(异值约束)。分配时逐一检查,失败则回滚之前已添加的约束。注意 distinctAttribute 仅 incubating 及以上层级支持,stable 层只支持 matchAttribute。
NewAllocator(allocator.go:127)按稳定性排序遍历 availableAllocators,选第一个 supportedFeatures 包含所需 Feature Gate 集的分配器。三层分配器传入的参数略有不同:stable 传入 AllocatedDevices,incubating 和 experimental 传入完整的 allocatedState。
stable 分配器的 Allocate(allocator_stable.go:110)分三阶段执行:
Phase 1:收集池 — GatherPools(pools_stable.go:58)遍历所有 ResourceSlice,按 (Driver, PoolName) 分组构建候选设备池。含 SharedCounters 的 Slice 直接加入(无需节点匹配);其余按 Slice 级别的 NodeName/AllNodes/NodeSelector 和 PerDeviceNodeSelection(设备级别节点选择)进行节点匹配。
Phase 2:验证请求 — 对每个 Claim 的每个 Request 调用 validateDeviceRequest,确定设备选择器和候选设备列表,同时构建约束。注意 DistinctAttribute 仅 incubating 及以上层级支持,stable 层遇到会报错。
Phase 3:递归搜索 — 调用 allocateOne(deviceIndices{}, false) 启动分配。
allocateOne 是分配算法的递归函数(allocator_stable.go:766),逐一为每个 Claim 的每个 Request 选择设备。逻辑和 5.1 节的伪代码一致,这里补充几个实现细节:
allocateDevice(allocator_stable.go:1105)— 检查设备可用性并标记分配,成功时返回回滚函数:
deviceInUse),AdminAccess 允许分配其他 Claim 已占用的设备(但不允许同一 Claim 内重复分配)checkAvailableCounters)taintPreventsAllocation)constraint.add),失败时回滚之前已添加的约束
|
|
Kubelet DRA Manager 的 Checkpoint 机制保证重启后状态恢复。还有一个协调循环(每 60 秒)扫描不活跃 Pod 的 Claim 执行 Unprepare。
上面是 Kubelet DRA Manager 的流程。当 Kubelet 通过 gRPC 调用 Driver 的 NodePrepareResources 时,进入 NVIDIA Driver 的处理逻辑。调用入口是 nodePrepareResource()(driver.go:373),获取全局 flock 后委托给 DeviceState.Prepare()(device_state.go:229):
|
|
几个设计要点:
prepareDevices() 的输入是 claim.Status.Allocation,Driver 只需按分配结果准备,不需要自己选设备CDI(Container Device Interface)是容器运行时级别的设备注入标准。DRA Driver 在 Prepare 阶段生成 CDI 描述文件,最终靠它把 GPU 注入到容器里。
|
|
生成的 CDI 设备名称格式:k8s.gpu.nvidia.com/claim=<claimUID>-<canonicalName>
CDI 设备 ID 返回给 Kubelet 后,Kubelet 通过 GetResources 为容器运行时提供 CDI 设备列表,容器运行时根据 CDI 描述文件完成挂载设备文件、驱动库、注入环境变量。Unprepare 时删除 CDI 描述文件,容器运行时就不再能访问这些设备。
对比 DevicePlugin:Allocate() 返回设备路径和环境变量的列表,Kubelet 自己手动挂载,每个 DevicePlugin 各自实现。DRA 通过 CDI 标准化了设备注入流程。
DRA Driver 通过全局文件锁(flock)串行化所有 Prepare/Unprepare 操作,保证同一时刻只有一个操作在执行。驱动在调用 kubeletplugin.Start() 时显式设置了 Serialize(false),禁用 kubeletplugin 自带的序列化机制,转而通过 flock 自行控制并发。
|
|
|
|
|
|
unprepareDevices() 按设备类型执行相应清理(如 VFIO 反配置、MIG 设备删除等)。CDI 描述文件的删除和 Checkpoint 中 Claim 的移除由调用方 Unprepare() 负责,不在 unprepareDevices() 内部。
从 Pod 提交到 GPU 可用,DRA 的工作流程可以概括为:Driver 注册设备 → 用户声明需求 → 调度器分配具体设备 → Kubelet 准备并注入 → Pod 终止后清理回收。
和 DevicePlugin 的根本区别:DevicePlugin 要自己管发现、分配、注入全流程;DRA Driver 只需实现 NodePrepareResources/NodeUnprepareResources,分配决策由调度器完成。
2026-05-20 04:00:00

RTK (Rust Token Killer) 是一个 CLI 代理工具,它的核心思路是在命令输出到达 AI 之前把噪音过滤压缩掉,从而实现平均省 60-90% 的 token 的效果。
你有没有算过,Claude Code 一个小时的会话,光 git status、cargo test 这些命令的输出就吃掉多少 token?
原因在于大多数 CLI 命令的输出是给人看的——带进度条、带彩色、带提示语、带格式化。但对 AI 来说,git push 输出的 15 行 “Enumerating objects…” 毫无意义,它只需要知道 “ok main”。
|
|
验证一下:
|
|
一条命令搞定——给你的 AI 工具装上 Hook:
|
|
配置完 RTK 之后重启 AI 工具就行。
之后你在 AI 会话里执行 git status,它会被自动重写成 rtk git status——AI 收到的是压缩后的输出,但你完全无感。
效果对比:
|
|
|
|
30 分钟典型会话的估算节省:
| 命令 | 频次 | 原始 token | rtk 后 | 节省 |
|---|---|---|---|---|
git status |
10x | 3,000 | 600 | -80% |
cargo test |
5x | 25,000 | 2,500 | -90% |
git diff |
5x | 10,000 | 2,500 | -75% |
cat/read
|
20x | 40,000 | 12,000 | -70% |
| 合计 | ~118,000 | ~23,900 | -80% |

RTK 是一个 CLI 代理,拦截命令输出并压缩后再交给 AI。
|
|
对不同类型的命令,RTK 用不同策略压缩:
ok,省 92%RTK 不认识的命令会原样执行,不做压缩——永远不会因为 RTK 导致命令失败。
rtk init -g 会在你的 AI 工具配置里加一个 Hook,让每次 Bash 命令执行前自动走 RTK。以 Claude Code 为例,它在 ~/.claude/settings.json 里加了一个 PreToolUse Hook:
|
|
每次 Claude Code 执行 Bash 命令前,这个 Hook 会先触发 rtk hook claude,由 RTK 接管命令的执行和输出压缩。
|
|
RTK 支持两种方式添加命令过滤:
src/cmds/):适合需要复杂解析的命令,比如 pytest 用状态机追踪测试状态、go test 用 NDJSON 流式解析src/filters/*.toml):声明式规则,适合简单的行过滤/替换,比如 helm、shellcheck
内置了 100+ 命令支持,你也可以在 .rtk/filters.toml 里写自定义规则。
|
|
全局标志:-u 开启超压缩模式(ASCII 图标 + 内联格式),-v/-vv/-vvv 逐级调试。
RTK 是一个 CLI 代理工具——Token Killer 名字很贴切,在命令输出到达 AI 之前把噪音过滤掉。它解决了一个很实际问题:AI 编程代理执行命令时大量无意义输出导致的 token 浪费。
用起来没什么感知,看账单才知道省了多少,时不时跑下 rtk gain 看看又省了多少 Token 哈哈。
2026-05-14 20:00:00

HAMi 是目前 Kubernetes 上最活跃的开源 vGPU 方案,能够将一块物理 GPU 按显存和算力细粒度地切分为多个虚拟 GPU,供不同 Pod 共享。
本文聚焦 HAMi DRA 模式的部署与使用:安装 HAMi DRA 驱动后,分别用原生模式和兼容模式提交 Pod,验证 GPU 切分是否生效。
Kubernetes 在 1.34 中正式 GA 了 DRA(Dynamic Resource Allocation,动态资源分配)。DRA 的核心改进是让调度器参与资源分配,在 Pod 调度阶段就精确匹配设备属性,避免了 DevicePlugin “调度到节点后才发现资源不够” 的问题。
HAMi 最近的版本已经正式接入了 DRA,用户既可以使用原生 DRA 模式,也可以用 DevicePlugin 兼容模式无缝迁移。
HAMi(异构 AI 计算虚拟化中间件)是一个用于管理 Kubernetes 集群中异构 AI 计算设备的开源平台。前身为 k8s-vGPU-scheduler,HAMi 可在多个容器和工作负载之间实现设备共享。
HAMi 是云原生计算基金会(CNCF)的 Sandbox 项目,并被收录于 CNCF 技术全景图和 CNAI 技术全景图。

设备共享
内存管理
设备规格
易用性
开放治理
前提条件:
K8s 1.34 及以上版本,同时开启 DRAConsumableCapacity Feature Gate
Container Runtime 必须开启 CDI
NVIDIA GPU 驱动 440 及以上版本
特别是第一条,DRAConsumableCapacity 在 1.36 才默认开启,1.34、1.35 需手动配置。
安装 GPU Operator 时需要关闭 DevicePlugin:
|
|
--set devicePlugin.enabled=false:关闭 DevicePlugin,避免与后续安装的 DRA Driver 冲突。
HAMi DRA Webhook 需要 TLS 证书,因此需要提前安装 cert-manager 用于自动签发。
|
|
为节点打上 gpu=on 标签,未标记的节点不会被 HAMi 接管。
|
|
使用以下命令添加 HAMi 图表仓库:
|
|
用以下命令进行安装:
|
|
注意:DRA 模式与传统模式不兼容,请勿同时启用。
另外如果 GPU 驱动是主机预装,非 GPU Operator 安装,则安装时需额外指定:
|
|
正常情况下,会在 hami-system 下启动以下 Pod
|
|
查看 dra-driver 是否正常发布 resourceslice:
|
|
详情如下:
|
|
可以看到,ResourceSlice 记录了 GPU 的架构、型号、显存等信息。
DRA 原生使用流程是先创建 ResourceClaim,然后创建 Pod 使用该 ResourceClaim。

ResourceClaim 以及对应 Pod 完整 yaml 如下:
|
|
通过 ResourceClaim 可以看到资源分配情况:
|
|
Pod 中执行 nvidia-smi 看到显存是我们申请的 10G,说明 HAMi 生效了。
|
|
原生 DRA 模式需要手动创建 ResourceClaim,对存量业务不够友好。
为了便于大家迁移,HAMi 提供了兼容模式:用户仍然像 DevicePlugin 那样申请资源,由 HAMi DRA Webhook 自动拦截并转换为 ResourceClaim,调度器分配后再挂载到 Pod。
和使用 DevicePlugin 一样,正常在 resources 中申请资源即可:
|
|
HAMi 会根据 nvidia.com/gpumem、nvidia.com/gpucores 自动生成 ResourceClaim,并绑定到 Pod。
对应的 ResourceClaim 如下:
|
|
核心配置:
|
|
对比原始 Pod 的资源申请:
|
|
Webhook 转换映射关系:
| 原始资源申请 | ResourceClaim 字段 |
|---|---|
nvidia.com/gpu: 1 |
requests.count: 1 |
nvidia.com/gpumem: 10240 |
requests.capacity.memory |
nvidia.com/gpucores: 50 |
requests.capacity.cores |
同样的,显存为 10240M,说明 HAMi 也生效了。
|
|
本文围绕 HAMi DRA 模式完成了从安装到验证的完整实践:
dra.enabled=true
nvidia.com/gpu 等资源申请,HAMi DRA Webhook 自动转换为 ResourceClaim,存量业务零改造即可迁移两种模式的核心差异在于 ResourceClaim 的创建方式——原生模式手动管理、兼容模式自动生成,底层调度与切分逻辑一致。