Kubernetes api-server源码阅读3(源码篇)正在更新中

云原生学习路线导航页(持续更新中)

本文是 Kubernetes api-server源码阅读 系列第三篇,主要学习 kubernetes api-server 的 源码

1.Kubernetes ApiServer的地位

  • Kubernetes APIServer的地位类似于我们的数据库,必不可少

  • 从业务上来说,APIServer并不是核心,它不处理具体的业务,具体的业务有其他组件来负责,如schedule、kubelet等

  • 但是,业务的处理,却又少不了APIServer。

  • 因此,APIServer地位极其重要

2.本章学习目录

  • 内容提要与准备:介绍
  • Server启动过程:API Server本身也有很多组件,组件的启动过程,如何相互配合的,会介绍一下
  • Scheme机制:类似于Windows的注册表,注册集群所有支持的APIGroup、APIObject,实现kubernetes各个版本的数据结构的互换,简化编程手段
  • Generic Server:抽取了API Server、Extension Server、Aggregation Server中,公共的部分
  • API Server:APIServer的子Server,也叫Master,最开始的时候,Kubernetes只有这么一个Server
  • Extension Server:APIServer的子Server,像CustomResourceDefinitions(CRD)这种,都是在这个Extension Server里,使得我们可以不必修改master Server,就可以添加自己的想法,扩展kubernetes的功能
  • Aggregation Server:APIServer的子Server,灵活性最高的,为了解决扩展master Server功能的代码Review不及时的问题,推出了Aggregation Server,支持用户自己开发APIServer,然后扩展功能
  • Admission:通过插件机制,让我们使用者有机会 介入到 对象的创建过程中
  • Default Filters:一系列的过滤器,当http request到达APIServer的时候,APIServer会使用这些过滤器做些什么
  • 处理Http Req:处理实际的请求
  • 认证与鉴权:登陆、权限校验等,是如何实现的
  • 其他:视情况说一些有意义的话题

3.内容提要与准备

3.1.学习APIServer源码的前置知识

  • kubernetes的基础概念和功能:推荐官方学习网站kubernetes.io
  • Golang基础:尤其是并发等知识,kubernetes大量借助了golang的这些特性
  • 重要的工具库:kubernetes构建的过程中,使用了一些其他项目或工具,也为社区贡献了一些项目和工具
    • gengo(包括informer机制):kubernetes的代码生成基本都是用这个,需要了解基本annotations的使用。再学习一下informer机制,是非常重要的构件客户端的机制
    • Cobra:命令行工具框架。因为go写出来的程序最终就是一个可执行文件,也就需要命令行交互
    • go-restful:golang写 restful service 的一个框架,APIServer实际上就是一个暴露Restful风格API的web应用
    • etcd:APIServer 的数据存储中心,不用看源码,知道使用方法即可
  • Restful基础:

3.2.书籍推荐和避坑

在这里插入图片描述

3.3.源码学习方法

  • 先整体,后局部
    • 一定先了解整体架构,大流程,然后逐步深入到局部细节。
    • 类似一种剥洋葱式的阅读。
    • 切忌阅读过程中过早陷入细节
  • 耐心,多看代码
    • 下载,并多多浏览源代码,对代码的理解不是能通过听别人讲解来完全获得的。
    • 对于大型开源项目,软件的设计文档往往是非常欠缺的,这是开源项目的通病,要做好上下求索的准备
    • 保持耐心很重要

3.4.重要源码文件夹

在这里插入图片描述

  • ./cmd/kube-apiserver
    • API Server可执行程序的主入口
    • 基于cobra,主要负责接收命令行参数,把api server启动起来。
    • 也是代码学习的入口
  • ./vendor/k8s.io
    • Vendor机制是 老一代 依赖包管理机制,module是 新一代
    • 不过vendor目录存在的时候,还是会被优先使用
  • ./staging/src/k8s.io
    • staging中包含正在被单独抽离的组件,软引用到vendor下
    • 组件抽离不是一朝一夕的事情,在抽离过程中,会把代码放在staging。一旦抽离完成,就会把代码移到vendor,以外部依赖的方式引入
  • ./pkg
    • 大部分k8s的源码所在地,除了被抽离为单独组件的部分。
    • 像api server、proxy、kubelet等组件的代码,主要都在pkg下
  • ./pkg/api 和 ./pkg/apis,要区分开
    • api文件夹:包含和OpenAPI相关的模型定义等内容,用于根据OpenAPI规范形成符合其规定的API
    • apis文件夹:包含内建API Groups、内建API Objects、scheme等相关的代码大部分在这里
  • ./plugin
    • Kubernetes内建的plugin实现,kubernetes1.24中,包含了admission和auth两个部分

4.API Server的启动过程

