K8S 剖析API对象类型

K8S API对象类型

一、metav1.TypeMeta, 对象的类型元数据信息。

1.1、类型成员

定义了资源类型和api版本。

type TypeMeta struct {
    
	Kind string `json:"kind,omitempty" protobuf:"bytes,1,opt,name=kind"`

	APIVersion string `json:"apiVersion,omitempty" protobuf:"bytes,2,opt,name=apiVersion"`
}
1.2、相关接口

TypeMeta的相关接口主要实现了查看和设置资源类型和api版本的功能。

func (obj *TypeMeta) GetObjectKind() schema.ObjectKind { return obj }
//实现了schema.ObjectKind接口
type ObjectKind interface {
	// SetGroupVersionKind sets or clears the intended serialized kind of an object. Passing kind nil
	// should clear the current setting.
	SetGroupVersionKind(kind GroupVersionKind)
	// GroupVersionKind returns the stored group, version, and kind of an object, or nil if the object does
	// not expose or provide these fields.
	GroupVersionKind() GroupVersionKind
}

// SetGroupVersionKind satisfies the ObjectKind interface for all objects that embed TypeMeta
func (obj *TypeMeta) SetGroupVersionKind(gvk schema.GroupVersionKind) {
	obj.APIVersion, obj.Kind = gvk.ToAPIVersionAndKind()
}

// GroupVersionKind satisfies the ObjectKind interface for all objects that embed TypeMeta
func (obj *TypeMeta) GroupVersionKind() schema.GroupVersionKind {
	return schema.FromAPIVersionAndKind(obj.APIVersion, obj.Kind)
}

二、metav1.ObjectMeta

定义对象的公共说明属性,包括对象名、命名空间、注释、标签等。

2.1 类型成员
// ObjectMeta is metadata that all persisted resources must have, which includes all objects
// users must create.
type ObjectMeta struct {

	Name string `json:"name,omitempty" protobuf:"bytes,1,opt,name=name"`

	GenerateName string `json:"generateName,omitempty" protobuf:"bytes,2,opt,name=generateName"`

	Namespace string `json:"namespace,omitempty" protobuf:"bytes,3,opt,name=namespace"`

	SelfLink string `json:"selfLink,omitempty" protobuf:"bytes,4,opt,name=selfLink"`

	UID types.UID `json:"uid,omitempty" 

	ResourceVersion string `json:"resourceVersion,omitempty" protobuf:"bytes,6,opt,name=resourceVersion"`

	Generation int64 `json:"generation,omitempty" protobuf:"varint,7,opt,name=generation"`
	
	CreationTimestamp Time `json:"creationTimestamp,omitempty" `

	DeletionTimestamp *Time `json:"deletionTimestamp,omitempty" 
	
	DeletionGracePeriodSeconds *int64 `json:"deletionGracePeriodSeconds,omitempty" 

	Labels map[string]string `json:"labels,omitempty" protobuf:"bytes,11,rep,name=labels"`

	Annotations map[string]string `json:"annotations,omitempty" protobuf:"bytes,12,rep,name=annotations"`

	OwnerReferences []OwnerReference `json:"ownerReferences,omitempty" 
	
	Finalizers []string `json:"finalizers,omitempty" 
	
	ClusterName string `json:"clusterName,omitempty" protobuf:"bytes,15,opt,name=clusterName"`
	
	ManagedFields []ManagedFieldsEntry `json:"managedFields,omitempty" 
}
2.2、相关接口

metav1.ObjectMeta的相关接口主要是对资源公共属性的查看和设置。ObjectMeta实现了metav1.object和ObjectMetaAccessor两个interface,而kubernetes所有单体对象都继承了ObjectMeta,那么所有的API对象就都实现了metav1.object和ObjectMetaAccessor。metav1.Object是对API对象公共属性的抽象(interface);

// TODO: move this, Object, List, and Type to a different package
type ObjectMetaAccessor interface {
	GetObjectMeta() Object
}

