ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

client-go 动态客户端实战:使用 dynamic 包对 Deployment 执行增删改查

2026/10/7 15:22:11 拓冰建站 浏览量
client-go 动态客户端实战:使用 dynamic 包对 Deployment 执行增删改查 云原生后端【免费下载链接】client-goGo client for Kubernetes.项目地址https://gitcode.com/gh_mirrors/cl/client-go点击查看免费下载本篇指南基于 client-go 仓库中的官方示例 dynamic-create-update-delete-deployment讲解如何借助dynamic包以运行时绑定的方式完成 Kubernetes Deployment 资源的 Create、List、Update、Delete 全流程操作。读完本文你将掌握unstructured.Unstructured对象模型、schema.GroupVersionResource资源定位、以及基于重试机制的并发安全更新等一套可直接复用的通用资源管理方案。为什么要用 dynamic 客户端Typed vs. DynamicKubernetes API 客户端存在两条截然不同的技术路线本示例正是基于 create-update-delete-deployment 这个 Typed 客户端版本改写而来二者对比是理解 dynamic 包价值的最佳入口。Typed 客户端类型化客户端通过预生成的本地 API 对象如k8s.io/api/apps/v1中的appsv1.Deployment与 API Server 通信获得类似 RPC 的编程体验。编译器在编译期即可强制数据安全并做部分校验如字段类型、必填项。代价是程序与特定的 API 版本和类型强耦合——k8s.io/api中某个字段一旦变更客户端代码就必须重新编译。Dynamic 客户端动态客户端使用一个简单类型unstructured.Unstructured表示来自 API Server 的所有对象值。该类型内部以嵌套的map[string]interface{}集合构建结构与 API Server 返回的 REST 载荷形态高度接近JSON 天然映射为嵌套 map。所有数据绑定都被推迟到运行时才发生因此使用 dynamic 客户端时无法在编译期获得类型校验字段拼写错误、类型不匹配等问题只能在程序运行时暴露这对强类型、强校验诉求的应用是一个需要权衡的短板但换来的是松耦合当客户端 API 变化时程序无需重新编译即可继续工作因为对象形态不依赖预生成类型。在 API 演进频繁、或需要管理自定义资源CRD时这种灵活性尤为关键。从源码层面看dynamic 包的核心抽象定义在 dynamic/interface.gotype Interface interface { Resource(resource schema.GroupVersionResource) NamespaceableResourceInterface }Interface只暴露一个Resource方法接收schema.GroupVersionResourceGVR即组-版本-资源三元组作为资源定位参数NamespaceableResourceInterface则支持.Namespace(ns)绑定命名空间并内嵌了ResourceInterface后者定义了Create、Update、UpdateStatus、Delete、DeleteCollection、Get、List、Watch、Patch、Apply等一整套增删改查方法。这个设计意味着只要知道资源的 GVR同一套代码就能操作任意 Kubernetes 资源包括任意自定义资源。示例程序整体结构示例主程序位于 examples/dynamic-create-update-delete-deployment/main.go完整流程如下通过clientcmd.BuildConfigFromFlags(, *kubeconfig)从 kubeconfig 构建rest.Config调用dynamic.NewForConfig(config)创建动态客户端定义 Deployment 的 GVRschema.GroupVersionResource{Group: apps, Version: v1, Resource: deployments}构造unstructured.Unstructured形式的 Deployment 对象依次执行 Create → Update → List → Delete每步之间由交互式prompt()暂停等待回车。程序开头注释特别注明the example only works with the code within the same release/branch即该示例仅与同 release/branch 的 client-go 代码配合使用引入本项目外的旧版本 client-go 可能产生兼容问题。运行前准备与编译执行在运行示例前请先确认拥有一个可用的 Kubernetes 集群且本机kubectl已正确配置kubectl get nodes然后编译示例示例所在目录已包含go.mod可直接基于仓库根目录构建go build -o ./app ./examples/dynamic-create-update-delete-deployment或先进入示例目录再构建cd examples/dynamic-create-update-delete-deployment go build -o ./app使用本机 kubeconfig 运行./app # 或通过 flag 显式指定 kubeconfig 文件 ./app -kubeconfig$HOME/.kube/config-kubeconfigflag 的默认值由 homedir 工具 推导程序会优先使用$HOME/.kube/config若HOME环境变量为空则默认为空字符串此时必须显式传入 kubeconfig 路径参见 main.go。运行后程序会在default命名空间执行以下四个操作Create Deployment创建一个 2 副本的 Deployment可用kubectl get pods验证Update Deployment将副本数改为 1并把容器镜像从nginx:1.12改为nginx:1.13可用kubectl describe deployment demo验证副本数与镜像List Deployments检索default命名空间下的所有 Deployment打印名称与副本数Delete Deployment删除 Deployment 对象及其依赖的 ReplicaSet 资源可用kubectl get deployments验证。每一步之间都有交互式提示需要按Return回车键才进入下一步——这是刻意设计的观察窗口方便你在每步操作后用kubectl实时检查集群状态。实战解析四步核心操作第一步创建 Deploymentunstructured 对象构造Dynamic 客户端下对象不再用强类型 struct 构造而是直接以map[string]interface{}逐层手写 YAML/JSON 对应的层级结构例如deployment : unstructured.Unstructured{ Object: map[string]interface{}{ apiVersion: apps/v1, kind: Deployment, metadata: map[string]interface{}{ name: demo-deployment, }, spec: map[string]interface{}{ replicas: 2, selector: map[string]interface{}{ matchLabels: map[string]interface{}{ app: demo, }, }, template: map[string]interface{}{ metadata: map[string]interface{}{ labels: map[string]interface{}{ app: demo, }, }, spec: map[string]interface{}{ containers: []map[string]interface{}{ { name: web, image: nginx:1.12, ports: []map[string]interface{}{ {name: http, protocol: TCP, containerPort: 80}, }, }, }, }, }, }, }, }注意两个细节apiVersion与kind必须显式给出容器端口等类型字段遵循 JSON 约定数字用int数组用切片。随后通过client.Resource(deploymentRes).Namespace(apiv1.NamespaceDefault).Create(context.TODO(), deployment, metav1.CreateOptions{})发起创建成功后从返回的result.GetName()读取对象名。apiv1.NamespaceDefault即常量default来自 k8s.io/api/core/v1 对应的 core v1 API 定义。从 dynamic/simple.go 的实现可以看到Create底层执行的是client.Post().AbsPath(...).Body(obj).SpecificallyVersionedParams(opts, ...)即向资源集合 URL 发送 HTTP POST请求体就是序列化后的 unstructured JSON——这等价于kubectl apply -f所走的 API 路径。第二步更新 Deployment冲突重试循环更新是示例中技术含量最高的一步源码注释给出了两种策略直接覆盖式修改本地deployment变量后调用Update(deployment)等价于kubectl replace。缺点是会覆盖/丢失从 Create 到 Update 之间其他客户端对对象所做的变更读取-修改-提交 冲突重试推荐先Get最新版本在最新对象上做修改再Update若因资源版本过期收到 Conflict 错误则退避重试。这正是官方推荐的并发安全做法。推荐的实现完整代码如下retryErr : retry.RetryOnConflict(retry.DefaultRetry, func() error { // 每次重试前都必须重新 Get 最新版本 result, getErr : client.Resource(deploymentRes).Namespace(apiv1.NamespaceDefault). Get(context.TODO(), demo-deployment, metav1.GetOptions{}) if getErr ! nil { panic(fmt.Errorf(failed to get latest version of Deployment: %v, getErr)) } // 将 replicas 更新为 1 if err : unstructured.SetNestedField(result.Object, int64(1), spec, replicas); err ! nil { panic(fmt.Errorf(failed to set replica value: %v, err)) } // 提取 spec.template.spec.containers 切片 containers, found, err : unstructured.NestedSlice(result.Object, spec, template, spec, containers) if err ! nil || !found || containers nil { panic(fmt.Errorf(deployment containers not found or error in spec: %v, err)) } // 更新容器 [0] 的镜像 if err : unstructured.SetNestedField(containers[0].(map[string]interface{}), nginx:1.13, image); err ! nil { panic(err) } if err : unstructured.SetNestedField(result.Object, containers, spec, template, spec, containers); err ! nil { panic(err) } _, updateErr : client.Resource(deploymentRes).Namespace(apiv1.NamespaceDefault). Update(context.TODO(), result, metav1.UpdateOptions{}) return updateErr // 必须原样返回 errRetryOnConflict 才能识别 Conflict }) if retryErr ! nil { panic(fmt.Errorf(update failed: %v, retryErr)) }这里的unstructured.SetNestedField/unstructured.NestedSlice/unstructured.NestedInt64是操作嵌套 map 的辅助函数分别用于写入字段、提取切片、读取整数全部位于k8s.io/apimachinery/pkg/apis/meta/v1/unstructured包中。重试机制原理retry.RetryOnConflict定义于 util/retry/util.go其内部调用OnError以errors.IsConflict作为可重试判定条件——只有 API Server 返回 HTTP 409 Conflict通常因resourceVersion过期引起时才触发重试其他错误如权限、网络问题直接返回。默认退避参数为var DefaultRetry wait.Backoff{ Steps: 5, // 最多重试 5 次 Duration: 10 * time.Millisecond, // 初始等待 10ms Factor: 1.0, // 退避因子 1.0固定间隔 Jitter: 0.1, // 加入 10% 抖动避免重试请求惊群 }源码注释明确建议fn内每次都必须重新 Get 最新版本因为一次 Update 冲突说明对象已被其他客户端修改过必须基于最新resourceVersion才能再次提交成功。这与 Kubernetes 官方 API 并发控制与一致性约定一脉相承。第三步List 查询 Deploymentlist, err : client.Resource(deploymentRes).Namespace(apiv1.NamespaceDefault).List(context.TODO(), metav1.ListOptions{}) if err ! nil { panic(err) } for _, d : range list.Items { replicas, found, err : unstructured.NestedInt64(d.Object, spec, replicas) if err ! nil || !found { fmt.Printf(Replicas not found for deployment %s: error%s, d.GetName(), err) continue } fmt.Printf( * %s (%d replicas)\n, d.GetName(), replicas) }List返回*unstructured.UnstructuredList其Items字段是[]unstructured.Unstructured每个元素仍是嵌套 map因此读取副本数同样要借助unstructured.NestedInt64沿着spec.replicas路径提取。在 dynamic/client_test.go 的TestList等测试用例中可以看到用httptest模拟 API Server 来验证 List 请求路径与响应解析的正确性测试驱动开发保证了这套 REST 语义的可靠性。第四步删除 Deployment级联删除策略deletePolicy : metav1.DeletePropagationForeground deleteOptions : metav1.DeleteOptions{ PropagationPolicy: deletePolicy, } if err : client.Resource(deploymentRes).Namespace(apiv1.NamespaceDefault). Delete(context.TODO(), demo-deployment, deleteOptions); err ! nil { panic(err) }这里显式指定了DeletePropagationForeground前台级联删除API Server 会先删除依赖对象如 ReplicaSet、Pod依赖清理完成后再删除主对象最终实现删除 Deployment 及其所有依赖资源的完整清理语义。Delete底层在 dynamic/simple.go 中表现为client.Delete().AbsPath(...).Body(opts)——注意删除参数是通过请求体Body携带的 DeleteOptions而不是 URL 参数。预期输出正常运行时程序输出如下Creating deployment... Created deployment demo-deployment. - Press Return key to continue. Updating deployment... Updated deployment... - Press Return key to continue. Listing deployments in namespace default: * demo-deployment (1 replicas) - Press Return key to continue. Deleting deployment... Deleted deployment.注意 List 输出的副本数为 1验证了 Update 步骤的修改确实生效。资源 URL 构造原理理解 dynamic 客户端如何把 GVR 翻译成 REST URL有助于排查各类资源访问问题。核心逻辑在 dynamic/simple.go 的makeURLSegments中url : []string{} if len(c.resource.Group) 0 { url append(url, api) // 核心组如 core/v1走 /api 前缀 } else { url append(url, apis, c.resource.Group) // 扩展组走 /apis/{group} } url append(url, c.resource.Version) if len(c.namespace) 0 { url append(url, namespaces, c.namespace) } url append(url, c.resource.Resource) if len(name) 0 { url append(url, name) }以本文的 DeploymentGroupapps为例最终 URL 为/apis/apps/v1/namespaces/default/deployments/demo-deployment。同时 dynamic/simple.go 的validateNamespaceWithOptionalName会对命名空间与对象名做路径段合法性校验非法字符会在请求发出前被拦截。另外ConfigFordynamic/simple.go会为 dynamic 客户端强制设置ContentType application/json并绑定专用的 negotiated serializer——这是 unstructured 能够透明解析任意 JSON 载荷的底层保障。清理与故障排查清理程序正常跑完会自行清理所创建的资源。若中途终止例如在交互提示处 Ctrl-C可手动执行清理kubectl delete deploy demo-deployment故障排查若运行时出现以下错误panic: the server could not find the requested resource请先通过kubectl version确认集群 Kubernetes 版本为v1.13 或更高本示例使用apps/v1的 Deployment API该 API 稳定版本需要较新的集群版本支持。从代码路径看这一 panic 通常源于dynamic.NewForConfig之外的首个 API 调用——当请求的 GVR 在当前集群的 discovery 结果中不存在时API Server 会返回 404动态客户端因无法获知正确的 API 路径而直接 panic。总结与适用场景本文以官方示例为骨架完整走通了 dynamic 客户端的四大核心操作。你可以把 main.go 作为模板仅替换deploymentRes的 GVR 与 unstructured 对象结构即可管理任意原生或自定义资源。适用场景包括需要管理CRD自定义资源且不愿手写类型化 clientset 的程序需要跨 API 版本通用的资源管理工具如 Operator 框架、通用巡检/备份工具对接尚未纳入 client-go 类型体系的新 API。取舍提醒若你的场景对字段安全校验、IDE 补全和编译期保障有强需求优先考虑 Typed 客户端参考 create-update-delete-deployment 对应的类型化实现而追求 API 演进灵活性与资源类型通用性的场景dynamic 客户端则是更合适的选择——二者各有定位互为补充。赞分享云原生后端【免费下载链接】client-goGo client for Kubernetes.项目地址https://gitcode.com/gh_mirrors/cl/client-go点击查看免费下载相关推荐使用 client-go Dynamic 客户端对 Deployment 执行增删改查dynamic-create-update-delete-deployment 实战详解使用 client go Dynamic 客户端对 Deployment 执行增删改查dynamic create update delete deploym云原生容器编排集群管理微服务仓颉编程基础及应用宏指南如何用宏机制写出自己的代码扩展仓颉编程基础及应用宏指南如何用宏机制写出自己的代码扩展 《仓颉编程基础及应用》开源仓库汇集了随书源代码、课件PPT与在线扩展阅读资料是学习仓颉语言Cang云原生后端Apereo CAS OpenID Connect 动态客户端注册Dynamic Client Registration端到端实战指南Apereo CAS OpenID Connect 动态客户端注册Dynamic Client Registration端到端实战指南 导读 本文围绕 Ap后端认证鉴权单点登录创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考