4.1.几个概念的解释

  • API Object

    • API Object是Kubernetes内部管理的基本元素,例如Deployment、Pod、Service等,代码内部也常用“API”来称呼它们
    • API Object也是 k8s etcd的信息存储单元
  • API Group

    • API Group,就是一组 具有 共有性质 的 API Object 组成的对象集合
    • 例如,apps这个group由Deployment、ReplicaSet、StatefulSet等组成
  • Legacy API Object

    • kubernetes初期,是没有API Group概念的,一开始就那么几个固定的API Object,如Pod、Event、Node等,没有必要分组。但是后来随着 API Object 越来越多,引入了 API Group 的概念
    • 可是,原本的那些老的 API Object,不属于任何Group,我们也 不能直接把他们加入某个Group
      • 因为这些 老API Object的 URL是不包含Group的,和代码都是耦合的。如果我们把它们加入一个Group,URL就会改变,那么要改的东西实在太多了
    • 所以,对这些老臣,高看一眼,它们都不属于任何的Group,只是在代码里,把他们都放在了一个core的目录里,所以我们也把他们称为 Core API Object,或者叫做 Legacy API Object。Legacy 是遗产的意思
  • API Resource

    • 发往APIServer的请求,大都是去操作某一个API Object的,所以每一个API Object都有自己的URL结构,也称为 API Resource

    • API Resource 结构为:Group(组)、Version(版本)和 Resource(资源种类)

      • (还有一种 /metrics,/healthz 之类的结构,这里面的资源是系统自带的,不在任何组里)
    • 通过这样的结构,整个 Kubernetes 里的所有资源,实际上就可以用如下图的树形结构表示出来:

      在这里插入图片描述

    • 参考博客:API 与 Resource

4.2.Cobra使用简介

4.2.1.Cobra学习的一些资料

  • github:https://github.com/spf13/cobra?tab=readme-ov-file
  • 官网:https://cobra.dev/
  • 官方文档:https://pkg.go.dev/github.com/spf13/cobra#section-documentation
  • 特别好的入门博客,看一遍就基本都清楚了:https://o-my-chenjian.com/2017/09/20/Using-Cobra-With-Golang/

