kubernetes代码阅读apiserver的示例分析

48次阅读
没有评论

共计 5235 个字符,预计需要花费 14 分钟才能阅读完成。

这篇文章主要介绍了 kubernetes 代码阅读 apiserver 的示例分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让丸趣 TV 小编带着大家一起了解一下。

apiserver 是整个 kubernetes 的核心模块,做的事情多,代码量也较大。市面上已经有不少 apiserver 代码解读的文章了,但问题在于,由于 k8s 的代码变化很快,想写一篇长久能用的未必能做到。所以,我参照了《Kubernetes 权威指南》和浙大 SEL 实验室的一些文章,先把我看到的东西记下来,待后观是否有用。

kubernetes 源代码版本 1.2.0

代码阅读方法

先简单讲讲整个代码的目录结构

目录说明 api 输出接口文档用 build 构建脚本 cluster 适配不同 I 层的云,例如亚马逊 AWS,微软 Azure,谷歌 GCE 的集群启动脚本 cmd 所有的二进制可执行文件入口代码,例如 apiserver/scheduler/kubeletcontrib 项目贡献者 docs 文档,包括了用户文档、管理员文档、设计、新功能提议 example 使用案例 Godeps 项目中依赖使用的 Go 第三方包,例如 docker 客户端 SDK,rest 等 hack 工具箱,各种编译、构建、测试、校验的脚本都在这里面 hooksgit 提交前后触发的脚本 pkg 项目代码主目录,cmd 的只是个入口,这里是所有的具体实现 plugin 插件,k8s 认为调度器是插件的一部分,所以调度器的代码在这里 release 应该是 Google 发版本用的?test 测试相关的工具 third_party 一些第三方工具,应该不是强依赖的?wwwUI,不过已经被移动到新项目了

可以看到,关键实现代码都放在 pkg 这个目录下。对于 apiserver 这种跨度很广的组件而言,唯一有效的阅读方式估计就是

遍历 pkg 下所有的目录,概览大概知道这个目录是干啥的

从 cmd 这个入口来看 apiserver 的代码,然后一点点由浅入深,看 apiserver 的大致实现

分特性,看具体某个大的特性是怎么实现的,例如安全,例如和 etcd 存储对接

在上面这几步的过程中可以看看别人的代码阅读文档,能有效的节省时间

0. apiserver 主要实现了什么?

apiserver 是 k8s 系统中所有对象的增删查改盯的 http/restful 式服务端,其中盯是指 watch 操作。数据最终存储在分布式一致的 etcd 存储内,apiserver 本身是无状态的,提供了这些数据访问的认证鉴权、缓存、api 版本适配转换等一系列的功能。

restful 服务入门

对于 http 服务和使用 go 语言实现方式,可以看 go-restful 的文档和例子,对这个有基本的了解,这个文档对入门者和一知半解者极为有效!

1. 对象的数据结构

古人有言,程序就是算法 + 数据结构,搞懂了数据结构,整个程序的处理过程就明白了一半。对于 apiserver 的任何一个 api 请求来说,上图说明了所有的数据结构关系。

k8s 放在 etcd 内的存储对象是 api.Pod 对象 (无版本),从不同版本的请求路径标识来操作,例如 api/v1,最后获取到的是不同版本,例如 v1.Pod 的 json 文本。这里就经历了几个过程,包括

http client 访问 /api/v1/pod/xyz,想要获取这个 Pod 的数据

从 etcd 获取到 api.Pod 对象

api.Pod 对象转换为 v1.Pod 对象

v1.Pod 对象序列化为 json 或 yaml 文本

文本通过 http 的 response 体,返回给 http client

其中用于处理业务数据的关键数据结构是 APIGroupVersion,里面的几个成员变量的作用是:

成员作用 GroupVersion 包含 api/v1 这样的 string,用于标识这个实例 Serializer 对象序列化和反序列化器 Converter 这是一个强大的数据结构,这里放的是个接口,本体在 /pkg/conversion/conversion.go,几乎可以转换任意一种对象到另一种,只要你事先注入了相应的转换函数 Storage 这个 map 的 key,用于对象的 url,value 是一个 rest.Storage 结构,用于对接 etcd 存储,在初始化注册时,会把这个 map 化开,化为真正的 rest 服务到存储的一条龙服务 2. 入口和启动文件主要数据结构 / 函数用途 kubernetes/cmd/kube-apiserver/apiserver.go
入口 kubernetes/cmd/kube-apiserver/app/options/options.gostruct APIServer 启动选项 kubernetes/cmd/kube-apiserver/apiserver.gofunc Run 初始化一些客户端、启动 master 对象 kubernetes/pkg/genericapiserver/genericapiserver.gofunc Run 启动安全和非安全的 http 服务 3. API 分组、多版本的初始化注册 (Rest)

k8s 采用 ApiGroup 来管理所有的 api 分组和版本升级,目前有的 API 分组包括

核心组,REST 路径在 /api/v1,但这个路径不是固定的,v1 是当前的版本。与之相对应的代码里面的 apiVersion 字段的值是 v1。

扩展组,REST 路径在 /apis/extensions/$VERSION,相对应的代码里面的 apiVersion: extensions/$VERSION (例如当前的 apiVersion: extensions/v1beta1)。这里提供的 API 对象今后有可能会被移动到别的组内。

