Kubernetes-apiServer源码深度分析(七)Master API Server

329 阅读5分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第10天,点击查看活动详情

记得看代码中的注释哈,理解都在里面 源码基于v1.24

本篇文章讲解处理kubernetes原生资源对象(如pod,Deployment等)的Master API Serve,从server的配置(option->config)到创建实例的过程慢慢深入

MasterServer的引入

之前的三四五六节都是讲的GenericServer,是一些通用的功能,有了GenericServer之后,其他的Server就看他基于GenericServer进行拓展,代码量就会减少许多

MasterServer主要处理k8s的原生对象的增删改查如Pod,Deployment...

image.png 只需做一些额外的配置 image.png

先找到通用配置

func CreateServerChain(completedOptions completedServerRunOptions) (*aggregatorapiserver.APIAggregator, error) {
/*
    CreateKubeAPIServerConfig非常重要
*/
   kubeAPIServerConfig, serviceResolver, pluginInitializer, err := CreateKubeAPIServerConfig(completedOptions)
   if err != nil {
      return nil, err
   }
/*
   先配置在创建server,上一个的server被下一个server利用
 */
   // If additional API servers are added, they should be gated.
   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
   }

   notFoundHandler := notfoundhandler.New(kubeAPIServerConfig.GenericConfig.Serializer, genericapifilters.NoMuxAndDiscoveryIncompleteKey)
   apiExtensionsServer, err := createAPIExtensionsServer(apiExtensionsConfig, genericapiserver.NewEmptyDelegateWithCustomHandler(notFoundHandler))
   if err != nil {
      return nil, err
   }

   kubeAPIServer, err := CreateKubeAPIServer(kubeAPIServerConfig, apiExtensionsServer.GenericAPIServer)
   if err != nil {
      return nil, err
   }
/*
   aggregator相当于一个请求的转发器,把不同路径的请求转发到不同server去处理
 */
   // aggregator comes last in the chain
   aggregatorConfig, err := createAggregatorConfig(*kubeAPIServerConfig.GenericConfig, completedOptions.ServerRunOptions, kubeAPIServerConfig.ExtraConfig.VersionedInformers, serviceResolver, kubeAPIServerConfig.ExtraConfig.ProxyTransport, pluginInitializer)
   if err != nil {
      return nil, err
   }
   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
   }

   return aggregatorServer, nil
}

func CreateKubeAPIServerConfig(s completedServerRunOptions) (
   *controlplane.Config,
   aggregatorapiserver.ServiceResolver,
   []admission.PluginInitializer,
   error,
) {
   proxyTransport := CreateProxyTransport()
/*
        buildGenericConfig 构建通用的配置
*/
   genericConfig, versionedInformers, serviceResolver, pluginInitializers, admissionPostStartHook, storageFactory, err := buildGenericConfig(s.ServerRunOptions, proxyTransport)
   if err != nil {
      return nil, nil, nil, err
   }

...
   config := &controlplane.Config{
      GenericConfig: genericConfig,
      ExtraConfig: controlplane.ExtraConfig{
         APIResourceConfigSource: storageFactory.APIResourceConfigSource,
         StorageFactory:          storageFactory,
         EventTTL:                s.EventTTL,
         KubeletClientConfig:     s.KubeletConfig,
         EnableLogsSupport:       s.EnableLogsHandler,
         ProxyTransport:          proxyTransport,

         ServiceIPRange:          s.PrimaryServiceClusterIPRange,
         APIServerServiceIP:      s.APIServerServiceIP,
         SecondaryServiceIPRange: s.SecondaryServiceClusterIPRange,

         APIServerServicePort: 443,

         ServiceNodePortRange:      s.ServiceNodePortRange,
         KubernetesServiceNodePort: s.KubernetesServiceNodePort,

         EndpointReconcilerType: reconcilers.Type(s.EndpointReconcilerType),
         MasterCount:            s.MasterCount,

         ServiceAccountIssuer:        s.ServiceAccountIssuer,
         ServiceAccountMaxExpiration: s.ServiceAccountTokenMaxExpiration,
         ExtendExpiration:            s.Authentication.ServiceAccounts.ExtendExpiration,

         VersionedInformers: versionedInformers,

         IdentityLeaseDurationSeconds:      s.IdentityLeaseDurationSeconds,
         IdentityLeaseRenewIntervalSeconds: s.IdentityLeaseRenewIntervalSeconds,
      },
   }
...
   return config, serviceResolver, pluginInitializers, nil
}