4.2.2.Cobra开发一个命令大体分5步

  • 定义一个命令对象

    • type Command struct{}包含的所有属性:https://pkg.go.dev/github.com/spf13/cobra#Command
    var rootCmd = &cobra.Command{
      Use:   "hugo",
      Short: "Hugo is a very fast static site generator",
      Long: `A Fast and Flexible Static Site Generator built with
                    love by spf13 and friends in Go.
                    Complete documentation is available at http://hugo.spf13.com`,
      Run: func(cmd *cobra.Command, args []string) {
        // Do Stuff Here
      },
    }
    
  • 为命令指定参数 flags

    • 不同于Argument ,Flag是控制程序行为的既定参数
    • 它们可以通过cmd的Flags(方法获取和设置,如:
      • cmd.Flags().BoolVar(&p, ……)
      • cmd.Flags().StringVar(&p, ……)
  • 编写flags和args的校验逻辑

    • 校验都可以通过在cmd的方法进行
    • 例如 cmd.MarkFlagsRequiredTogether( "a”,“b”)cmd.ExactArgs(10) 或 在cmd.Args方法中校验
  • 编写 命令响应代码

    • cmd的Run或RunE属性都是方法,它们是响应命令,执行操作,这里是代码的主体。
    • RunE会把出现的错误返回,Run不会
  • 执行命令

    • 到这里就非常简单,直接调用cmd.Execute()来触发命令的逻辑,也就是上面的Run或RunE。
    • 框架会负责把用户输入转化到Arg和Flag中,校验,然后交给Run方法

4.2.3.为命令设置 flags 的一些注意点

  • 可以通过两种方法获取一个命令的 FlagSet,然后就可以对这个Set做 获取或添加操作了,二者略有不同:

    • cmd.Flags():表示获取当前命令的 local flags,即只有在使用这个命令的时候,才能设置这些参数
    • cmd.PersistentFlags():表示获取当前命令的 全局flags,即在使用所有命令的时候,都可以设置的参数
    func (c *Command) Flags() *flag.FlagSet
    
    func (c *Command) PersistentFlags() *flag.FlagSet
    
  • 在为FlagSet设置值的写法上,Flags()和PersistentFlags()是一样的,都有4种不同的写法,方法名称里有两个规定:

    • P 的表示 需要给参数指定一个短名称shorthand,如下面的g
    • Var 的表示 需要给参数指定一个 接受对象,如下面的&p
    rootCmd.Flags().Bool("gender", ......)
    rootCmd.Flags().BoolP("gender", "g", ......)
    rootCmd.Flags().BoolVar(&p, "gender", ......)
    rootCmd.Flags().BoolVarP(&p, "gender", "g", ......)
    
    rootCmd.PersistentFlags().Bool("gender", ......)
    rootCmd.PersistentFlags().BoolP("gender", "g", ......)
    rootCmd.PersistentFlags().BoolVar(&p, "gender", ......)
    rootCmd.PersistentFlags().BoolVarP(&p, "gender", "g", ......)
    

4.2.4.Command的前置、后置方法执行顺序

  • Run功能的执行先后顺序如下:

    • PersistentPreRun

    • PreRun

    • Run

    • PostRun

    • PersistentPostRun

  • RunE功能的执行先后顺序如下:

    • PersistentPreRunE
    • PreRunE
    • RunE
    • PostRunE
    • PersistentPostRunE

4.3.API Server在Cobra下的启动过程

4.3.1.API Server创建Cobra.Command的源码解析

  • main函数,在 /cmd/kube-apiserver/apiserver.go

    • app.NewAPIServerCommand() 就是创建了一个 Cobra.Command
    • 然后,使用 cli 包下的 Run 方法,把这个command执行起来,其实就是调了command的Execute方法
    • Run方法 退出的时候,有个返回码,作为程序的退出码
    func main() {
    	command := app.NewAPIServerCommand()
    	code := cli.Run(command)
    	os.Exit(code)
    }
    
  • app.NewAPIServerCommand() 内容如下:

    • 从结构上看,这就是创建了一个 Cobra.Command
    // NewAPIServerCommand creates a *cobra.Command object with default parameters
    func NewAPIServerCommand() *cobra.Command {
       
        // 用于包含所有用户可选flag的,最完整的一个结构体
        // 用户在命令行的设置,最终都会合并到这个s中去
        s := options.NewServerRunOptions()
        
    	cmd := &cobra.Command{
            // 命令的名称
    		Use: "kube-apiserver",
            // 命令的解释,长解释
    		Long: `The Kubernetes API server validates and configures data
    for the api objects which include pods, services, replicationcontrollers, and
    others. The API Server services REST operations and provides the frontend to the
    cluster's shared state through which all other components interact.`,
    
            // SilenceUsage 设置为 true 时,表示在命令执行遇到输入错误时,不显示使用方法
    		SilenceUsage: true,
            
            // 在RunE方法执行之前,会先执行这个方法,做一些预设置
    		PersistentPreRunE: func(*cobra.Command, []string) error {
    			// 禁用 kube-apiserver 自己的警告
    			rest.SetDefaultWarningHandler(rest.NoWarnings{})
    			return nil
    		},
            
            // 真正的执行方法,最后会返回一个 error
    		RunE: func(cmd *cobra.Command, args []string) error {
                // 检查参数是否指定了 -version,如果指定了,则打印版本后退出
    			verflag.PrintAndExitIfRequested()
                
                // 取出当前执行命令的 FlagSet (这个已经是 合并之后的了 = 默认+用户输入)
    			fs := cmd.Flags()
    
    			// Activate logging as soon as possible, after that
    			// show flags with the final logging configuration.
    			if err := s.Logs.ValidateAndApply(utilfeature.DefaultFeatureGate); err != nil {
    				return err
    			}
    
                // 打印所有的flags
    			cliflag.PrintFlags(fs)
    
    			// 将s中,没有设置值的那些,进行默认值的设置,返回值completedOptions
    			completedOptions, err := Complete(s)
    			if err != nil {
    				return err
    			}
    
    			// 对completedOptions,进行校验(比如校验是否有冲突),校验失败,直接退出
    			if errs := completedOptions.Validate(); len(errs) != 0 {
    				return utilerrors.NewAggregate(errs)
    			}
    
                // 真正的Server启动逻辑(会把options配置传进去,并传入一个 停止信号channel)
    			return Run(completedOptions, genericapiserver.SetupSignalHandler())
    		},
    		Args: func(cmd *cobra.Command, args []string) error {
                // 做了参数的校验,不允许有任何的args,有的话,就报错
    			for _, arg := range args {
    				if len(arg) > 0 {
    					return fmt.Errorf("%q does not take any arguments, got %q", cmd.CommandPath(), args)
    				}
    			}
    			return nil
    		},
    	}
    
        // 获取用户在命令行输入的flags
    	fs := cmd.Flags()
        
        // 获取s中,默认的flags
    	namedFlagSets := s.Flags()
        
        // 下面这段是将s中的默认设置,都合并到 命令cmd的flags 上去
    	verflag.AddFlags(namedFlagSets.FlagSet("global"))
    	globalflag.AddGlobalFlags(namedFlagSets.FlagSet("global"), cmd.Name(), logs.SkipLoggingConfigurationFlags())
    	options.AddCustomGlobalFlags(namedFlagSets.FlagSet("generic"))
    	for _, f := range namedFlagSets.FlagSets {
    		fs.AddFlagSet(f)
    	}
    
        // 设置用法和帮助函数。打印我们需要的flags,而不是所有的flags。
    	cols, _, _ := term.TerminalSize(cmd.OutOrStdout())
    	cliflag.SetUsageAndHelpFunc(cmd, namedFlagSets, cols)
    
        // 最后把command返回给main函数
    	return cmd
    }
    

4.3.2.API Server Run方法的源码解析

4.3.2.1.学习APIServer Run方法的前置知识
1、理解几个概念
  • Options
    • Options 是面向用户的一个概念,接收用户的输入,创建出一个Options对象。
    • Options 含有Flag中可以指定的选项。如果使用者没有全部指定flag , option会为漏掉的flag使用默认值
  • Config
    • Config 是 面向Server的一个概念,含有Server运转需要的各种配置信息
    • Config 由 Option转化而来的,根据用户输入,转换成Server运行的配置信息 Config
  • API Server 实例
    • API Server实例,实际上就是一个Web服务对象,该对象启动之后就是一个API Server了。
    • API Server实例,是基于Config信息进行启动的,有了Config也就具备了启动的条件
  • 三者关系
    • 从上述看,三者之间的关系是:Flags–>Options–>Config–>API Server实例
2、Server Chain
  • API Server并不是一个Server,而是一个Server的集合,包含了4个小Server

    在这里插入图片描述

  • 需要注意

    • 这些Server在启动的时候,构建过程是 从右向左 进行构建的,最终构建成一个 Server链,链头是Aggregation Server
    • 当一个Http Request到达时,请求的流向是 从左向右
  • Aggregation Server更像是一个分发器,由他来判断 请求应该由 Master APIServer处理,还是由用户自定义的Custom APIServer处理

4.3.2.2.APIServer Run方法源码解析
  • 由4.3.1可知,NewAPIServerCommand() 方法创建的 Cobra.Command 中,RunE 方法最后调用了Run(completedOptions, genericapiserver.SetupSignalHandler())。本节我们就来学习这个Run方法

  • Run方法如下:

    // Run runs the specified APIServer.  This should never exit.
    func Run(completeOptions completedServerRunOptions, stopCh <-chan struct{}) error {
    	// 为了方便debug,打印当前版本信息(包括git、编译器等)
    	klog.Infof("Version: %+v", version.Get())
    
        // 记录golang的一些设置
    	klog.InfoS("Golang settings", "GOGC", os.Getenv("GOGC"), "GOMAXPROCS", os.Getenv("GOMAXPROCS"), "GOTRACEBACK", os.Getenv("GOTRACEBACK"))
    
        // 这里就是构建我们说的 Server Chain 了,返回的就是 链头对象
    	server, err := CreateServerChain(completeOptions, stopCh)
    	if err != nil {
    		return err
    	}
    
    	prepared, err := server.PrepareRun()
    	if err != nil {
    		return err
    	}
    
    	return prepared.Run(stopCh)
    }
    
  • 下面我们进入 CreateServerChain(),查看 Server Chain 的创建过程

    • 注意我们在4.3.2.2中说的,Server的构建过程是 从右向左 的:Not Found Handler–>Extension Server–>Kube API Server(Master Server)–>Aggregation Server。下面的代码正好印证了我们的说法
    // CreateServerChain creates the apiservers connected via delegation.
    func CreateServerChain(completedOptions completedServerRunOptions, stopCh <-chan struct{}) (*aggregatorapiserver.APIAggregator, error) {
        
        // 先根据 completedOptions,创建出 kube API Server 的 Config(实际上对好几个Server都起作用)
    	kubeAPIServerConfig, serviceResolver, pluginInitializer, err := CreateKubeAPIServerConfig(completedOptions)
    	if err != nil {
    		return nil, err
    	}
    
    	// 然后 创建出 Extension server 的Config
    	apiExtensionsConfig, err := createAPIExtensionsConfig(*kubeAPIServerConfig.GenericConfig, kubeAPIServerConfig.ExtraConfig.VersionedInformers, pluginInitializer, completedOptions.ServerRunOptions, completedOptions.MasterCount,
    		serviceResolver, webhook.NewDefaultAuthenticationInfoResolverWrapper(kubeAPIServerConfig.ExtraConfig.ProxyTransport, kubeAPIServerConfig.GenericConfig.EgressSelector, kubeAPIServerConfig.GenericConfig.LoopbackClientConfig, kubeAPIServerConfig.GenericConfig.TracerProvider))
    	if err != nil {
    		return nil, err
    	}
    
        // 下面就开始创建Server了
        
        // 第一个创建的是 notFoundHandler
    	notFoundHandler := notfoundhandler.New(kubeAPIServerConfig.GenericConfig.Serializer, genericapifilters.NoMuxAndDiscoveryIncompleteKey)
        
        // 第二个创建的是 apiExtensionsServer,并将链条下一个设置为 notFoundHandler
    	apiExtensionsServer, err := createAPIExtensionsServer(apiExtensionsConfig, genericapiserver.NewEmptyDelegateWithCustomHandler(notFoundHandler))
    	if err != nil {
    		return nil, err
    	}
    
        // 第三个创建的是 kubeAPIServer,并将链条下一个设置为 apiExtensionsServer
    	kubeAPIServer, err := CreateKubeAPIServer(kubeAPIServerConfig, apiExtensionsServer.GenericAPIServer)
    	if err != nil {
    		return nil, err
    	}
    
    	// 聚合Server在最后,先创建 Aggregator Config
    	aggregatorConfig, err := createAggregatorConfig(*kubeAPIServerConfig.GenericConfig, completedOptions.ServerRunOptions, kubeAPIServerConfig.ExtraConfig.VersionedInformers, serviceResolver, kubeAPIServerConfig.ExtraConfig.ProxyTransport, pluginInitializer)
    	if err != nil {
    		return nil, err
    	}
        
        // 第四个创建的是 aggregatorServer,并将链条下一个设置为 kubeAPIServer
    	aggregatorServer, err := createAggregatorServer(aggregatorConfig, kubeAPIServer.GenericAPIServer, apiExtensionsServer.Informers)
    	if err != nil {
    		// we don't need special handling for innerStopCh because the aggregator server doesn't create any go routines
    		return nil, err
    	}
        
        // 最后将链头 aggregatorServer 返回
    	return aggregatorServer, nil
    }
    
  • 我们暂时说到这里,不再深入了

4.4.Master Server装载API Object源码解析

  • API Server 通过暴露Restful服务,对外提供API Object的能力。那么API Object究竟是什么时候完成装载的?

    • 只有被装载进来的API Object,API Server才会认识它,才能响应操作它的Rest请求
  • 流程图如下:

    在这里插入图片描述

  • 下面用文字描述一下整个过程:

    • 在上面的API Server的Run()中,我们看到有一个 CreateKubeAPIServer(),是创建 Master Server 的,该方法代码如下:

      // CreateKubeAPIServer creates and wires a workable kube-apiserver
      func CreateKubeAPIServer(kubeAPIServerConfig *controlplane.Config, delegateAPIServer genericapiserver.DelegationTarget) (*controlplane.Instance, error) {
          // 先调用Complete(),对 kubeAPIServer 的Config进行最后的设置
          // 然后就调用了New(),该方法才是去完成 Master Server 创建的核心方法
      	kubeAPIServer, err := kubeAPIServerConfig.Complete().New(delegateAPIServer)
      	if err != nil {
      		return nil, err
      	}
      
      	return kubeAPIServer, nil
      }
      
    • 上述代码 调用的 New(),是属于 pkg/controlplane 包下的 instance.go 文件

      • New方法的签名如下,返回值就是一个Instance,也就是一个Server实例,即Kube API Server实例(Master Server实例)
      func (c completedConfig) New(delegationTarget genericapiserver.DelegationTarget) (*controlplane.Instance, error)
      
    • 先理解一下New()要做什么事情,主要做两件事:

      • 创建 Storage Provider 对象,并为之创建APIGroupInfo对象
        • 先找到Master Server所支持的所有 Build-In 的 API Group,每个Group会包含不同的API Object。
        • 然后每一个API Group都会形成一个 Storage Provider 的对象,然后每一个Storage Provider 都会创建出来一个对应的 APIGroupInfo对象,APIGroupInfo对象 就是 负责处理与Etcd交互的请求
        • 但是,在kubernetes里比较奇怪的是, APIGroupInfo 对象不仅负责和etcd交互,还负责响应外部的Rest请求。相当于把 java 里的 controller和数据库操作 写在一块了
      • 安装 LegacyAPIs、APIs
        • 先安装LegacyAPIs,就是把那些 不属于任何Group 的元老API Object们,装载进来
        • 再安装APIs,就是把 所有Group的API Object们,装载进来
    • 知道New()要做的事情后,我们看一下 New() 的源码

      func (c completedConfig) New(delegationTarget genericapiserver.DelegationTarget) (*Instance, error) {
      	......
      
          // 创建了一个 Instance 结构体变量,待会作为返回值
      	m := &Instance{
      		GenericAPIServer:          s,
      		ClusterAuthenticationInfo: c.ExtraConfig.ClusterAuthenticationInfo,
      	}
      
      	// 安装 LegacyAPI,方法内部会把元老APIObject装载进来,下面还会详细介绍
      	if err := m.InstallLegacyAPI(&c, c.GenericConfig.RESTOptionsGetter); err != nil {
      		return nil, err
      	}
      
      	// 这里是为每一个APIGroup,创建一个 RESTStorageProvider,放在一个切片里
      	restStorageProviders := []RESTStorageProvider{
      		apiserverinternalrest.StorageProvider{},
      		authenticationrest.RESTStorageProvider{Authenticator: c.GenericConfig.Authentication.Authenticator, APIAudiences: c.GenericConfig.Authentication.APIAudiences},
      		authorizationrest.RESTStorageProvider{Authorizer: c.GenericConfig.Authorization.Authorizer, RuleResolver: c.GenericConfig.RuleResolver},
      		autoscalingrest.RESTStorageProvider{},
      		batchrest.RESTStorageProvider{},
      		certificatesrest.RESTStorageProvider{},
      		coordinationrest.RESTStorageProvider{},
      		discoveryrest.StorageProvider{},
      		networkingrest.RESTStorageProvider{},
      		noderest.RESTStorageProvider{},
      		policyrest.RESTStorageProvider{},
      		rbacrest.RESTStorageProvider{Authorizer: c.GenericConfig.Authorization.Authorizer},
      		schedulingrest.RESTStorageProvider{},
      		storagerest.RESTStorageProvider{},
      		flowcontrolrest.RESTStorageProvider{InformerFactory: c.GenericConfig.SharedInformerFactory},
      		appsrest.StorageProvider{},
      		admissionregistrationrest.RESTStorageProvider{},
      		eventsrest.RESTStorageProvider{TTL: c.ExtraConfig.EventTTL},
      	}
          
          // 安装 APIs,方法内部会把所有 group 下的 API Object 装载进来
      	if err := m.InstallAPIs(c.ExtraConfig.APIResourceConfigSource, c.GenericConfig.RESTOptionsGetter, restStorageProviders...); err != nil {
      		return nil, err
      	}
      
      	......
      
      	return m, nil
      }
      
    • New()m.InstallLegacyAPI(&c, c.GenericConfig.RESTOptionsGetter) 源码分析

      // InstallLegacyAPI will install the legacy APIs for the restStorageProviders if they are enabled.
      func (m *Instance) InstallLegacyAPI(c *completedConfig, restOptionsGetter generic.RESTOptionsGetter) error {
          
          // 为 元老APIObject们,创建一个 RESTStorageProvider
      	legacyRESTStorageProvider := corerest.LegacyRESTStorageProvider{
      		StorageFactory:              c.ExtraConfig.StorageFactory,
      		ProxyTransport:              c.ExtraConfig.ProxyTransport,
      		KubeletClientConfig:         c.ExtraConfig.KubeletClientConfig,
      		EventTTL:                    c.ExtraConfig.EventTTL,
      		ServiceIPRange:              c.ExtraConfig.ServiceIPRange,
      		SecondaryServiceIPRange:     c.ExtraConfig.SecondaryServiceIPRange,
      		ServiceNodePortRange:        c.ExtraConfig.ServiceNodePortRange,
      		LoopbackClientConfig:        c.GenericConfig.LoopbackClientConfig,
      		ServiceAccountIssuer:        c.ExtraConfig.ServiceAccountIssuer,
      		ExtendExpiration:            c.ExtraConfig.ExtendExpiration,
      		ServiceAccountMaxExpiration: c.ExtraConfig.ServiceAccountMaxExpiration,
      		APIAudiences:                c.GenericConfig.Authentication.APIAudiences,
      	}
          
          // 使用这个 RESTStorageProvider,创建一个 APIGroupInfo 出来
          // APIGroupInfo 包含了该Group的所有APIObject,还含有一个map。记录了rest.storage的信息
      	legacyRESTStorage, apiGroupInfo, err := legacyRESTStorageProvider.NewLegacyRESTStorage(c.ExtraConfig.APIResourceConfigSource, restOptionsGetter)
      	if err != nil {
      		return fmt.Errorf("error building core storage: %v", err)
      	}
      	if len(apiGroupInfo.VersionedResourcesStorageMap) == 0 { // if all core storage is disabled, return.
      		return nil
      	}
          
      	......
      
          // 在GenericAPIServer中,完成 LegacyAPIGroupInfo 的安装
      	if err := m.GenericAPIServer.InstallLegacyAPIGroup(genericapiserver.DefaultLegacyAPIPrefix, &apiGroupInfo); err != nil {
      		return fmt.Errorf("error in registering group versions: %v", err)
      	}
      	return nil
      }
      
    • New()m.InstallAPIs(c.ExtraConfig.APIResourceConfigSource, c.GenericConfig.RESTOptionsGetter, restStorageProviders...) 源码分析

      func (m *Instance) InstallAPIs(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter, restStorageProviders ...RESTStorageProvider) error {
      	
          // 上来先创建了一个 APIGroupInfo的切片,名称为 apiGroupsInfo
          apiGroupsInfo := []*genericapiserver.APIGroupInfo{}
      
          ......
      	
          // 遍历参数传进来的 restStorageProviders 切片
      	for _, restStorageBuilder := range restStorageProviders {
      		groupName := restStorageBuilder.GroupName()
              
              // 对每一个StorageProvider,都创建一个apiGroupInfo出来
      		apiGroupInfo, err := restStorageBuilder.NewRESTStorage(apiResourceConfigSource, restOptionsGetter)
      		
              ......
      
              // 把创建出来的apiGroupInfo,都加入切片 apiGroupsInfo
      		apiGroupsInfo = append(apiGroupsInfo, &apiGroupInfo)
      	}
      
          // 最后将 apiGroupsInfo切片,传入方法,完成 所有APIObject 的安装
      	if err := m.GenericAPIServer.InstallAPIGroups(apiGroupsInfo...); err != nil {
      		return fmt.Errorf("error in registering group versions: %v", err)
      	}
      	return nil
      }
      

5.详解Scheme机制

5.1.理解几个概念

  • Version
    • Version有两种:External Version、Internal Version
    • External Version,面向API Server外部。每个API Group都会有多个version,每一个version会包含多个kind,一个kind可能会出现在多个version下
    • Internal Version,是API Server在内部程序中处理数据时API object的实际类型
    • 为什么需要两种Version?
      • 因为在kubernetes发展过程中,会演进出很多version,version之间有时需要互换。如果每两个version就写一个互换方法,两两组合下来太繁琐了,形成的是一个 拓扑型
      • 所以,就在kubernetes内部,给每个API Group,制定了一个Internal Version,作为所有Version互换的中间站。v1–>Internal Version–>v2,这样组合的拓扑型就变成了 星型
  • GVK
    • GVK,即Group, Version,Kind。这个三元组唯一确定了一个Kind。
    • 程序中,GVK就是一个含三字段的结构体
  • Type
    • 代码中常见“Type”,例如 gvk2Type 字段。
    • 这里的Type是一个APl Object 结构体类型(元数据),是程序中处理一个Object时使用的数据结构
    • 简单来说,假如我现在有 一个未知数据 + 该数据的GVK类型,那么我就可以将这段未知数据,解析成一个具体的Kind对应的Type

5.2.Scheme的作用和应用场景

  • Scheme是一个结构体,内含处理 内外部Version之间转换、GVK和Go Type之间转换 的数据和方法。

  • Scheme有很多应用场景,下面列举3个:

    • GVK 与 Go Type之间转换

      • Scheme内部有两张map,分别对应 gvk–>type,type–>gvk,使得二者可以互相找到
      • 当你拿着GVK信息时,通过gvk–>type,才能将数据转成一个go type,进而填充属性
      • 当你从请求里得到一个数据的type后,通过type–>gvk抽取出它的gvk,进而进行一些判断
    • 填充API Object的默认值

      • API Object有诸多属性,使用者在操作一个object时,不太可能给出所有属性值
      • 另外在Object从一个Version转换到另一个Version时,由于两个Version有所差异,有些属性可能没有值,此时就需要为不存在对应关系的字段填值
      • Scheme内部维护了这样的default函数,可以完成各种情况下的默认值填充
    • 内外部Version之间Convert

      • 所有外部Version之间的转换,都会先转成内部Version,然后再转成另一个外部version。

        • v1-->internal version-->v2
        • v2-->internal version-->v1
      • 转换函数都是自动生成,然后注册到scheme之内

      • 如下图:

        • 假如我们编写的 json/yamlapiObject 数据,apiVersion是v2,通过 Marshal 的方法转成 v2 的 go struct 结构
        • API Server 所有的算法和逻辑,都是基于 internal version 写的
        • 因此要在程序里处理这个kind,不会直接操作v2的结构,而是先借助 Scheme 转成内部版本 internal version ,在程序中使用,处理完后,再转成v2向外输出。
        • 如果我编写的 json/yamlapiVersion是v1,也是可以正常处理的。这样的设计,就使得版本的兼容性变得特别好,程序也只需要处理Internal Version,编写也变得简单

        在这里插入图片描述

5.3.Scheme的内部结构

  • Scheme的结构体如下:

    type Scheme struct {
        // map,记录 gvk-->type。其中type是通过反射的方式记录的
    	gvkToType map[schema.GroupVersionKind]reflect.Type
    
    	// map,记录 type-->gvk
    	typeToGVK map[reflect.Type][]schema.GroupVersionKind
    
    	// map,记录 type-->gvk。像pod这种,只有一个version的,就记录在这里。
    	unversionedTypes map[reflect.Type]schema.GroupVersionKind
    
    	// map,记录 gvk-->type。像pod这种,只有一个version的,就记录在这里。
    	unversionedKinds map[string]reflect.Type
    
    	// Map from version and resource to the corresponding func to convert
    	// resource field labels in that version to internal version.
    	fieldLabelConversionFuncs map[schema.GroupVersionKind]FieldLabelConversionFunc
    
    	// map,记录默认方法。为某一个具体的type,设置默认值
    	defaulterFuncs map[reflect.Type]func(interface{})
    
    	// 转换器
    	converter *conversion.Converter
    
    	// 记录version的优先级。当没有选择version的时候,优先使用谁
    	versionPriority map[string][]string
    
    	// observedVersions keeps track of the order we've seen versions during type registration
    	observedVersions []schema.GroupVersion
    
    	// schemeName is the name of this scheme.  If you don't specify a name, the stack of the NewScheme caller will be used.
    	// This is useful for error reporting to indicate the origin of the scheme.
    	schemeName string
    }
    
  • Scheme的方法

    在这里插入图片描述

5.4.构造并填充Scheme源码解析

  • Scheme是什么时候填充的?

    • Scheme是类似于Windows注册表的概念,用于存储当前APIServer 所有的APIGroup信息
    • 但是从上面的代码解析中,没有看到 API Group 写入 Scheme,那么APIGroup信息到底是什么时候填充到Scheme中的呢?
    • 实际上,API Server 巧妙地利用的 golang 的 init机制,实现了 所有 APIGroup 注册到 Scheme
  • 流程图如下:

    在这里插入图片描述

  • 下面用文字描述一下整个过程:

    • cmd/kube-apiserver/app/server.go,导入了这么一个包:pkg/controlplane

    • pkg/controlplane 导入过程中,它又导入了 pkg/controlplane/import_known_versions.go 文件的所有 import

    • 我们看一下 pkg/controlplane/import_known_versions.go 文件的源代码

      • 可以看到,这个文件没有任何代码,只有很多 空导入,目的是 将这些包 也导入进来
      • 可以看到,这些包都可以分成两部分:k8s.io/kubernetes/pkg/apis/apps + install
        • k8s.io/kubernetes/pkg/apis/apps就是一个group
        • 所以这里是进行group的导入
      package controlplane
      
      import (
      	// These imports are the API groups the API server will support.
      	_ "k8s.io/kubernetes/pkg/apis/admission/install"
      	_ "k8s.io/kubernetes/pkg/apis/admissionregistration/install"
      	_ "k8s.io/kubernetes/pkg/apis/apiserverinternal/install"
      	_ "k8s.io/kubernetes/pkg/apis/apps/install"
      	_ "k8s.io/kubernetes/pkg/apis/authentication/install"
      	_ "k8s.io/kubernetes/pkg/apis/authorization/install"
      	_ "k8s.io/kubernetes/pkg/apis/autoscaling/install"
      	_ "k8s.io/kubernetes/pkg/apis/batch/install"
      	_ "k8s.io/kubernetes/pkg/apis/certificates/install"
      	_ "k8s.io/kubernetes/pkg/apis/coordination/install"
      	_ "k8s.io/kubernetes/pkg/apis/core/install"
      	_ "k8s.io/kubernetes/pkg/apis/discovery/install"
      	_ "k8s.io/kubernetes/pkg/apis/events/install"
      	_ "k8s.io/kubernetes/pkg/apis/extensions/install"
      	_ "k8s.io/kubernetes/pkg/apis/flowcontrol/install"
      	_ "k8s.io/kubernetes/pkg/apis/imagepolicy/install"
      	_ "k8s.io/kubernetes/pkg/apis/networking/install"
      	_ "k8s.io/kubernetes/pkg/apis/node/install"
      	_ "k8s.io/kubernetes/pkg/apis/policy/install"
      	_ "k8s.io/kubernetes/pkg/apis/rbac/install"
      	_ "k8s.io/kubernetes/pkg/apis/scheduling/install"
      	_ "k8s.io/kubernetes/pkg/apis/storage/install"
      )
      
    • 我们挑一个常见的 "_ k8s.io/kubernetes/pkg/apis/apps/install" 看一看

      • install包下只有一个文件:install.go

      • install.go 中,通过 init(),触发 Install()

        • apps.AddToScheme(scheme) 就是把 apps 包下的所有 APIObject 加入Scheme中
        • v1beta1.AddToScheme(scheme) 就是把 v1beta1 版本所有 APIObject 加入Scheme中
        package install
        
        import (
        	"k8s.io/apimachinery/pkg/runtime"
        	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
        	"k8s.io/kubernetes/pkg/api/legacyscheme"
        	"k8s.io/kubernetes/pkg/apis/apps"
        	"k8s.io/kubernetes/pkg/apis/apps/v1"
        	"k8s.io/kubernetes/pkg/apis/apps/v1beta1"
        	"k8s.io/kubernetes/pkg/apis/apps/v1beta2"
        )
        
        func init() {
        	Install(legacyscheme.Scheme)
        }
        
        // Install registers the API group and adds types to a scheme
        func Install(scheme *runtime.Scheme) {
        	utilruntime.Must(apps.AddToScheme(scheme))
        	utilruntime.Must(v1beta1.AddToScheme(scheme))
        	utilruntime.Must(v1beta2.AddToScheme(scheme))
        	utilruntime.Must(v1.AddToScheme(scheme))
        	utilruntime.Must(scheme.SetVersionPriority(v1.SchemeGroupVersion, v1beta2.SchemeGroupVersion, v1beta1.SchemeGroupVersion))
        }
        
  • 综上所述,在API Server一启动的时候,利用pkg的import机制,就把scheme填充上了,属于非常靠前的了

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值