// Object lets you work with object metadata from any of the versioned or
// internal API objects. Attempting to set or retrieve a field on an object that does
// not support that field (Name, UID, Namespace on lists) will be a no-op and return
// a default value.
//注意此object为metav1.object
type Object interface {  
	GetNamespace() string
	SetNamespace(namespace string)
	GetName() string
	SetName(name string)
	GetGenerateName() string
	SetGenerateName(name string)
	GetUID() types.UID
	SetUID(uid types.UID)
	GetResourceVersion() string
	SetResourceVersion(version string)
	GetGeneration() int64
	SetGeneration(generation int64)
	GetSelfLink() string
	SetSelfLink(selfLink string)
	GetCreationTimestamp() Time
	SetCreationTimestamp(timestamp Time)
	GetDeletionTimestamp() *Time
	SetDeletionTimestamp(timestamp *Time)
	GetDeletionGracePeriodSeconds() *int64
	SetDeletionGracePeriodSeconds(*int64)
	GetLabels() map[string]string
	SetLabels(labels map[string]string)
	GetAnnotations() map[string]string
	SetAnnotations(annotations map[string]string)
	GetFinalizers() []string
	SetFinalizers(finalizers []string)
	GetOwnerReferences() []OwnerReference
	SetOwnerReferences([]OwnerReference)
	GetClusterName() string
	SetClusterName(clusterName string)
	GetManagedFields() []ManagedFieldsEntry
	SetManagedFields(managedFields []ManagedFieldsEntry)
}


// 代码源自k8s.io/apimachinery/pkg/apis/meta/v1/meta.go,GetObjectMeta是MetaAccessor
// 的接口函数,这个函数说明了ObjectMeta实现了MetaAccessor。
func (obj *ObjectMeta) GetObjectMeta() Object { return obj }
 
// 下面所有的函数是接口Object的ObjectMeta实现,可以看出来基本就是setter/getter方法。
func (meta *ObjectMeta) GetNamespace() string                { return meta.Namespace }
func (meta *ObjectMeta) SetNamespace(namespace string)       { meta.Namespace = namespace }
func (meta *ObjectMeta) GetName() string                     { return meta.Name }
func (meta *ObjectMeta) SetName(name string)                 { meta.Name = name }
func (meta *ObjectMeta) GetGenerateName() string             { return meta.GenerateName }
func (meta *ObjectMeta) SetGenerateName(generateName string) { meta.GenerateName = generateName }
func (meta *ObjectMeta) GetUID() types.UID                   { return meta.UID }
func (meta *ObjectMeta) SetUID(uid types.UID)                { meta.UID = uid }
func (meta *ObjectMeta) GetResourceVersion() string          { return meta.ResourceVersion }
func (meta *ObjectMeta) SetResourceVersion(version string)   { meta.ResourceVersion = version }
func (meta *ObjectMeta) GetGeneration() int64                { return meta.Generation }
func (meta *ObjectMeta) SetGeneration(generation int64)      { meta.Generation = generation }
func (meta *ObjectMeta) GetSelfLink() string                 { return meta.SelfLink }
func (meta *ObjectMeta) SetSelfLink(selfLink string)         { meta.SelfLink = selfLink }
func (meta *ObjectMeta) GetCreationTimestamp() Time          { return meta.CreationTimestamp }
func (meta *ObjectMeta) SetCreationTimestamp(creationTimestamp Time) {
    meta.CreationTimestamp = creationTimestamp
}
func (meta *ObjectMeta) GetDeletionTimestamp() *Time { return meta.DeletionTimestamp }
func (meta *ObjectMeta) SetDeletionTimestamp(deletionTimestamp *Time) {
    meta.DeletionTimestamp = deletionTimestamp
}
func (meta *ObjectMeta) GetDeletionGracePeriodSeconds() *int64 { return meta.DeletionGracePeriodSeconds }
func (meta *ObjectMeta) SetDeletionGracePeriodSeconds(deletionGracePeriodSeconds *int64) {
    meta.DeletionGracePeriodSeconds = deletionGracePeriodSeconds
}
func (meta *ObjectMeta) GetLabels() map[string]string                 { return meta.Labels }
func (meta *ObjectMeta) SetLabels(labels map[string]string)           { meta.Labels = labels }
func (meta *ObjectMeta) GetAnnotations() map[string]string            { return meta.Annotations }
func (meta *ObjectMeta) SetAnnotations(annotations map[string]string) { meta.Annotations = annotations }
func (meta *ObjectMeta) GetFinalizers() []string                      { return meta.Finalizers }
func (meta *ObjectMeta) SetFinalizers(finalizers []string)            { meta.Finalizers = finalizers }
func (meta *ObjectMeta) GetOwnerReferences() []OwnerReference         { return meta.OwnerReferences }
func (meta *ObjectMeta) SetOwnerReferences(references []OwnerReference) {
    meta.OwnerReferences = references
}
func (meta *ObjectMeta) GetClusterName() string                 { return meta.ClusterName }
func (meta *ObjectMeta) SetClusterName(clusterName string)      { meta.ClusterName = clusterName }
func (meta *ObjectMeta) GetManagedFields() []ManagedFieldsEntry { return meta.ManagedFields }
func (meta *ObjectMeta) SetManagedFields(managedFields []ManagedFieldsEntry) {
    meta.ManagedFields = managedFields
}

