作者 | baron TKE专项技术组
导读:本文主要对kube-apiserver的功能在源码层面进行解读,分析了kube-apiserver三个基本功能的代码结构以及逻辑实现。分别解读了kube-apiserver服务路由的配置方式,kube-apiserver服务请求访问控制模式,bootstrapToken鉴权模式等内容。
版本环境• kubernetes版本:kubernetes:v1.17.0
• go环境:go version go1.13.4 windows/amd64
代码总体解析 1. ApiServer的功能设计 kube-apiserver的设计目的在于成为一台相对优雅简单的server,对外提供规范的Kubernetes API。它不仅提 供了外部访问集群api的渠道,同时也达到了解耦各组件的目的。 kube-apiserver的主要功能是处理其他业务的REST请求,对其进行验证及鉴权,请求成功后将结果更新 到后端存储 etcd (也可以是其他存储)中。 从kube-apiserver的功能可以看出,其源码主要围绕三个关键点展开。我们将依次从三个方面:配置 服务路由、访问权限以及同数据库(etcd)的交互对kube-apiserver源码进行解读。 2. 数据结构 数据结构体现了代码的组织和存储形式,为了便于后续代码阅读,这里先简单介绍下kube-apiserver主要逻辑涉及的几个重要的结构体,主要为ServerRunOptions、Config、APIGroupInfo。ServerRunOptions
ServerRunOptions位于k8s.io/kubernetes/cmd/kube-apiserver/app/options/options.go,是kube-apiserver的启动参数结构体,涵括了kube-apiserver启动需要所有参数,后续各个逻辑需要的相关配置参数均来源于此。其中每项参数的含义请参考官网文档:
type ServerRunOptions struct {
// kube-apiserver系列服务基础配置项 GenericServerRunOptions *genericoptions.ServerRunOptions // 后端存储etcd的配置项 Etcd *genericoptions.EtcdOptions SecureServing *genericoptions.SecureServingOptionsWithLoopback InsecureServing *genericoptions.DeprecatedInsecureServingOptionsWithLoopback Audit *genericoptions.AuditOptions Features *genericoptions.FeatureOptions Admission *kubeoptions.AdmissionOptions // 集群验证配置项 Authentication *kubeoptions.BuiltInAuthenticationOptions // 集群操作鉴权配置项 Authorization *kubeoptions.BuiltInAuthorizationOptions CloudProvider *kubeoptions.CloudProviderOptions APIEnablement *genericoptions.APIEnablementOptions EgressSelector *genericoptions.EgressSelectorOptions ...}
Config
Config位于k8s.io/apiserver/pkg/server/config.go,是kube-apiserver系列服务的通用配置项,用于与各服务对应的额外配置组合来构成对应服务的配置结构体。
type Config struct {
// https服务配置项SecureServing *SecureServingInfo // 服务验证配置Authentication AuthenticationInfo // 服务鉴权配置Authorization AuthorizationInfoLoopbackClientConfig *restclient.ConfigEgressSelector *egressselector.EgressSelectorRuleResolver authorizer.RuleResolverAdmissionControl admission.InterfaceAuditBackend audit.Backend // 配置服务路由BuildHandlerChainFunc func(apiHandler http.Handler, c *Config) (securehttp.Handler)RESTOptionsGetter genericregistry.RESTOptionsGetter ...}
APIGroupInfo
APIGroupInfo位于k8s.io/apiserver/pkg/server/generaticapiserver.go,定义了一个 API 组的相关信息。(以customresourcedefinitions资源为例,对应的API组name为apiextensions.k8s.io,API组支持的版本信息PrioritizedVersions为v1和v1beta1两个版本,版本 、资源 、 后端CRUD结构体的映射关系为map[v1beta1]
[customresourcedefinitions]customresourcedefinitionsStorage)type APIGroupInfo struct {
PrioritizedVersions []schema.GroupVersion // map[version][resource]storage VersionedResourcesStorageMap map[string]map[string]rest.Storage OptionsExternalVersion *schema.GroupVersion MetaGroupVersion *schema.GroupVersion Scheme *runtime.Scheme NegotiatedSerializer runtime.NegotiatedSerializer ParameterCodec runtime.ParameterCodec StaticOpenAPISpec *spec.Swagger}
kube-apiserver功能介绍
功能一:配置服务路由
kube-apiserver中配置路由的代码完全遵循的设计模式,先将处理方法注册到对应Route,再Route中注册到WebService,最后将所有的WebService注册到Container中,由Container负责分发流量。访问的过程为Container-->WebService-->Route。 kube-apiserver整个的api如下:![53ff051cf5a657235d9bb483cfd8dfa0.png](https://img-blog.csdnimg.cn/img_convert/53ff051cf5a657235d9bb483cfd8dfa0.png)
main
入口函数,这里使用构建apiserver的cli命令工具,代码逻辑放在app.NewAPIServerCommand():
func main() {
rand.Seed(time.Now().UnixNano()) command := app.NewAPIServerCommand() logs.InitLogs() defer logs.FlushLogs() if err := command.Execute(); err != nil {
os.Exit(1) }}
NewAPIServerCommand
在这里,代码组织形式是cobra构建cli命令行工具的模式。这里主要是对启动参数进行解析、完善及验证其合法性。各项启动参数及其意义可参考。apiserver的具体逻辑放在Run(completedOptions, genericapiserver.SetupSignalHandler())。
func NewAPIServerCommand() *cobra.Command s := options.NewServerRunOptions() cmd := &cobra.Command{
Use: "kube-apiserver", Long: `xxxxx`, RunE: func(cmd *cobra.Command, args []string) error {
verflag.PrintAndExitIfRequested() utilflag.PrintFlags(cmd.Flags()) completedOptions, err := Complete(s) if errs := completedOptions.Validate(); len(errs) != 0 {
return utilerrors.NewAggregate(errs) } return Run(completedOptions, genericapiserver.SetupSignalHandler()) , }
在笔者的测试环境里,kube-apiserver对应的启动参数如下:
Run
run函数逻辑很明了,分为三部分:
创建kube-apiserver服务链,配置各api路由的逻辑。
kube-apiserver服务运行前的准备,如设置健康检查和存活检查等操作。
正式运行kube-apiserver服务。
func Run(completeOptions completedServerRunOptions, stopCh {
server, err := CreateServerChain(completeOptions, stopCh) prepared, err := server.PrepareRun() return prepared.Run(stopCh)}
CreateServerChain
kube-apiserver包含一系列服务:
createAPIExtensionsServer:创建CustomResourceDefinitions对应的服务,配置对应api的路由(/apis/apiextensions.k8s.io/)。
CreateKubeAPIServer:创建kube-apiserver的api服务,配置/api和/apis根路径下的路由。
createAggregatorServer:创建聚合服务,聚合在kube-apiserver之外的拓展apiserver。
func CreateServerChain(completedOptions completedServerRunOptions, stopCh chan struct{}) (*aggregatorapiserver.APIAggregator, error) {
... // 启动CustomResourceDefinitions对应的服务 apiExtensionsServer, err := createAPIExtensionsServer(apiExtensionsConfig, genericapiserver.NewEmptyDelegate()) // 启动kube-apiserver的api服务 kubeAPIServer, err := CreateKubeAPIServer(kubeAPIServerConfig, apiExtensionsServer.GenericAPIServer) // 启动聚合服务 aggregatorServer, err := createAggregatorServer(aggregatorConfig, kubeAPIServer.GenericAPIServer, apiExtensionsServer.Informers) ...}
createAPIExtensionsServer
这里就以createAPIExtensionsServer为例详细梳理下kube-apiserver配置api路由的代码逻辑。
Complete()完善apiextensionsConfig的配置信息,然后到new的逻辑里面组装customresourcedefinitions对应的api组信息apiGroupInfo。
func createAPIExtensionsServer(apiextensionsConfig *apiextensionsapiserver.Config, delegateAPIServer genericapiserver.DelegationTarget) (*apiextensionsapiserver.CustomResourceDefinitions, error) {
return apiextensionsConfig.Complete().New(delegateAPIServer)}func (c completedConfig) New(delegationTarget genericapiserver.DelegationTarget) (*CustomResourceDefinitions, error) {
... // 将这组api的GroupName设置为apiextensions.k8s.io apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(apiextensions.GroupName, Scheme, metav1.ParameterCodec, Codecs) // 根据配置情况,装填对应资源的CRUD后端交互结构体 if apiResourceConfig.VersionEnabled(v1beta1.SchemeGroupVersion) {
storage := map[string]rest.Storage{} customResourceDefintionStorage := customresourcedefinition.NewREST(Scheme, c.GenericConfig.RESTOptionsGetter) storage["customresourcedefinitions"] = customResourceDefintionStorage storage["customresourcedefinitions/status"] = customresourcedefinition.NewStatusREST(Scheme, customResourceDefintionStorage) apiGroupInfo.VersionedResourcesStorageMap[v1beta1.SchemeGroupVersion.Version] = storage } if apiResourceConfig.VersionEnabled(v1.SchemeGroupVersion) {
storage := map[string]rest.Storage{} customResourceDefintionStorage := customresourcedefinition.NewREST(Scheme, c.GenericConfig.RESTOptionsGetter) storage["customresourcedefinitions"] = customResourceDefintionStorage storage["customresourcedefinitions/status"] = customresourcedefinition.NewStatusREST(Scheme, customResourceDefintionStorage) apiGroupInfo.VersionedResourcesStorageMap[v1.SchemeGroupVersion.Version] = storage } // 转入下一步逻辑 if err := s.GenericAPIServer.InstallAPIGroup(&apiGroupInfo); err != nil {
return nil, err } ...}
ApiGroupInfo在测试环 境的体现为:
![97604ba299165cb2cc7865c7187b15e0.png](https://img-blog.csdnimg.cn/img_convert/97604ba299165cb2cc7865c7187b15e0.png)
InstallAPIGroupfunc (s *GenericAPIServer) InstallAPIGroup(apiGroupInfo *APIGroupInfo) error {
return s.InstallAPIGroups(apiGroupInfo)}func (s *GenericAPIServer) InstallAPIGroups(apiGroupInfos ...*APIGroupInfo) error {
... for _, apiGroupInfo := range apiGroupInfos {
// 设置APIGroupPrefix即api根路径为/apis,随后转入下一步逻辑 if err := s.installAPIResources(APIGroupPrefix, apiGroupInfo, openAPIModels); err != nil {
return fmt.Errorf("unable to install api resources: %v", err) } ... } return nil}
apiGroupInfo在测试环境对应于:
installAPIResourcesfunc (s *GenericAPIServer) installAPIResources(apiPrefix string, apiGroupInfo *APIGroupInfo, openAPIModels openapiproto.Models) error {
// 分别对对应的api组的不同版本进行处理 for _, groupVersion := range apiGroupInfo.PrioritizedVersions {
if len(apiGroupInfo.VersionedResourcesStorageMap[groupVersion.Version]) == 0 {
klog.Warningf("Skipping API %v because it has no resources.", groupVersion) continue } apiGroupVersion := s.getAPIGroupVersion(apiGroupInfo, groupVersion, apiPrefix) if apiGroupInfo.O