下面buildGenericConfig里面就是一系列的配置,把option转换成config的过程

func buildGenericConfig(
   s *options.ServerRunOptions,
   proxyTransport *http.Transport,
) (
   genericConfig *genericapiserver.Config,
   versionedInformers clientgoinformers.SharedInformerFactory,
   serviceResolver aggregatorapiserver.ServiceResolver,
   pluginInitializers []admission.PluginInitializer,
   admissionPostStartHook genericapiserver.PostStartHookFunc,
   storageFactory *serverstorage.DefaultStorageFactory,
   lastErr error,
) {
   genericConfig = genericapiserver.NewConfig(legacyscheme.Codecs)
   genericConfig.MergedResourceConfig = controlplane.DefaultAPIResourceConfigSource()

   if lastErr = s.GenericServerRunOptions.ApplyTo(genericConfig); lastErr != nil {
      return
   }

   if lastErr = s.SecureServing.ApplyTo(&genericConfig.SecureServing, &genericConfig.LoopbackClientConfig); lastErr != nil {
      return
   }
   if lastErr = s.Features.ApplyTo(genericConfig); lastErr != nil {
      return
   }
   if lastErr = s.APIEnablement.ApplyTo(genericConfig, controlplane.DefaultAPIResourceConfigSource(), legacyscheme.Scheme); lastErr != nil {
      return
   }
   if lastErr = s.EgressSelector.ApplyTo(genericConfig); lastErr != nil {
      return
   }
   if utilfeature.DefaultFeatureGate.Enabled(genericfeatures.APIServerTracing) {
      if lastErr = s.Traces.ApplyTo(genericConfig.EgressSelector, genericConfig); lastErr != nil {
         return
      }
   }
   // wrap the definitions to revert any changes from disabled features
   getOpenAPIDefinitions := openapi.GetOpenAPIDefinitionsWithoutDisabledFeatures(generatedopenapi.GetOpenAPIDefinitions)
   genericConfig.OpenAPIConfig = genericapiserver.DefaultOpenAPIConfig(getOpenAPIDefinitions, openapinamer.NewDefinitionNamer(legacyscheme.Scheme, extensionsapiserver.Scheme, aggregatorscheme.Scheme))
   genericConfig.OpenAPIConfig.Info.Title = "Kubernetes"
   if utilfeature.DefaultFeatureGate.Enabled(genericfeatures.OpenAPIV3) {
      genericConfig.OpenAPIV3Config = genericapiserver.DefaultOpenAPIV3Config(getOpenAPIDefinitions, openapinamer.NewDefinitionNamer(legacyscheme.Scheme, extensionsapiserver.Scheme, aggregatorscheme.Scheme))
      genericConfig.OpenAPIV3Config.Info.Title = "Kubernetes"
   }

   genericConfig.LongRunningFunc = filters.BasicLongRunningRequestCheck(
      sets.NewString("watch", "proxy"),
      sets.NewString("attach", "exec", "proxy", "log", "portforward"),
   )

   kubeVersion := version.Get()
   genericConfig.Version = &kubeVersion

   storageFactoryConfig := kubeapiserver.NewStorageFactoryConfig()
   storageFactoryConfig.APIResourceConfig = genericConfig.MergedResourceConfig
   completedStorageFactoryConfig, err := storageFactoryConfig.Complete(s.Etcd)
   if err != nil {
      lastErr = err
      return
   }
   storageFactory, lastErr = completedStorageFactoryConfig.New(genericConfig.DrainedNotify())
   if lastErr != nil {
      return
   }
   if genericConfig.EgressSelector != nil {
      storageFactory.StorageConfig.Transport.EgressLookup = genericConfig.EgressSelector.Lookup
   }
   if utilfeature.DefaultFeatureGate.Enabled(genericfeatures.APIServerTracing) {
      storageFactory.StorageConfig.Transport.TracerProvider = genericConfig.TracerProvider
   } else {
      storageFactory.StorageConfig.Transport.TracerProvider = oteltrace.NewNoopTracerProvider()
   }
   if lastErr = s.Etcd.ApplyWithStorageFactoryTo(storageFactory, genericConfig); lastErr != nil {
      return
   }

   // Use protobufs for self-communication.
   // Since not every generic apiserver has to support protobufs, we
   // cannot default to it in generic apiserver and need to explicitly
   // set it in kube-apiserver.
   genericConfig.LoopbackClientConfig.ContentConfig.ContentType = "application/vnd.kubernetes.protobuf"
   // Disable compression for self-communication, since we are going to be
   // on a fast local network
   genericConfig.LoopbackClientConfig.DisableCompression = true

   kubeClientConfig := genericConfig.LoopbackClientConfig
   clientgoExternalClient, err := clientgoclientset.NewForConfig(kubeClientConfig)
   if err != nil {
      lastErr = fmt.Errorf("failed to create real external clientset: %v", err)
      return
   }
   versionedInformers = clientgoinformers.NewSharedInformerFactory(clientgoExternalClient, 10*time.Minute)

   // Authentication.ApplyTo requires already applied OpenAPIConfig and EgressSelector if present
   if lastErr = s.Authentication.ApplyTo(&genericConfig.Authentication, genericConfig.SecureServing, genericConfig.EgressSelector, genericConfig.OpenAPIConfig, genericConfig.OpenAPIV3Config, clientgoExternalClient, versionedInformers); lastErr != nil {
      return
   }

   genericConfig.Authorization.Authorizer, genericConfig.RuleResolver, err = BuildAuthorizer(s, genericConfig.EgressSelector, versionedInformers)
   if err != nil {
      lastErr = fmt.Errorf("invalid authorization config: %v", err)
      return
   }
   if !sets.NewString(s.Authorization.Modes...).Has(modes.ModeRBAC) {
      genericConfig.DisabledPostStartHooks.Insert(rbacrest.PostStartHookName)
   }

   lastErr = s.Audit.ApplyTo(genericConfig)
   if lastErr != nil {
      return
   }

   admissionConfig := &kubeapiserveradmission.Config{
      ExternalInformers:    versionedInformers,
      LoopbackClientConfig: genericConfig.LoopbackClientConfig,
      CloudConfigFile:      s.CloudProvider.CloudConfigFile,
   }
   serviceResolver = buildServiceResolver(s.EnableAggregatorRouting, genericConfig.LoopbackClientConfig.Host, versionedInformers)
   pluginInitializers, admissionPostStartHook, err = admissionConfig.New(proxyTransport, genericConfig.EgressSelector, serviceResolver, genericConfig.TracerProvider)
   if err != nil {
      lastErr = fmt.Errorf("failed to create admission plugin initializer: %v", err)
      return
   }

   err = s.Admission.ApplyTo(
      genericConfig,
      versionedInformers,
      kubeClientConfig,
      utilfeature.DefaultFeatureGate,
      pluginInitializers...)
   if err != nil {
      lastErr = fmt.Errorf("failed to initialize admission: %v", err)
      return
   }

   if utilfeature.DefaultFeatureGate.Enabled(genericfeatures.APIPriorityAndFairness) && s.GenericServerRunOptions.EnablePriorityAndFairness {
      genericConfig.FlowControl, lastErr = BuildPriorityAndFairness(s, clientgoExternalClient, versionedInformers)
   }

   return
}