三、资源实例

以pod资源为例

type Pod struct {

    metav1.TypeMeta `json:",inline"`
   
 
    metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
  
    Spec PodSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"`

    Status PodStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"`
}

K8S的资源一般都继承了metav1.TypeMeta和metav1.ObjectMeta两个类型成员,主要为资源类型和公共属性相关字段。Spec用于定义资源的私有属性,这也是资源之间的区别所在。所以可以把metav1.TypeMeta和metav1.ObjectMeta两个类型成员作为全部k8s资源对象的基类,这两个基类相关的接口,全部k8s资源对象都可以调用。


四、runtime包

import "k8s.io/apimachinery/pkg/runtime"

Package runtime defines conversions between generic types and structs to map query strings to struct objects.

Package runtime includes helper functions for working with API objects that follow the kubernetes API object convention.

4.1 Scheme 资源序列化和反序列化的方法以及资源类型和版本的对应关系

metav1.TypeMeta实现了schema.ObjectKind接口,所以K8S资源类型都可以调用schema.ObjectKind。

  • 从Scheme序列化的所有对象都对其类型信息进行编码。 序列化使用此接口,将Scheme中的类型信息设置到对象的序列化版本上。 对于无法序列化或具有独特要求的对象,此接口可能是无操作的。

    // All objects that are serialized from a Scheme encode their type information. This interface is used
    // by serialization to set type information from the Scheme onto the serialized version of an object.
    // For objects that cannot be serialized or have unique requirements, this interface may be a no-op.
    type ObjectKind interface {
    	// SetGroupVersionKind sets or clears the intended serialized kind of an object. Passing kind nil
    	// should clear the current setting.
    	SetGroupVersionKind(kind GroupVersionKind)
    	// GroupVersionKind returns the stored group, version, and kind of an object, or nil if the object does
    	// not expose or provide these fields.
    	GroupVersionKind() GroupVersionKind
    }
    
  • “k8s.io/apimachinery/pkg/runtime/schema” 包中k8s schema.GroupVersionKind结构体存储group、version、kind字段的值。

    // GroupVersionKind unambiguously identifies a kind.  It doesn't anonymously include GroupVersion
    // to avoid automatic coercion.  It doesn't use a GroupVersion to avoid custom marshalling
    type GroupVersionKind struct {
    	Group   string
    	Version string
    	Kind    string
    }
    
4.2 runtime.Object (所有API单体对象的根类(interface))

"k8s.io/apimachinery/pkg/runtime"包中runtime.Object接口,k8s的资源类型都应该实现了该接口。在Scheme上注册的所有API类型都必须支持object对象接口。 对象必须提供接口允许序列化程序设置对象表示的种类,版本和组。 在不希望序列化对象的情况下,对象可以选择返回无操作ObjectKindAccessor。

