在 Kubernetes 和相关的客户端库中,结构化对象和非结构化对象指的是如何处理和表示 Kubernetes 资源对象,它们有着不同的用法和用例。
基本概念
结构化对象(Structured Objects)
结构化对象指的是那些已经明确定义了其结构(属性和类型)的 Kubernetes API 对象。这些对象通常用具体的 Go 结构体来表示,每个结构体都对应于 Kubernetes API 中定义的一个资源类型(如 Pod、Deployment、Service 等)。这些结构化对象允许静态类型检查,以及 IDE 的代码完成功能,因为所有属性和方法在编译时都已经定义好了。
例如,下面是一个结构化的 Deployment 对象的示例:
deployment := &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: "example-deployment",
},
Spec: appsv1.DeploymentSpec{
// ... deployment spec configuration
},
// ... other fields
}
非结构化对象(Unstructured Objects)
非结构化对象则是一种灵活的表示方法,它使用 map[string]interface{} 数据结构来通用地表示 Kubernetes API 中的任何资源对象,而不需要一个预先定义的 Go 结构体。
下面是一个非结构化的对象的示例:
unstructuredObj := &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": map[string]interface{}{
"name": "example-deployment",
},
// ... other fields in a format of map
},
}
// 资源对象的 GVK 可以这样设置
unstructuredObj.SetGroupVersionKind(schema.GroupVersionKind{
Group: "apps",
Version: "v1",
Kind: "Deployment",
})
比较与联系
-
unstructured.Unstructured 直接解码的内容是 YAML/JSON,使用 unstructured.Unstructured 可以方便地操作任意类型的资源,在处理动态内容或者编写不具体依赖于资源具体类型的通用工具时非常有用,比如给不同资源打上同样的标签。如果使用结构体对象,则需要处理不同类型,而使用非结构化对象,那么只需要一个循环。
var manifest = ` apiVersion: v1 kind: pod metadata: name: web spec: ... ` // convert yaml to unstructured obj := &unstructured.Unstructured{} dec := yaml.NewDecodingSerializer(unstructured.UnstructuredJSONScheme) dec.Decode([]byte(manifest), nil, obj) labels := obj.GetLabels() labels["owner"]="xxx" obj.SetLabels(labels) dynamicClient.Resource().Namespace(namespace).Create(context.TODO(), obj, metav1.CreateOptions{})
-
对于结构化对象来说,它们有着严格的类型校验。通过各种 clientset 可以方便地实现 API 资源对象增删改查,主要用于静态客户端。而对于使用 unstructured.Unstructured 类型的对象来说,不需要了解各个字段是怎么定义的,只需要从 YAML/JSON 转化为 unstructured.Unstructured 对象或者干脆直接定义
map[string]interface{}
来表示 Kubernetes API 对象即可,主要用于动态客户端。// 使用静态客户端与结构化对象来创建 Pod 对象 import "k8s.io/client-go/kubernetes" clientset, err := kubernetes.NewForConfig(config) pod := &Corev1.Pod{} pod.Name = "web" pod.Spec = pod.PodSpec{ ... } clientset.CoreV1().Pods(apiv1.NamespaceDefault).Create(pod) // 使用动态客户端与非结构化创建 Pod 对象 import "k8s.io/client-go/dynamic" client, _ := dynamic.NewForConfig(config) podGVR := schema.GroupVersionResource{Group: "core", Version: "v1", Resource: "pods"} pod := &unstructured.Unstructured{ Object: map[string]interface{}{ "apiVersion": "v1", "kind": "Pod", "metadata": map[string]interface{}{ "name": "web", }, "spec": map[string]interface{}{ "serviceAccount": "default", ... } } } client.Resource(podGVR).Namespace(namespace).Create(context.TODO(), pod, metav1.CreateOptions{})
-
unstructured.Unstructured 对象也可以和具体资源对象相互转换,这个主要是借助了 Go 的反射机制,依赖于 runtime.UnstructuredConverter 接口,该接口定义了 unstructured.Unstructured 对象与具体资源对象的相互转换方法。同时,包中也内置了 runtime.DefaultUnstructuredConverter,其实现了 runtime.unstructuredConverter 接口。
type UnstructuredConverter interface { ToUnstructured(obj interface{}) (map[string]interface{}, error) FromUnstructured(u map[string]interface{}, obj interface{}) error } DefaultUnstructuredConverter = &unstructuredConverter{ mismatchDetection: parseBool(os.Getenv("KUBE_PATCH_CONVERSION_DETECTOR")), comparison: conversion.EqualitiesOrDie( func(a, b time.Time) bool { return a.UTC() == b.UTC() }, ), }
-
但是,无论使用哪种方式,最终都会调用 Kubernetes 的 RESTful API,比如上面创建 Pod 的例子最后都是执行
POST /api/v1/namespaces/{namespace}/pods/{name}
请求。
runtime.Object
Pod、Service、Deployment 等结构化对象,都实现了 runtime.Object 接口。
- 每个 API 资源对象都继承了 metav1.TypeMeta,TypeMeta 实现了 GetObjectKind() 方法,因此默认实现了 GetObjectKind() 方法。
- 每个 API 资源对象,都将由工具自动生成 DeepCopyObject() 方法,定义在
zz_generated.deepcopy.go
文件中。
因此,runtime.Object 接口可以表示任何类型的结构化资源对象。
type Object interface {
GetObjectKind() schema.ObjectKind
DeepCopyObject() Object
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *ConfigMap) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
unstructured.Unstructured
非结构化资源对象使用 unstructured.Unstructured 表示,如下所示。
type Unstructured struct {
// Object is a JSON compatible map with string, float, int, bool, []interface{}, or
// map[string]interface{} children.
Object map[string]interface{}
}
unstructured.Unstructured 实现了存取类型元信息与对象元信息的方法,也实现了 runtime.Unstructured 接口中的所有方法。
// 存取类型元信息和对象元信息的方法
func (u *Unstructured) GetAPIVersion() string {
return getNestedString(u.Object, "apiVersion")
}
func (u *Unstructured) SetAPIVersion(version string) {
u.setNestedField(version, "apiVersion")
}
func (u *Unstructured) GetKind() string {
return getNestedString(u.Object, "kind")
}
func (u *Unstructured) SetKind(kind string) {
u.setNestedField(kind, "kind")
}
...
// runtime.Unstructured 接口
type Unstructured interface {
Object
NewEmptyInstance() Unstructured
UnstructuredContent() map[string]interface{}
SetUnstructuredContent(map[string]interface{})
IsList() bool
EachListItem(func(Object) error) error
}