kubernetes kube-apiserver源码阅读1启动流程

文章目录

kubernetes代码版本: v1.20.2

个人认为kube-apiserver是k8s中最核心的组件,承上启下,无论是k8s其他组件还是是外部客户端都需要跟kube-apiserver组件进行交互,kube-apiserver负责接受请求并将数据持久化到后端存储(一般来说就是etcd.)。

总的来说,apiserver就做一件事,就是为k8s资源提供增删改查以及监听的接口,而这件事可以大致分为以下三个方面

  1. 处理请求前,这部分包括认证, 鉴权, 准入控制
  2. 处理请求中,这部分包括两个部分
    1. 内部资源 转发给对应hanlder
    2. 外部资源 转发给对应的注册方,比如metric server或者健康检查
  3. 处理请求后,这部分主要包括后端持久化,以及如何响应

因为kube-apiserver非常核心和重要,所以细节会非常多,并且逻辑也会很复杂,所以不能一上来钻各种细节,而应该从上到下,从外到内,依次阅读,本文只是大致过一下apiserver的启动逻辑。

环境准备

直接下载的的源代码并不会包括自动生成的文件,比如deepcopy, openapi之类的代码文件,这两部份的文件可以通过下面的命令生成

我生成的时候有报错,但是对应的文件还是生成了

生成转换, 拷贝, 设置默认值等函数

go mod vendor && ./hack/update-codegen.sh

可以搜索是否生成了以下文件

zz_generated.conversion.go
zz_generated.deepcopy.go
zz_generated.defaults.go

这些文件会在被导入的时候回在对应的Scheme里注册转换, 拷贝, 设置默认值等函数

生成openapi代码, 如果生成编辑器会报错,说是GetOpenAPIDefinitions找不到之类的。