如果想通过一个基类的指针指向任意API单体对象,schema.ObjecKind和metav1.Object都不合适,因为他们所能访问的域是有限。如果有一个函数需要访问任何API对象的类型和公共属性,那么就要传入同一个对象的两个指针(schema.ObjecKind和metav1.Object),这就太让人难以接受了。有没有一个类型作为API单体对象的统一的基类呢?这就是本节要讨论的:runtime.Object。

// Object interface must be supported by all API types registered with Scheme. Since objects in a scheme are
// expected to be serialized to the wire, the interface an Object must provide to the Scheme allows
// serializers to set the kind, version, and group the object is represented as. An Object may choose
// to return a no-op ObjectKindAccessor in cases where it is not expected to be serialized.
type Object interface {
    //metav1.TypeMeta实现了schema.ObjectKind接口,接口包含了此方法。
	GetObjectKind() schema.ObjectKind
    //deepcopy是golang深度复制对象的方法,各个类型都实现了此方法。
	DeepCopyObject() Object
}

以pod资源为例:

//core>v1>zz_generated.deepcopy.go
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *Pod) DeepCopyObject() runtime.Object {
	if c := in.DeepCopy(); c != nil {
		return c
	}
	return nil
}

如前所述,K8S的资源实现了runtime.object接口,其实大多数资源结构体继承了ObjectMeta也实现了metav1.object接口来获取资源的公共属性。若资源要访问类型之外的公共属性,主要是ObjectMeta结构相关,使用以下的处理方式。Accessor()函数可以把obj安全的转换为metav1.Object,这样也就避免了每个API对象类型都需要实现类似GetObjectMeta()的接口了。meta包中使用了MetadataAccessor 接口聚合了对ObjectMeta和TypeMeta的属性访问和设置。


// MetadataAccessor exposes Interface in a way that can be used with multiple objects.
type MetadataAccessor interface {
	APIVersion(obj runtime.Object) (string, error)
	SetAPIVersion(obj runtime.Object, version string) error

	Kind(obj runtime.Object) (string, error)
	SetKind(obj runtime.Object, kind string) error

	Namespace(obj runtime.Object) (string, error)
	SetNamespace(obj runtime.Object, namespace string) error
	...
}
//k8s.io/apimachinery/pkg/api/meta/meta.go
//实现了对ObjectMeta的属性访问和设置
func Accessor(obj interface{}) (metav1.Object, error) { 
    // 使用了golang的switch type语法
    switch t := obj.(type) {
    // 因为API对象类型都继承了metav1.ObjectMeta,也就自然实现了metav1.Object。
    case metav1.Object:
        return t, nil
    // metav1.ObjectMeta也实现了metav1.ObjectMetaAccessor
    case metav1.ObjectMetaAccessor:
        if m := t.GetObjectMeta(); m != nil {
            return m, nil
        }
        return nil, errNotObject
    default:
        return nil, errNotObject
    }
}

//实现了对TypeMeta的属性访问和设置
type objectAccessor struct {   
	runtime.Object		
}

func (obj objectAccessor) GetKind() string {
	return obj.GetObjectKind().GroupVersionKind().Kind
}

func (obj objectAccessor) SetKind(kind string) {
	gvk := obj.GetObjectKind().GroupVersionKind()
	gvk.Kind = kind
	obj.GetObjectKind().SetGroupVersionKind(gvk)
}
...

func NewAccessor() MetadataAccessor {
	return resourceAccessor{}
}

// resourceAccessor implements ResourceVersioner and SelfLinker.
//聚合objectAccessor结构体方法和Accessor函数实现了MetadataAccessor接口
type resourceAccessor struct{} 

func (resourceAccessor) APIVersion(obj runtime.Object) (string, error) {
	return objectAccessor{obj}.GetAPIVersion(), nil
}

func (resourceAccessor) SetAPIVersion(obj runtime.Object, version string) error {
	objectAccessor{obj}.SetAPIVersion(version)
	return nil
}

