Velero install 命令 --apply 标志:基于 Server-Side Apply 的存量安装升级方案
【免费下载链接】veleroBackup and migrate Kubernetes applications and their persistent volumes项目地址: https://gitcode.com/GitHub_Trending/ve/velero
本篇文章围绕 Velero 官方设计文档 apply-flag.md 展开,讲解为velero install命令新增的--apply标志:它如何借助 Kubernetes Server-Side Apply(SSA)在不删除已有资源的前提下,将新的安装清单应用到已存在的 Velero 部署上,从而简化传统"三步式"升级流程。读完本文,你将掌握该标志的设计动机、底层实现原理(字段管理器与force=true的行为)、与--dry-run/--crds-only的组合用法,以及它的能力边界与安全考量。
一、背景:为什么需要--apply
1.1 install 命令的"只建不更"困境
Velero 的velero install命令职责是在 Kubernetes 集群中一次性创建整套 Velero 资源:所有必需的 CustomResourceDefinition(CRD)、命名空间、ServiceAccount、RBAC、Velero Deployment 以及(启用时)node-agent DaemonSet。在首次安装场景下,create语义完全够用。
但一旦集群中已经存在一套 Velero 安装,问题就出现了。在实现--apply之前,安装流程底层调用的是资源创建接口(c.Create),对已存在的资源直接报 "already exists" 错误。设计文档 apply-flag.md 明确指出:
用户对已有安装重复执行 install 命令会收到 "already exists" 消息;而既有安装的升级通常需要三步甚至更多步骤:先用
--dry-run生成新 CRD 清单、再管道交给kubectl apply,随后再手工更新 Velero Deployment 与 node-agent 的镜像。
这种多步操作既繁琐又容易遗漏,尤其 CRD 的 schema 变更若未及时应用,可能导致新版本 server 无法正常工作。
1.2 设计目标与非目标
设计文档 Goals / Non Goals 划定了清晰的边界:
| 目标(Goals) | 非目标(Non Goals) |
|---|---|
| 提供一个简单标志,在既有安装上"应用"资源 | 不实现特定版本到版本的专用升级逻辑(如资源删除) |
| 使用 Server-Side Apply 更新已有资源,而非试图重建 | 不增加复杂的升级校验或升级前/后钩子 |
| 与常规安装流程保持一致 | 不提供回滚能力 |
这意味着--apply被刻意设计成一个**通用、尽力而为(best-effort)**的机制:它可以作为升级流程的一部分,但不保证替你处理所有跨版本破坏性变更。
二、高层设计:一个标志切换"创建"与"应用"
--apply是一个布尔标志,挂在velero install命令上。当开启时,安装流程不再走create,而是对同一批清单资源走Server-Side Apply,用服务端的最新状态覆盖旧状态。
关键设计决策是不新造命令:不引入单独的velero upgrade子命令,而是复用现有 install 命令的完整逻辑(资源生成、顺序编排、CRD 等待就绪等),仅在"落库"环节切换语义。这样既避免了代码重复,也保证了 apply 与 install 走完全相同的资源清单与顺序。
三、详细设计:从 CLI 标志到 SSA 调用链
3.1 CLI 层:标志定义与参数传递
在 pkg/cmd/cli/install/install.go 中,Options结构体新增了Apply bool字段(第 97 行),并在BindFlags中注册了命令行标志(第 108 行):
flags.BoolVar(&o.Apply, "apply", o.Apply, "Flag indicating if resources should be applied instead of created. This can be used for updating existing resources.")该标志的默认值为false,因此对现有用户完全可选、向后兼容——不加该标志时行为与旧版本一致。
在Run方法中,o.Apply被透传到资源安装函数(第 443 行):
err = install.Install(dynamicFactory, kbClient, resources, os.Stdout, o.Apply)注意Run的执行顺序:先调用output.PrintWithFormat输出资源清单(支持-o yaml/json),若指定了--dry-run则直接返回、不发往集群;只有在真正执行安装时才走Install。也就是说--apply只影响"发送到集群"这一步,不影响资源生成与输出。
3.2 核心实现:createOrApplyResource
安装的核心逻辑位于 pkg/install/install.go 的createOrApplyResource函数(第 284 行起)。它根据apply布尔值走两条路径:
非 apply 路径(默认):调用c.Create(r);若返回apierrors.IsAlreadyExists,仅打印 "already exists, proceeding" 并继续,不中断安装——这与文档中描述的"收到 already exists 消息"现象吻合,只是在新代码中它被降级为一条日志。
apply 路径:构造metav1.ApplyOptions并调用动态客户端的Apply方法:
if apply { log("attempting to apply resource") // Set field manager for server-side apply and force to override conflicts applyOpts := metav1.ApplyOptions{ FieldManager: "velero-cli", Force: true, } if _, err := c.Apply(r.GetName(), r, applyOpts); err != nil { return errors.Wrapf(err, "Error applying resource %s", id) } log("applied") }两个关键参数值得展开:
FieldManager: "velero-cli":这是 SSA 的字段级所有权标识。Kubernetes 会记录"velero-cli"这个管理器对每个字段的最后应用状态,后续再次 apply 时以该管理器的视图为准进行三方合并。这意味着即使集群中有其他控制器(如 Helm)也在管理部分字段,只要不冲突,SSA 可以做到字段级别的精确合并,而不是整对象替换。Force: true:当其他字段管理器持有某些字段的所有权(ownership)而产生冲突时,force=true会覆盖冲突,以velero-cli的视图为准强制更新。正如设计文档 Security Considerations 所提示,这可能会覆盖人工对资源的修改,但这是保证 apply 一定成功的必要代价。
3.3 调用链底层:动态客户端 Apply 的实现
Apply方法定义在动态客户端封装 pkg/client/dynamic.go 中。Dynamic接口新增了Apply签名(第 107 行),dynamicResourceClient的实现直接委托给dynamic.ResourceInterface(第 145-147 行):
func (d *dynamicResourceClient) Apply(name string, obj *unstructured.Unstructured, opts metav1.ApplyOptions) (*unstructured.Unstructured, error) { return d.resourceClient.Apply(context.TODO(), name, obj, opts) }从源码结构看,它对应的是 client-go 中resourceClient.Apply的 PATCH 语义(Content-Type 为application/apply-patch+yaml),因此 apply 本质上是一次服务端 PATCH 请求,而非 DELETE + CREATE,这也是它能做到"更新而非重建"的根本原因。
3.4 资源顺序:CRD 先行并等待就绪
Install函数(第 355 行起)完整保留了安装流程的顺序保证,与设计文档 Detailed Design 中的约定一致:
- 调用
GroupResources将清单按kind == "CustomResourceDefinition"拆成CRDResources与OtherResources两组; - 先对 CRD 逐个执行
createOrApplyResource; - 通过
crdsAreReady等待 CRD 在集群中established(最多等待 1 分钟,超时报 "timeout reached, CRDs not ready"); - 再按序应用其余所有资源(Namespace、ServiceAccount、Deployment、DaemonSet 等)。
这一顺序在 apply 模式下同样成立:升级时 CRD 的 schema 必须先就绪,Velero server 等 CR 的实例化依赖新 schema 才能正常工作。
3.5 测试佐证
仓库中的单元测试直接验证了"创建/应用"两条路径的互斥行为:
- pkg/install/install_test.go 中的
TestInstallWithApplyFlag构造了一个 ConfigMap 资源,分别以apply=false与apply=true调用Install:false时断言Create被调用、Apply未被调用;true时断言Apply被调用、Create未被调用。
- 同文件中的
TestCreateOrApplyResourceApplyError(第 252 行起)模拟Apply返回错误,验证 apply 失败时会正确向上传播错误。
四、实战用法:升级既有 Velero 安装
4.1 一次性应用整套资源
最直接的用法是在升级时对整个安装重新执行 install 并加上--apply,让命令把当前版本的完整清单(含新 CRD、新 RBAC、新镜像配置等)应用到集群:
# 以新的镜像与插件信息重新应用整套资源 velero install \ --provider aws \ --plugins velero/velero-plugin-for-aws:v1.1.0 \ --bucket backups \ --secret-file ./aws-iam-creds \ --image velero/velero:v1.15.0 \ --use-node-agent \ --apply执行过程中,每个资源会打印形如Deployment/velero: applied的日志;--apply失败时,命令会给出包含kubectl logs deploy/velero -n <namespace>提示的错误信息,便于排查。
4.2 组合--crds-only:仅升级 CRD
升级时 CRD 往往是最敏感的一环。可以结合既有的--crds-only标志,只对 CRD 执行 apply,先完成 schema 升级,再单独处理应用层:
# 仅重新应用 CRD(等价于传统的 dry-run + kubectl apply,但一条命令完成) velero install --crds-only --apply这正是设计文档 Compatibility 中提到的、Helm Chart 也可借鉴的模式——用它简化 CRD 更新 Job。相比传统做法velero install --crds-only --dry-run -o yaml | kubectl apply -f -,--crds-only --apply直接由 Velero 客户端完成 SSA,省去管道与手工步骤。
4.3 组合--dry-run:先预览再执行
--apply与--dry-run可安全组合:--dry-run会让命令在发送到集群之前返回,因此可以先用它生成并审查将被 apply 的完整清单,确认无误后再去掉--dry-run真正执行:
# 预览将被应用的全部资源 velero install --provider gcp --plugins velero/velero-plugin-for-gcp:v1.1.0 \ --bucket gcp-backups --secret-file ./gcp-creds.json \ --apply --dry-run -o yaml五、备选方案回顾:为什么不做单独的 upgrade 命令
设计文档 Alternatives Considered 记录了三类被否决的候选方案,理解它们有助于把握--apply的设计哲学:
- 独立的
upgrade命令:会大量复制 install 命令的资源生成与流程编排逻辑,导致代码重复与维护负担,被否决。 - 版本专用升级逻辑:为每对版本路径编写迁移逻辑过于复杂、难以长期维护;文档明确表示未来可能重新考虑,但不在当前设计范围内。
- 自动探测已有资源并切换 apply 模式:可能让用户在不知情的情况下意外覆盖已有资源的修改,行为不可预期,被否决。
--apply选择把决定权显式交还用户:只有主动加标志才启用覆盖式更新,符合"显式优于隐式"的 CLI 设计惯例。
六、安全与兼容性边界
- 权限面不变:apply 所需的权限与创建资源一致,不额外要求更多 RBAC 权限(见设计文档 Security Considerations)。
- 尽力而为,不跨版本打包票:文档明确强调 apply 是 best-effort 的,不保证任意两个 Velero 版本之间的资源兼容;破坏性变更仍需查阅 release notes,必要时手工介入。
- 可能覆盖人工修改:
force=true在遇到字段所有权冲突时会以velero-cli的视图覆盖,因此不建议在集群资源被大量手工调优且未纳入版本管理的场景下盲目使用。 - 向后兼容:作为新的 opt-in 标志,不改变任何资源格式与 API 契约,对所有既有安装均兼容。
七、总结
--apply是 Velero 在"安装"与"升级"之间架起的一座小桥:它没有引入全新的命令或复杂的版本迁移框架,而是复用 install 命令已有的资源清单与执行顺序,通过 Server-Side Apply(FieldManager: "velero-cli"、force=true)把"创建"语义平滑切换为"更新"语义。从 CLI 标志(install.go)、核心落库函数(createOrApplyResource)到动态客户端封装(dynamic.go)与单元测试(install_test.go),整条链路清晰、可验证。它适合作为升级流程的组成部分,配合--crds-only可简化 CRD 更新,配合--dry-run可先行预览——理解其字段所有权与 force 语义,就能安全地把这条命令纳入你的 Velero 日常运维。
【免费下载链接】veleroBackup and migrate Kubernetes applications and their persistent volumes项目地址: https://gitcode.com/GitHub_Trending/ve/velero
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考