流程解析如下图 image.png

MasterServer的创建

对配置进行complete

func CreateKubeAPIServer(kubeAPIServerConfig *controlplane.Config, delegateAPIServer genericapiserver.DelegationTarget) (*controlplane.Instance, error) {
   /*
   主要逻辑在New中,函数返回的*controlplane.Instance就是MasterServer的代名词,Complete是加载配置的默认值
    */
   kubeAPIServer, err := kubeAPIServerConfig.Complete().New(delegateAPIServer)
   if err != nil {
      return nil, err
   }

   return kubeAPIServer, nil
}

func (c *Config) Complete() CompletedConfig {
   cfg := completedConfig{
      c.GenericConfig.Complete(c.ExtraConfig.VersionedInformers),
      &c.ExtraConfig,
   }

   serviceIPRange, apiServerServiceIP, err := ServiceIPRange(cfg.ExtraConfig.ServiceIPRange)
   if err != nil {
      klog.Fatalf("Error determining service IP ranges: %v", err)
   }
   if cfg.ExtraConfig.ServiceIPRange.IP == nil {
      cfg.ExtraConfig.ServiceIPRange = serviceIPRange
   }
   if cfg.ExtraConfig.APIServerServiceIP == nil {
      cfg.ExtraConfig.APIServerServiceIP = apiServerServiceIP
   }

   discoveryAddresses := discovery.DefaultAddresses{DefaultAddress: cfg.GenericConfig.ExternalAddress}
   discoveryAddresses.CIDRRules = append(discoveryAddresses.CIDRRules,
      discovery.CIDRRule{IPRange: cfg.ExtraConfig.ServiceIPRange, Address: net.JoinHostPort(cfg.ExtraConfig.APIServerServiceIP.String(), strconv.Itoa(cfg.ExtraConfig.APIServerServicePort))})
   cfg.GenericConfig.DiscoveryAddresses = discoveryAddresses

   if cfg.ExtraConfig.ServiceNodePortRange.Size == 0 {
      // TODO: Currently no way to specify an empty range (do we need to allow this?)
      // We should probably allow this for clouds that don't require NodePort to do load-balancing (GCE)
      // but then that breaks the strict nestedness of ServiceType.
      // Review post-v1
      cfg.ExtraConfig.ServiceNodePortRange = kubeoptions.DefaultServiceNodePortRange
      klog.Infof("Node port range unspecified. Defaulting to %v.", cfg.ExtraConfig.ServiceNodePortRange)
   }

   if cfg.ExtraConfig.EndpointReconcilerConfig.Interval == 0 {
      cfg.ExtraConfig.EndpointReconcilerConfig.Interval = DefaultEndpointReconcilerInterval
   }

   if cfg.ExtraConfig.MasterEndpointReconcileTTL == 0 {
      cfg.ExtraConfig.MasterEndpointReconcileTTL = DefaultEndpointReconcilerTTL
   }

   if cfg.ExtraConfig.EndpointReconcilerConfig.Reconciler == nil {
      cfg.ExtraConfig.EndpointReconcilerConfig.Reconciler = c.createEndpointReconciler()
   }

   if cfg.ExtraConfig.RepairServicesInterval == 0 {
      cfg.ExtraConfig.RepairServicesInterval = repairLoopInterval
   }

   return CompletedConfig{&cfg}
}