func (resourceAccessor) Namespace(obj runtime.Object) (string, error) {
	accessor, err := Accessor(obj)
	if err != nil {
		return "", err
	}
	return accessor.GetNamespace(), nil
}

func (resourceAccessor) SetNamespace(obj runtime.Object, namespace string) error {
	accessor, err := Accessor(obj)
	if err != nil {
		return err
	}
	accessor.SetNamespace(namespace)
	return nil
}
4.3 runtime.RawExtension

runtime.RawExtension结构体,存储资源runtime.object和object的json字节流数据。

type RawExtension struct {
	// Raw is the underlying serialization of this object.
	//
	// TODO: Determine how to detect ContentType and ContentEncoding of 'Raw' data.
	Raw []byte `json:"-" protobuf:"bytes,1,opt,name=raw"`
	// Object can hold a representation of this extension - useful for working with versioned
	// structs.
	Object Object `json:"-"`
}

五、非结构化数据类型

无法预知数据结构的数据类型或属性名称不确定的数据类型是非结构化数据,其无法通过构建预定的struct数据结构来序列化或反序列化。当无法预知确定的数据结构类型或属性名称不确定时,可以通过以下结构解决。每个字符串对应一个json属性,其映射为interface{}类型对应值。

var result map[string]interface{}

Kubernetes 的所有 Resource 都可以转换为该结构类型。处理完成后,再将 Unstructured 转换成想要的确切类型。整个过程类似于 Go 语言的 interface{} 断言转换过程。另外,Unstructured 结构类型是通过 map[string]interface{} 转换的, 即k8s的非结构化数据通过map[string]interface{}表达。

type Unstructured struct {
	// Object is a JSON compatible map with string, float, int, bool, []interface{}, or
	// map[string]interface{}
	// children.
	Object map[string]interface{}
}

应用示例:

func (h *handler) decode(r io.Reader) error {
	decoder := yaml.NewYAMLOrJSONDecoder(r, 4096)
	for {
//提供object序列化后的存储
		ext := runtime.RawExtension{}
//每次序列化一个object到ext中,把接收的yaml文件流转为json流到ext中
		if err := decoder.Decode(&ext); err != nil {
			if err == io.EOF {
				return nil
			}
			return fmt.Errorf("error parsing: %v", err)
		}
		ext.Raw = bytes.TrimSpace(ext.Raw)
		if len(ext.Raw) == 0 || bytes.Equal(ext.Raw, []byte("null")) {
			continue
		}
//关键步骤反序列化json数据为相应的runtime.Object,并返回相应的gkv结构
		obj, gkv, err := unstructured.UnstructuredJSONScheme.Decode(ext.Raw, nil, nil)
		if err != nil {
			return err
		}
		ext.Object = obj
		h.groupVersionKinds = append(h.groupVersionKinds, gkv)
		h.exts = append(h.exts, &ext)
	}
}

unstructured结构体实现了runtime.object接口。

//代码位于 k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/unstructured.go
type Unstructured struct {
	// Object is a JSON compatible map with string, float, int, bool, []interface{}, or
	// map[string]interface{}
	// children.
	Object map[string]interface{}
}


// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *Unstructured) DeepCopyObject() runtime.Object {
	if c := in.DeepCopy(); c != nil {
		return c
	}
	return nil
}

func (obj *Unstructured) GetObjectKind() schema.ObjectKind { 
	return obj 
}

//Unstructured中schema.ObjectKind接口的实现
func (u *Unstructured) GroupVersionKind() schema.GroupVersionKind {
	gv, err := schema.ParseGroupVersion(u.GetAPIVersion())
	if err != nil {
		return schema.GroupVersionKind{}
	}
	gvk := gv.WithKind(u.GetKind())
	return gvk
}

func (u *Unstructured) GetAPIVersion() string {
	return getNestedString(u.Object, "apiVersion")
}

func (u *Unstructured) SetAPIVersion(version string) {
	u.setNestedField(version, "apiVersion")
}

