



























当我们需要操作 Kubernetes 内置资源(如 Pod、Deployment)时,可以使用 typed client(Clientset),因为这些资源的 Go 类型已经定义好了。
但当我们需要操作自定义资源(CRD)时,如果每次都等代码生成,会非常麻烦。DynamicClient 就是来解决这个问题的:它可以动态地操作任意 Kubernetes 资源,包括 CRD,无需提前知道资源的具体类型。这一篇文章,我们来深入理解 DynamicClient 的设计和使用。
Kubernetes DynamicClient CRD Unstructured v1.36.1
🔓 学习重点提示 — 建议先通读全文,再重点回顾标注内容
★ 重点掌握(必须)
• DynamicClient vs Clientset:什么时候用哪个,为什么
• Unstructured 对象:如何用 map[string]interface{} 表示任意资源
• GroupVersionResource:如何用 GVR 定位任意资源
☆ 次重点(了解即可)
• DynamicInformer 与 DynamicClient 的配合
假设我们开发了一个通用工具,需要操作用户的 CRD,但用户可能有任意类型的 CRD,不可能每次都等 CRD 定义好之后重新生成代码。
另一个场景是 Operator Framework 的早期版本,它们需要在运行时动态发现和操作 CRD。
DynamicClient 的核心思想:用通用的数据结构(map/list)来表示资源,而不是用编译时已知的 Go struct。这样就可以操作任意类型的资源了。
DynamicClient 的结构非常简单:
// staging/src/k8s.io/client-go/dynamic/simple.go(行 34-36)
// DynamicClient 是一个动态客户端,可以操作任意 Kubernetes 资源
type DynamicClient struct {
client rest.Interface // 通用的 REST 客户端
}
var _ Interface = &DynamicClient{} // 实现了 Interface 接口
DynamicClient 只有一个字段:rest.Interface。所有对 APIServer 的请求都通过这个通用接口完成。创建 DynamicClient 的方式也很简单:
// 创建 DynamicClient
import "k8s.io/client-go/dynamic"
func main() {
// 方式一:从 config 创建
config, _ := clientcmd.BuildConfigFromFlags("", "")
dynamicClient, _ := dynamic.NewForConfig(config)
// 方式二:从已有的 rest.Config 创建
dynamicClient, _ := dynamic.NewForConfigAndClient(config, httpClient)
}
typed client 通过 Clientset 直接访问特定的资源,比如 `clientset.AppsV1().Deployments()`。DynamicClient 不一样,我们需要用 GroupVersionResource(GVR)来定位资源。
import "k8s.io/apimachinery/pkg/runtime/schema"
// GVR 示例
podGVR := schema.GroupVersionResource{
Group: "", // core 组,Group 为空
Version: "v1", // API 版本
Resource: "pods", // 资源名(复数形式)
}
// 自定义资源 GVR 示例(假设有一个 Foo CRD)
fooGVR := schema.GroupVersionResource{
Group: "example.com", // CRD 的 API Group
Version: "v1", // CRD 的版本
Resource: "foos", // CRD 的复数资源名
}
有了 GVR 之后,就可以通过 DynamicClient 获取资源了:
// 获取某个 namespace 下的资源
resourceClient := dynamicClient.Resource(fooGVR).Namespace("default")
// 获取所有 namespace 下的资源
resourceClient := dynamicClient.Resource(fooGVR) // 不调用 Namespace()
DynamicClient 的核心是 Unstructured 对象。它是一个用 `map[string]interface{}` 表示的动态对象,可以装任意 JSON 数据。
// Unstructured 对象示例
import "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
// 创建一个 Unstructured 对象
obj := &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "example.com/v1",
"kind": "Foo",
"metadata": map[string]interface{}{
"name": "my-foo",
"namespace": "default",
},
"spec": map[string]interface{}{
"replicas": 3,
"selector": map[string]interface{}{
"matchLabels": map[string]interface{}{
"app": "myapp",
},
},
},
},
}
Unstructured 提供了便捷的方法来读写嵌套字段:
// 设置嵌套字段
unstructured.SetNestedField(obj.Object, int64(3), "spec", "replicas")
unstructured.SetNestedMap(obj.Object, map[string]interface{}{
"matchLabels": map[string]interface{}{
"app": "myapp",
},
}, "spec", "selector")
// 读取嵌套字段
replicas, found, err := unstructured.NestedInt64(obj.Object, "spec", "replicas")
name, found, err := unstructured.NestedString(obj.Object, "metadata", "name")
下面是一个完整的 DynamicClient 使用示例,演示如何操作一个名为 Foo 的 CRD:
// 完整的 DynamicClient 使用示例
package main
import (
"context"
"fmt"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/tools/clientcmd"
)
func main() {
// 第一步:创建 DynamicClient
config, _ := clientcmd.BuildConfigFromFlags("", "")
dynamicClient, _ := dynamic.NewForConfig(config)
// 第二步:定义 CRD 的 GVR
fooGVR := schema.GroupVersionResource{
Group: "example.com",
Version: "v1",
Resource: "foos",
}
// 第三步:创建资源
foo := &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "example.com/v1",
"kind": "Foo",
"metadata": map[string]interface{}{
"name": "my-foo",
"namespace": "default",
},
"spec": map[string]interface{}{
"replicas": 3,
},
},
}
// 创建资源
created, err := dynamicClient.Resource(fooGVR).Namespace("default").Create(
context.Background(),
foo,
metav1.CreateOptions{},
)
if err != nil {
panic(err)
}
fmt.Printf("Created: %s/%s\n", created.GetNamespace(), created.GetName())
// 第四步:读取资源
fetched, err := dynamicClient.Resource(fooGVR).Namespace("default").Get(
context.Background(),
"my-foo",
metav1.GetOptions{},
)
if err != nil {
panic(err)
}
fmt.Printf("Fetched: %+v\n", fetched.Object)
// 第五步:修改资源(使用 Unstructured 的便捷方法)
unstructured.SetNestedField(fetched.Object, int64(5), "spec", "replicas")
updated, err := dynamicClient.Resource(fooGVR).Namespace("default").Update(
context.Background(),
fetched,
metav1.UpdateOptions{},
)
if err != nil {
panic(err)
}
fmt.Printf("Updated replicas to: %d\n",
updated.Object["spec"].(map[string]interface{})["replicas"])
// 第六步:列表资源
list, err := dynamicClient.Resource(fooGVR).Namespace("default").List(
context.Background(),
metav1.ListOptions{},
)
if err != nil {
panic(err)
}
fmt.Printf("Found %d Foo resources\n", len(list.Items))
// 第七步:删除资源
err = dynamicClient.Resource(fooGVR).Namespace("default").Delete(
context.Background(),
"my-foo",
metav1.DeleteOptions{},
)
if err != nil {
panic(err)
}
fmt.Println("Deleted my-foo")
}
DynamicClient 虽然灵活,但也有一些限制:
| 限制 | 说明 | 解决方案 |
|---|---|---|
| 无类型安全 | map[string]interface{} 没有编译时检查,容易写错字段名 | 使用 Unstructured 便捷方法,设置/读取嵌套字段 |
| 无代码补全 | IDE 无法提供字段补全 | 用 typed client 处理已知的资源类型 |
| 性能稍差 | 每次都需要序列化/反序列化 map[string]interface{} | 对于频繁操作的资源,使用 typed client |
和 typed Informer 类似,DynamicClient 也有对应的 DynamicInformer,用于监听 CRD 的变化:
import "k8s.io/client-go/dynamic/dynamicinformer"
import "k8s.io/client-go/tools/cache"
func main() {
// 创建 DynamicInformerFactory
factory := dynamicinformer.NewFilteredDynamicSharedInformerFactory(
dynamicClient,
30*time.Second, // resync 周期
"default", // namespace
nil, // tweakListOptions
)
// 为 Foo CRD 创建 Informer
fooInformer := factory.ForResource(fooGVR)
// 注册事件处理函数
fooInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
u := obj.(*unstructured.Unstructured)
fmt.Printf("Foo Added: %s\n", u.GetName())
},
UpdateFunc: func(old, new interface{}) {
u := new.(*unstructured.Unstructured)
fmt.Printf("Foo Updated: %s\n", u.GetName())
},
DeleteFunc: func(obj interface{}) {
u := obj.(*unstructured.Unstructured)
fmt.Printf("Foo Deleted: %s\n", u.GetName())
},
})
// 启动 Informer
factory.Start(ctx.Done())
factory.WaitForCacheSync(ctx.Done())
}
这一节我们深入理解了 DynamicClient:
下一节我们将学习 工具与调试方法,了解如何排查 Controller 的问题。敬请期待!
Kubernetes 编程 / Operator 专题【左扬精讲】—— DynamicClient 操作 CRD · 来源:Kubernetes v1.36.1 client-go 源码分析
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。