流程解析如下图 image.png 完善配置后,开始NewServer

func (c completedConfig) New(delegationTarget genericapiserver.DelegationTarget) (*Instance, error) {
   if reflect.DeepEqual(c.ExtraConfig.KubeletClientConfig, kubeletclient.KubeletClientConfig{}) {
      return nil, fmt.Errorf("Master.New() called with empty config.KubeletClientConfig")
   }
   /*
   这个GenericConfig挺重要的,后面再讲,我们先找Api-Object的装载
    */
   s, err := c.GenericConfig.New("kube-apiserver", delegationTarget)
   if err != nil {
      return nil, err
   }

   if c.ExtraConfig.EnableLogsSupport {
      routes.Logs{}.Install(s.Handler.GoRestfulContainer)
   }

   // Metadata and keys are expected to only change across restarts at present,
   // so we just marshal immediately and serve the cached JSON bytes.
   md, err := serviceaccount.NewOpenIDMetadata(
      c.ExtraConfig.ServiceAccountIssuerURL,
      c.ExtraConfig.ServiceAccountJWKSURI,
      c.GenericConfig.ExternalAddress,
      c.ExtraConfig.ServiceAccountPublicKeys,
   )
   if err != nil {
      // If there was an error, skip installing the endpoints and log the
      // error, but continue on. We don't return the error because the
      // metadata responses require additional, backwards incompatible
      // validation of command-line options.
      msg := fmt.Sprintf("Could not construct pre-rendered responses for"+
         " ServiceAccountIssuerDiscovery endpoints. Endpoints will not be"+
         " enabled. Error: %v", err)
      if c.ExtraConfig.ServiceAccountIssuerURL != "" {
         // The user likely expects this feature to be enabled if issuer URL is
         // set and the feature gate is enabled. In the future, if there is no
         // longer a feature gate and issuer URL is not set, the user may not
         // expect this feature to be enabled. We log the former case as an Error
         // and the latter case as an Info.
         klog.Error(msg)
      } else {
         klog.Info(msg)
      }
   } else {
      routes.NewOpenIDMetadataServer(md.ConfigJSON, md.PublicKeysetJSON).
         Install(s.Handler.GoRestfulContainer)
   }
   /*
   在这里有个Instance,是MasterServer的代名词
    */
   m := &Instance{
      GenericAPIServer:          s,
      ClusterAuthenticationInfo: c.ExtraConfig.ClusterAuthenticationInfo,
   }

   // install legacy rest storage
   /*
   既负责和etcd打交道也负责响应restful的HTTP请求,下面是装载LegacyAPI的对象
    */
   if err := m.InstallLegacyAPI(&c, c.GenericConfig.RESTOptionsGetter); err != nil {
      return nil, err
   }

   /*
   非Legacy的对象的Provider,进行api的装载
    */
   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},
      // keep apps after extensions so legacy clients resolve the extensions versions of shared resource names.
      // See https://github.com/kubernetes/kubernetes/issues/42392
      appsrest.StorageProvider{},
      admissionregistrationrest.RESTStorageProvider{},
      eventsrest.RESTStorageProvider{TTL: c.ExtraConfig.EventTTL},
   }
   if err := m.InstallAPIs(c.ExtraConfig.APIResourceConfigSource, c.GenericConfig.RESTOptionsGetter, restStorageProviders...); err != nil {
      return nil, err
   }
