前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >kubernetes-api-machinery

kubernetes-api-machinery

原创
作者头像
王磊-字节跳动
发布2019-10-12 00:25:01
3.8K0
发布2019-10-12 00:25:01
举报
文章被收录于专栏:01ZOO01ZOO

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

代码语言:txt
复制
// 生成的 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

代码语言:txt
复制
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中获取

代码语言:txt
复制
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
image

应用

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

image
image

参考1

参考2

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Decode 问题
  • Protobuf Unmarshal
  • Kubernetes Scheme
  • 应用
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档