The website uses cookies. By using this site, you agree to our use of cookies as described in the Privacy Policy.
I Agree
blank_error__heading
blank_error__body
Text direction?

kubernetes-api-machinery

阅读 1.1K0

Decode 问题

http server 或者 rpc server 要解决的一个问题是:如何解析用户的请求数据,并把他反序列化为语言中的一个具体的类型。反序列化的程序需要知道具体的类型(这在收到请求的时候就已经知道一些信息了,比如 用户访问的是 EchoService,那么请求肯定是 EchoRequest,不管是 EchoRequestV1,还是 EchoRequestV2), 同时反序列化程序即 decode 程序,还需要知道 他对应的语言里面的具体结构的信息,以便新建这个结构,填充数据,提交给上层处理。以一个 EchoService 为例,decode 程序需要从用户请求(如 post http://echo ) 文本或者二进制数据中创建出 EchoRequestV1,提供给上层处理,同时这个 decode 函数需要足够通用,他返回的是可能是一个 Message Interface, 里面是 EchoRequestV1,decode 相关的细节要么通过代码生成的技术提供给 decoder,要么在 二进制或者文本请求数据(或者 header等元数据)中携带这部分信息。

我们先来看看 golang protobuf 是如何解决这个问题的。

Protobuf Unmarshal

简单来说就是根据 生成的 golang 结构体的 Field tag来做 Unmarshal

// 生成的 golang 结构体
type EchoRequest struct {
	A  string   `protobuf:"bytes,1,opt,name=A,proto3" json:"A,omitempty"`
}

// 收到请求,在 Unmarshal 过程中会调用这个函数
func (m *EchoRequest) XXX_Unmarshal(b []byte) error {
	return xxx_messageInfo_EchoRequest.Unmarshal(m, b)
}

var xxx_messageInfo_EchoRequest proto.InternalMessageInfo


// InternalMessageInfo 是 Unmarshal 相关信息的存储位置
// b 是 protocol buffer raw 数据,而a 是要 unmarshal 到的结构
// 基础库不关心具体 unmarshal 类型,始终 unmarshal 到一个 interface Message
// 实际上面到结构调用到时候 会是 EchoRequest 类型
func (a *InternalMessageInfo) Unmarshal(msg Message, b []byte) error {
	// ... 略
	err := u.unmarshal(toPointer(&msg), b)
	return err
}

func (u *unmarshalInfo) unmarshal(m pointer, b []byte) error{
    if atomic.LoadInt32(&u.initialized) == 0 {
        // 保存 unmarshal 这个类型的函数、信息到一个结构里面,加速重复的 unmarshal
		u.computeUnmarshalInfo()
	}
	
	// .... 略
	
	if fn := f.unmarshal; fn != nil {
		var err error
		// unmarshal 这个 field 这里的关键是 unmarshal 到原始 bytes 设置到对应字段的
		// offset上面去,里面比较关键的是用了 golang reflect的 StructField 
		// StructField 的 Offset 是固定的,根据 一个结构的指针的 pointer 以及 Field的
		// offset 就可以直接用指针设置 结构的某个字段内容了
		b, err = fn(b, m.offset(f.field), wire)
		// ....
	}
}

Kubernetes Scheme

kubernetes 解决这个问题的方法很类似。github.com/kubernetes/apimachinery 部分为了解决这个问题而存在的,当然 kubernetes 的问题更为复杂一些,由于支持 资源的版本,他还需要解决版本之间互相转化的问题。具体的说 apimachinery 解决的是 kubernetes 的 API Object 的 Scheme, typing, encoding, decoding, and conversion问题。

GVK(GroupVersionKind) 和 GVR(GroupVersionResource) 是 apimachinery 里面的两个重要概念. 区别是 GVK 是一个 Object 概念,而 GVR 代表一个 Http Path

Http Path:

/apis/batch/v2alpha1/(namespaces/<name>)jobs
       |     |                           | 
    Apigroup Version                  Resource
    
/apis/extensions/v1alpha1/jobs 
/apis/batch/v2alpha1/jobs 
/apis/batch/v1/jobs

------------------------------------------ 
对象:

apiVersion: batch/v2alpha1
kind: Job
metadata:
  name: aaa
  namespace: tiems
spec:
    ...
    
apiVersion: v1/v2alpha1/v1alpha1 彼此之间可以转换 => "storage version" (比如v1)  => Etcd

反序列化使用 api.Scheme + gvk,而 gvk 中的信息可以从 request中获取

gvk := schema.GroupVersionKind{Group: "batch", Version: "v2alpha1", Kind: "Job"} 
obj := api.Scheme.New(gvk)
codec := api.Codecs.LegacyCodec(gvk.GroupVersion())
codec.Decode(reqBody, gvk, obj)

type Job struct {  
    metav1.TypeMeta     ---> type TypeMeta struct { Kind string; APIVersion string }
    metav1.ObjectMeta   ---> type ObjectMeta struct { Name string...}
    Spec JobSpec
    Status JobStatus 
}
 

代码流程分析

  • API groups install, 包括了 InstallLegacyAPI,InstallAPIs, LegacyAPI 指core group 在 /api/v1, 而named groups 在 /apis/$GROUP_NAME/$VERSION
  • (a *APIInstaller) Install 里面安装了各种 http path的处理流程
  • 比如 "POST" /apis/extensions/v1alpha1/jobs => restfulCreateNamedResource
  • endpoints/handlers/create.go: createHandler => 反序列化语句为 decoder.Decode(body, &defaultGVK, original)
  • defaultGVK 是默认 GVK 信息来自 path (APIGroup), 但是还是以 body 里面的 gvk 为准
  • 如 runtime/serializer/json/json.go: Decode 语句,会先运行 s.meta.Interpret(data), 从中或取真正的 GVK, 信息有不足的时候才会用 defaultGVK 补充
  • runtime.UseOrCreateObject(s.typer, s.creater, *actual, into)
  • 几个重要的函数 在 init的时候就执行了 var Scheme = runtime.NewScheme(); var Codecs = serializer.NewCodecFactory(Scheme); var ParameterCodec = runtime.NewParameterCodec(Scheme) 这里是全局的 Scheme. Codecs. ParameterCodec 正因为各种 type 都注册到了(用 scheme.AddKnownTypes ) 这个 scheme, 所以 Codecs 才有所有类型的信息以便于做序列化 反序列化
  • 本质 runtime.UseOrCreateObject 会调用 (s *Scheme) New(kind schema.GroupVersionKind) -> s.gvkToTypekind 从gvkToType去找类型,如果没有注册,会报错 NotRegisteredErr
  • 创建完成对象之后 则调用 caseSensitiveJsonIterator.Unmarshal 反序列数据到这个对象, 这个就比较简单了,使用 golang 的 unmarshal 函数就可以,kubernetes 里面用了 json-iterator
  • NewCodecFactory 存储了各种 content-type 以及各种 type 对应的 反序列化工具
  • Scheme 同时还注册了各种版本类型互相转换的信息, default变量信息,通过DefaultingFunc,AddConversionFunc 函数注册 localSchemeBuilder.Register(addDefaultingFuncs, addConversionFuncs)
image

应用

了解 api-machinery 对于编写 crd controller 或者 aggregation apiserver很重要,由于api-machinery 以及 controller 逻辑的复杂性,有很多辅助工具可以帮助生成很多相关的代码,比如 kubebuilder, wrangler, 这里我们只关注 api-machinery 相关的代码,可以找到需要上面流程中提到的注册,转换代码. 比如这里

image

参考1

参考2

Measure
Measure
Related Notes
Get a free MyMarkup account to save this article and view it later on any device.
Create account

End User License Agreement

Summary | 2 Annotations
解析用户的请求数据
2020/10/05 09:06
反序列化为语言中的一个具体的类型
2020/10/05 09:06