/*
    拓展的customServer
*/
   m.GenericAPIServer.AddPostStartHookOrDie("start-cluster-authentication-info-controller", func(hookContext genericapiserver.PostStartHookContext) error {
      kubeClient, err := kubernetes.NewForConfig(hookContext.LoopbackClientConfig)
      if err != nil {
         return err
      }
      controller := clusterauthenticationtrust.NewClusterAuthenticationTrustController(m.ClusterAuthenticationInfo, kubeClient)

      // generate a context  from stopCh. This is to avoid modifying files which are relying on apiserver
      // TODO: See if we can pass ctx to the current method
      ctx, cancel := context.WithCancel(context.Background())
      go func() {
         select {
         case <-hookContext.StopCh:
            cancel() // stopCh closed, so cancel our context
         case <-ctx.Done():
         }
      }()

/*
启动一些controller来更新CA
 */

      // prime values and start listeners
      if m.ClusterAuthenticationInfo.ClientCA != nil {
         m.ClusterAuthenticationInfo.ClientCA.AddListener(controller)
         if controller, ok := m.ClusterAuthenticationInfo.ClientCA.(dynamiccertificates.ControllerRunner); ok {
            // runonce to be sure that we have a value.
            if err := controller.RunOnce(ctx); err != nil {
               runtime.HandleError(err)
            }
            go controller.Run(ctx, 1)
         }
      }
      if m.ClusterAuthenticationInfo.RequestHeaderCA != nil {
         m.ClusterAuthenticationInfo.RequestHeaderCA.AddListener(controller)
         if controller, ok := m.ClusterAuthenticationInfo.RequestHeaderCA.(dynamiccertificates.ControllerRunner); ok {
            // runonce to be sure that we have a value.
            if err := controller.RunOnce(ctx); err != nil {
               runtime.HandleError(err)
            }
            go controller.Run(ctx, 1)
         }
      }

      go controller.Run(ctx, 1)
      return nil
   })

/*
   续约的操作
 */

   if utilfeature.DefaultFeatureGate.Enabled(apiserverfeatures.APIServerIdentity) {
      m.GenericAPIServer.AddPostStartHookOrDie("start-kube-apiserver-identity-lease-controller", func(hookContext genericapiserver.PostStartHookContext) error {
         kubeClient, err := kubernetes.NewForConfig(hookContext.LoopbackClientConfig)
         if err != nil {
            return err
         }
         controller := lease.NewController(
            clock.RealClock{},
            kubeClient,
            m.GenericAPIServer.APIServerID,
            int32(c.ExtraConfig.IdentityLeaseDurationSeconds),
            nil,
            time.Duration(c.ExtraConfig.IdentityLeaseRenewIntervalSeconds)*time.Second,
            metav1.NamespaceSystem,
            labelAPIServerHeartbeat)
         go controller.Run(hookContext.StopCh)
         return nil
      })
      m.GenericAPIServer.AddPostStartHookOrDie("start-kube-apiserver-identity-lease-garbage-collector", func(hookContext genericapiserver.PostStartHookContext) error {
         kubeClient, err := kubernetes.NewForConfig(hookContext.LoopbackClientConfig)
         if err != nil {
            return err
         }
         go apiserverleasegc.NewAPIServerLeaseGC(
            kubeClient,
            time.Duration(c.ExtraConfig.IdentityLeaseDurationSeconds)*time.Second,
            metav1.NamespaceSystem,
            KubeAPIServerIdentityLeaseLabelSelector,
         ).Run(hookContext.StopCh)
         return nil
      })
   }

   return m, nil
}

流程解析如下图 image.png

到此MasterServer的实例已经创建,剩下就是Run就可以了

Summary

  1. 构建通用配置 image.png
  2. 配置的完善 image.png
  3. Server实例创建 image.png