componentconfig 和 metrics 这这些组。

在这个文档里面讲述了实现 ApiGroup 的几个目标,包括 api 分组演化,对旧版 API 的向后兼容(Backwards compatibility),包括用户可以自定义自己的 api 等。接下来我们看看他么是怎么初始化注册的,这里都是缩减版代码,去掉了其他部分。

kubernetes/pkg/master/master.go

api 注册入口

func New(c *Config) (*Master, error) {m.InstallAPIs(c)
}

根据 Config 往 APIGroupsInfo 内增加组信息,然后通过 InstallAPIGroups 进行注册

func (m *Master) InstallAPIs(c *Config) {if err := m.InstallAPIGroups(apiGroupsInfo); err != nil {glog.Fatalf( Error in registering group versions: %v , err)
}

转换为 APIGroupVersion 这个关键数据结构,然后进行注册

func (s *GenericAPIServer) installAPIGroup(apiGroupInfo *APIGroupInfo) error {apiGroupVersion, err := s.getAPIGroupVersion(apiGroupInfo, groupVersion, apiPrefix)
 if err := apiGroupVersion.InstallREST(s.HandlerContainer); err != nil {return fmt.Errorf( Unable to setup API %v: %v , apiGroupInfo, err)
}

关键数据结构

kubernetes/pkg/apiserver/apiserver.go
type APIGroupVersion struct {Storage map[string]rest.Storage
 Root string
 // GroupVersion is the external group version
 GroupVersion unversioned.GroupVersion
}

实际注册的 Storage 的 map 如下:

kubernetes/pkg/master/master.go
m.v1ResourcesStorage = map[string]rest.Storage{
 pods : podStorage.Pod,
 pods/attach : podStorage.Attach,
 pods/status : podStorage.Status,
 pods/log : podStorage.Log,
 pods/exec : podStorage.Exec,
 pods/portforward : podStorage.PortForward,
 pods/proxy : podStorage.Proxy,
 pods/binding : podStorage.Binding,
 bindings : podStorage.Binding,

那么,这里的 map[string]rest.Storage 最后是怎么变成一个具体的 API 来提供服务的呢?例如这么一个 URL:

GET /api/v1/namespaces/{namespace}/pods/{name}

restful 服务的实现

k8s 使用的一个第三方库 github.com/emicklei/go-restful,里面提供了一组核心的对象,看例子

数据结构功能在 k8s 内的位置 restful.Container 代表一个 http rest 服务对象,包括一组 restful.WebServicegenericapiserver.go – GenericAPIServer.HandlerContainerrestful.WebService 由多个 restful.Route 组成,处理这些路径下所有的特殊的 MIME 类型等 api_installer.go – NewWebService()restful.Route 路径——处理函数映射 mapapi_installer.go – registerResourceHandlers()

实际注册过程

kubernetes/pkg/apiserver/api_installer.go
func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storage, ws *restful.WebService, proxyHandler http.Handler) (*unversioned.APIResource, error) {}

最终的 API 注册过程是在这个函数中完成的,把一个 rest.Storage 对象转换为实际的 getter, lister 等处理函数,并和实际的 url 关联起来。

4.etcd 存储的操作 (ORM)

上面已经基本厘清了从 http 请求 – restful.Route – rest.Storage 这条线路,那 rest.Storage 仅仅是一个接口,有何德何能,可以真正的操作 etcd 呢?

这段也是牵涉到多个文件,但还比较清晰,首先,所有的对象都有增删改查这些操作,如果为 Pod 单独搞一套,Controller 单独搞一套,那代码会非常重复,不可复用,所以存储的关键目录是在这里:

kubernetes/pkg/registry/generic/etcd/etcd.go

这个文件定义了所有的对 etcd 对象的操作,get,list,create 等,但具体的对象是啥,这个文件不关心;etcd 客户端地址,这个文件也不关心。这些信息都是在具体的 PodStorage 对象创建的时候注入的。以 Pod 为例子,文件在:

kubernetes/pkg/registry/pod/etcd/etcd.go

这里的 NewStorage 方法,把上述的信息注入了 etcd 里面去,生成了 PodStorage 这个对象。

// REST implements a RESTStorage for pods against etcd
type REST struct {
 *etcdgeneric.Etcd
 proxyTransport http.RoundTripper
}

由于 PodStorage.Pod 是一个 REST 类型,而 REST 类型采用了 Go 语言的 struct 匿名内部成员,天然就拥有 Get, List 等方法。

kubernetes/pkg/apiserver/api_installer.go

最后在这里把 PodStorage 转换成了 Getter 对象,并最终注册到 ApiGroup 里面去。

感谢你能够认真阅读完这篇文章,希望丸趣 TV 小编分享的“kubernetes 代码阅读 apiserver 的示例分析”这篇文章对大家有帮助,同时也希望大家多多支持丸趣 TV,关注丸趣 TV 行业资讯频道,更多相关知识等着你来学习!

正文完
 
丸趣
版权声明:本站原创文章,由 丸趣 2023-08-16发表,共计5235字。
转载说明:除特殊说明外本站除技术相关以外文章皆由网络搜集发布,转载请注明出处。
评论(没有评论)