//从map[string]interface查找特定key的value
func getNestedString(obj map[string]interface{}, fields ...string) string {
	val, found, err := NestedString(obj, fields...)
	if !found || err != nil {
		return ""
	}
	return val
}

// NestedString returns the string value of a nested field.
// Returns false if value is not found and an error if not a string.
func NestedString(obj map[string]interface{}, fields ...string) (string, bool, error) {
	val, found, err := NestedFieldNoCopy(obj, fields...)
	if !found || err != nil {
		return "", found, err
	}
	s, ok := val.(string)
	if !ok {
		return "", false, fmt.Errorf("%v accessor error: %v is of the type %T, expected string", jsonPath(fields), val, val)
	}
	return s, true, nil
}


func NestedFieldNoCopy(obj map[string]interface{}, fields ...string) (interface{}, bool, error) {
	var val interface{} = obj

	for i, field := range fields {
		if val == nil {
			return nil, false, nil
		}
		if m, ok := val.(map[string]interface{}); ok {
			val, ok = m[field]
			if !ok {
				return nil, false, nil
			}
		} else {
			return nil, false, fmt.Errorf("%v accessor error: %v is of the type %T, expected map[string]interface{}", jsonPath(fields[:i+1]), val, val)
		}
	}
	return val, true, nil
}

如前所述,K8S的资源实现了runtime.object接口,其实大多数资源结构体继承了ObjectMeta也实现了metav1.object接口来获取资源的公共属性。但是unstructured结构体中没有继承ObjectMeta结构体,所以unstructured单独实现了metav1.object接口,来获取资源对象的元数据的公共属性。

func (u *Unstructured) GetNamespace() string {
	return getNestedString(u.Object, "metadata", "namespace")
}

func (u *Unstructured) SetNamespace(namespace string) {
	if len(namespace) == 0 {
		RemoveNestedField(u.Object, "metadata", "namespace")
		return
	}
	u.setNestedField(namespace, "metadata", "namespace")
}

func (u *Unstructured) GetName() string {
	return getNestedString(u.Object, "metadata", "name")
}

func (u *Unstructured) SetName(name string) {
	if len(name) == 0 {
		RemoveNestedField(u.Object, "metadata", "name")
		return
	}
	u.setNestedField(name, "metadata", "name")
}
....

unstructured.UnstructuredJSONScheme.Decode源码Decode比较复杂,它尝试把提供的数据转换成YAML或者JSON,从中提取出其中存储的group、kind等,并合并缺省的gvk,然后在数据加载到对象中。

//代码位于 k8s.io/apimachinery/pkg/apis/meta/v1/unstructured/helpers.go
func (s unstructuredJSONScheme) Decode(data []byte, _ *schema.GroupVersionKind, obj runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) {
	var err error
	if obj != nil {
		err = s.decodeInto(data, obj)
	} else {
		obj, err = s.decode(data)
	}

	if err != nil {
		return nil, nil, err
	}

	gvk := obj.GetObjectKind().GroupVersionKind()
	if len(gvk.Kind) == 0 {
		return nil, &gvk, runtime.NewMissingKindErr(string(data))
	}

	return obj, &gvk, nil
}

func (s unstructuredJSONScheme) decode(data []byte) (runtime.Object, error) {
	type detector struct {
		Items gojson.RawMessage
	}
	var det detector
	if err := json.Unmarshal(data, &det); err != nil {
		return nil, err
	}

	if det.Items != nil {
		list := &UnstructuredList{}
		err := s.decodeToList(data, list)
		return list, err
	}

	// No Items field, so it wasn't a list.
	unstruct := &Unstructured{}
	err := s.decodeToUnstructured(data, unstruct)
	return unstruct, err
}

func (unstructuredJSONScheme) decodeToUnstructured(data []byte, unstruct *Unstructured) error {
	m := make(map[string]interface{})
	if err := json.Unmarshal(data, &m); err != nil {
		return err
	}

	unstruct.Object = m

	return nil
}

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值