cp -afr staging/src/k8s.io/code-generator/* vendor/k8s.io/code-generator/
go mod tidy && make gen_openapi ; ls -lh pkg/generated/openapi/

生成的文件应该在pkg\generated\openapi\zz_generated.openapi.go

启动流程

如果不深入kube-apiserver的初始化细节,kube-apiserver的启动逻辑还是比较清晰的,代码如下:

func main() {
    // 1.  惯用操作了
	command := app.NewAPIServerCommand()
	if err := command.Execute(); err != nil {
		os.Exit(1)
	}
}

func NewAPIServerCommand() *cobra.Command {
    // 2.
    // Options对象
	s := options.NewServerRunOptions()
    
	cmd := &cobra.Command{
		Use: "kube-apiserver",
		RunE: func(cmd *cobra.Command, args []string) error {
			verflag.PrintAndExitIfRequested()
			fs := cmd.Flags()
			cliflag.PrintFlags(fs)

			err := checkNonZeroInsecurePort(fs)
            // 3.
			// 设置默认值
			completedOptions, err := Complete(s)
			// 检验命令行参数
			if errs := completedOptions.Validate(); len(errs) != 0 {
				return utilerrors.NewAggregate(errs)
			}
			// 4.
			return Run(completedOptions, genericapiserver.SetupSignalHandler())
		},
	}
	// 添加命令行参数
	// 帮助文档显示逻辑
	return cmd
}

// 5.
func Run(completeOptions completedServerRunOptions, stopCh <-chan struct{}) error {
    // 创建服务链对象
	server, err := CreateServerChain(completeOptions, stopCh)
    // 启动前准备
	prepared, err := server.PrepareRun()
	return prepared.Run(stopCh)
}

代码分解如下:

  1. 创建cobra.Command对象, 并执行。cobra的常见操作
  2. 创建Options对象用于绑定命令行参数。k8s惯用模式。
  3. 运行主逻辑前设置默认值,校验参数。k8s的惯用模式
  4. 主逻辑入口
  5. 主逻辑的业务代码

总结起来就是,创建Options对象,解析命令行参数到Options参数,校验,补全Options对象,最后基于Options对象生成server,最后做启动前准备,然后启动。

但是仅是了解到这个层面,显然是无法理解kube-apiserver的,所以继续深入CreateServerChainCreateServerChain

CreateServerChain

为了使得k8s易于扩展,所以k8s将扩展的部分和自身的核心部分分离开的,分别叫做apiExtensionsServer, kubeAPIServer,那么怎么在两者之间分配请求和做服务发现呢? 那就再引入一个叫做aggregatorServer的组件。

func CreateServerChain() (*aggregatorapiserver.APIAggregator, error) {
    // 1.
	kubeAPIServerConfig, serviceResolver, pluginInitializer, err := CreateKubeAPIServerConfig(completedOptions, nodeTunneler, proxyTransport)

	// 2.
	apiExtensionsConfig, err := createAPIExtensionsConfig()
	apiExtensionsServer, err := createAPIExtensionsServer(apiExtensionsConfig, genericapiserver.NewEmptyDelegate())

    // 3.
	kubeAPIServer, err := CreateKubeAPIServer(kubeAPIServerConfig, apiExtensionsServer.GenericAPIServer)

	// 4.
	aggregatorConfig, err := createAggregatorConfig()
	aggregatorServer, err := createAggregatorServer(aggregatorConfig, kubeAPIServer.GenericAPIServer, apiExtensionsServer.Informers)

	return aggregatorServer, nil
}

代码分解如下:

  1. 基于Options对象创建一个新的Config对象, 这个对象在三个组件中都会用到,所以就先于三者之前创建。
  2. 创建apiExtensionsServer需要的Config对象, 基于这个Config对象创建apiExtensionsServer
  3. 创建kubeAPIServer
  4. 跟第二步差不多。

这里Options对象和Config对象其实本质功能差不多,都是存储配置信息,但是可能是为了抽象和解耦,所以Options对象会跟命令行参数联系的更紧密,而Config对象在Options对象的基础上创建服务所需的各个对象。

从代码可以知道,apiserver由三个组件组成,apiExtensionsServer,kubeAPIServer, aggregatorServer

请求的处理逻辑大致如下:

sequenceDiagram aggregatorServer ->> kubeAPIServer: kubeAPIServer ->> apiExtensionsServer: 如果自身处理不了就交给后面 kubeAPIServer ->> aggregatorServer: 如果处理的了就返回 apiExtensionsServer ->> aggregatorServer: 如果处理的了就返回,处理不了就not found

这里其实有一个疑问,既然是委托调用的这种设计模式,似乎只需要kubeAPIServer, apiExtensionsServer两个组件也行,为啥还需要一个aggregatorServer,这是因为两者只能返回自己所有的可以处理的资源,那么怎么同时获得两者的所有资源呢? 这个问题的解决方案是aggregatorServeraggregatorServer会将两者的资源聚合在一起提供给调用方,调用者一般是发现客户端(看过client-go代码的人应该不陌生)。

PrepareRun

启动流程总结起来就两步,CreateServerChain, PrepareRun, 前者创建一个APIAggregator, 它会将请求发送到后面的 kubeAPIServer,apiExtensionsServer, 也负责聚合两者的所有资源, 我们继续看看他的PrepareRun方法

func (s *APIAggregator) PrepareRun() (preparedAPIAggregator, error) {
    // 1.
	prepared := s.GenericAPIServer.PrepareRun()
	return preparedAPIAggregator{APIAggregator: s, runnable: prepared}, nil
}

func (s *GenericAPIServer) PrepareRun() preparedGenericAPIServer {
    // 2.
	s.delegationTarget.PrepareRun()
	s.installHealthz()
	s.installLivez()
	s.installReadyz()
	return preparedGenericAPIServer{s}
}

代码分解如下:

  1. 调用GenericAPIServer.PrepareRun()
  2. delegationTarget就是ServerChain上一个个组件, 会依次传递到 kubeAPIServer,apiExtensionsServer

代码并不是太复杂,就是调用GenericAPIServer.PrepareRun(), 如果你仔细阅读CreateServerChain的代码你会发现三个组件都会创建自己的GenericAPIServer, 它是apiserver中一个非常重要的对象,负责处理请求,而资源对象都是会注册到它上面。

总结

apiserver内部根据设计需要分为三个部分, apiExtensionsServer,kubeAPIServer, aggregatorServer,分别处理不同的逻辑。

参考链接