K8S中为什么需要Unstructured对象

本文探讨Kubernetes中Unstructured对象的应用场景与实现方式,对比Structured对象,阐述其在处理多类型资源时的优势。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

熟悉client-go的同学都知道,不止有DeploymentPod这些结构化对象,也提供了unstructured.Unstructured对象,那么为什么需要非结构对象?

Structured vs Unstructured

结构化对象是指可以用Go Struct表示的对象,比如Deploymentk8s.io/api/apps/v1定义

type Deployment struct {
	metav1.TypeMeta `json:",inline"`
	// Standard object's metadata.
	// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata
	// +optional
	metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
    ...
}

我们可以直接通过appsv1.Deployment来安全地定义Deployment的各个字段,通常创建过程如下:

clientset, err := kubernetes.NewForConfig(config)

deployment := &appsv1.Deployment{}
deployment.Name = "example"
deployment.Spec = appsv1.DeploymentSpec{
	...
}

clientset.AppsV1().Deployments(apiv1.NamespaceDefault).Create(deployment)

而对于Unstructured定义在k8s.io/apimachinery/pkg/apis/meta/v1/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{}
}

通过定义map[string]interface{}可以来表示任意的JSON/YAML对象,而不需要引用Go Struct。可以通过Dynamic client来创建非结构化对象,以下是使用Unstructured创建Deployment的样例。

client, _ := dynamic.NewForConfig(config)
deploymentRes := schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"}

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,
			...
		}
	}
}

client.Resource(deploymentRes).Namespace(namespace).Create(context.TODO(), deployment, metav1.CreateOptions{})

Why

那么什么情况下需要使用到Unstructured对象呢,结构化对象有着安全地类型校验,通过clientset可以方便地增删改查。而非结构化对象只能手动设置GVR、通过map[string]interface{}设置各个字段。

假想你作为一个Paas平台的开发者,需要为每个用户传入的YAML/JSON资源添加label,比如添加user信息creator=xxx。如果用户只能创建Deployment,那么我们可以将资源解析成appsv1.Deployment{}对象,再添加label。但是通常会传入多种资源,不仅有内置的DeploymentService等,也可能会包含自定义资源。由于不确定资源类型,我们只能通过Unstructured对象来解析。

const manifest = `
apiVersion: apps/v1
kind: Deployment
metadata:
  name: example
spec:
  ...
`
// convert yaml to unstructured
obj := &unstructured.Unstructured{}
dec := yaml.NewDecodingSerializer(unstructured.UnstructuredJSONScheme)
dec.Decode([]byte(manifest), nil, obj)

// add label
labels := obj.GetLabels()
labels["creator"]="userxxx"

// set label
obj.SetLabels(labels)

dynamicClient.Resource().Namespace(namespace).Create(context.TODO(), obj, metav1.CreateOptions{})

当实现对多种资源的通用处理(上面的示例),或者运行时才能确定的对象(例如根据配置监听不同对象),又或者不愿引入额外的依赖(处理大量的CRD),可以使用Unstructured对象来处理以上情况。

How

不管是结构化对象还是非结构化,最终会调用k8s的Rest API,例如Create Deployment

POST /apis/apps/v1/namespaces/{namespace}/deployments/{name}

K8s中GVR(GroupVersionResource)可以唯一表征资源对象,用来组成Rest API, 如上Group为apps、Version为v1、Resource是deploymentsGVK(GroupVersionKind)可以来标识类型(如Deployment)。Resource与Kind的对应关系可以通过kubectl api-resources查看。

~ kubectl api-resources --api-group apps
NAME                  SHORTNAMES   APIVERSION   NAMESPACED   KIND
controllerrevisions                apps/v1      true         ControllerRevision
daemonsets            ds           apps/v1      true         DaemonSet
deployments           deploy       apps/v1      true         Deployment
replicasets           rs           apps/v1      true         ReplicaSet
statefulsets          sts          apps/v1      true         StatefulSet

对于结构化对象,使用clientset可以获取到GVR,最后调用restClient组成到Rest API

clientset.AppsV1().Deployments(namespace).Create(deployment)

// Create takes the representation of a deployment and creates it.  Returns the server's representation of the deployment, and an error, if there is any.
func (c *deployments) Create(ctx context.Context, deployment *v1.Deployment, opts metav1.CreateOptions) (result *v1.Deployment, err error) {
	result = &v1.Deployment{}
	err = c.client.Post().
		Namespace(c.ns).
		Resource("deployments"). // Resource设置
		VersionedParams(&opts, scheme.ParameterCodec).
		Body(deployment).
		Do(ctx).
		Into(result)
	return
}

对于非结构化对象,需要用户手动填充GVR,如果只知道GVK可以通过restMapping获取

deploymentRes := schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"}

dynamicClient.Resource().Namespace(namespace).Create()

// Create具体实现
func (c *dynamicResourceClient) Create(ctx context.Context, obj *unstructured.Unstructured, opts metav1.CreateOptions, subresources ...string) (*unstructured.Unstructured, error) {
	outBytes, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj)
	name := ""
	if len(subresources) > 0 {
		accessor, err := meta.Accessor(obj)
		name = accessor.GetName()
	}

    // 调用restClient
	result := c.client.client.
		Post().
		AbsPath(append(c.makeURLSegments(name), subresources...)...).
		Body(outBytes).
		SpecificallyVersionedParams(&opts, dynamicParameterCodec, versionV1).
		Do(ctx)
	// ...
}

总结

本文描述Unstructured对象在K8s中的使用场景、使用方式,与Structured对象的对比,以及相关代码解析。

引用

  • https://kubernetes.io/zh/docs/reference/using-api/api-concepts/
Kubernetesk8s)中,Custom Resource (CR) 是用户自定义的资源对象。通过k8s的client-go库,可以对CR进行PATCH修改。PATCH操作允许你部分更新资源对象,而不需要替换整个对象。以下是通过client-go对CR进行PATCH修改的步骤: 1. **引入必要的包**: 首先,确保你已经引入了client-go的相关包。 ```go import ( "context" "fmt" "path/filepath" appsv1 "k8s.io/api/apps/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/dynamic" "k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/util/homedir" ) ``` 2. **创建客户端**: 创建一个动态客户端来与CR进行交互。 ```go func main() { var kubeconfig string if home := homedir.HomeDir(); home != "" { kubeconfig = filepath.Join(home, ".kube", "config") } else { kubeconfig = "" } config, err := clientcmd.BuildConfigFromFlags("", kubeconfig) if err != nil { panic(err.Error()) } client, err := dynamic.NewForConfig(config) if err != nil { panic(err.Error()) } } ``` 3. **定义CR的GVR**: 定义CR的Group、Version和Resource。 ```go gvr := schema.GroupVersionResource{ Group: "apps", Version: "v1", Resource: "deployments", } ``` 4. **执行PATCH操作**: 使用客户端执行PATCH操作。 ```go deployment, err := client.Resource(gvr).Namespace("default").Patch( context.TODO(), "my-deployment", types.MergePatchType, []byte(`{"spec":{"replicas":3}}`), metav1.PatchOptions{}, ) if err != nil { panic(err.Error()) } fmt.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值