controller调用controller的方法_kubernetes kube-controller-manager 源码分析

046e79fe3368aacb940239add73d852b.png
  • 1. 研究背景
  • 2. 源码分析
    • 2.1 目录结构
      • 2.1.1 controller-manager
      • 2.1.2 kube-controller-manager
      • 2.1.3 pkg/controler
    • 2.2 kube-controller-manage 如何启动各种controllers
      • 2.2.1 cobra.Command 分析
        • 2.2.1.1 main 分析
        • 2.2.1.2 NewControllerManagerCommand
        • 2.2.1.3 command.Execute()
      • 2.2.2 cobra.Command.Run 分析
      • 2.2.3 StartControllers 分析
      • 2.2.3 startJobController
    • 2.3 实现不同controllers差异
      • 2.3.1 参数差异
      • 2.3.2 参数校验
      • 2.3.3 controller 返回结果
  • 3. 架构分析
    • 3.1 独立kube-contrller
    • 3.2 config 和 option独立
    • 3.3 将informer作为参数传递给 NewControllerStruct
    • 3.4 使用Cobra

1. 研究背景

kubernetes版本:release-1.15

分析kube-controller-manager源码出于以下问题:

  • 如何start所有controllers
  • start controllers如何体现各自差异,比如传递参数,校验,返回结果处理等
  • 体会架构的特点
    • 为什么独立kube-controller结构
    • config 和option包含信息大致一样,为什么要分开
    • 为什么将informer 作为参数传入各自controller

2. 源码分析

在描述源码分析时,首先会列出重点目录结构及每块主要作用,然后分别对研究背景中提出的三个问题进行分析。

2.1 目录结构

在分析源码中,主要涉及的二级目录是:

  • cmd/controller-manager
  • cmd/kube-controller-manager
  • pkg/controler

2.1.1 controller-manager

controler-manager没有主干逻辑,主要是辅助kube-controller-manager模块,将Kubernetes controller 非核心模块抽象出来,从而精简了kube-controller-manager逻辑。当然controler-manager也被其它模块调用。

实现的核心功能有:

  • Profiling
  • Prometheus Exporter
  • 非核心模块的options,部分shared with kube-controller-manager
  • some help funcs

controller-manager 目录结构如下:

  • serve.go: Profiling,Prometheus Exporter
  • helper.go: some help funcs
  • options: 非核心模块的options
cmd/controller-manager/
├── OWNERS
└── app
    ├── BUILD
    ├── helper.go
    ├── helper_test.go
    ├── options
    │   ├── BUILD
    │   ├── cloudprovider.go
    │   ├── debugging.go
    │   ├── generic.go
    │   ├── kubecloudshared.go
    │   └── servicecontroller.go
    └── serve.go

2.1.2 kube-controller-manager

kube-controller-manager实现启动所有controllers的主干逻辑。

kube-controller-manager 目录结构如下:

  • controller-manager.go: main启动入口,使用了cobra.Command
  • app/config: 是controller manager 的 main context object
    • 各种controller关心的参数,和pkg/controlelr/apis/config对应
    • kubeconfig(kubeconfig 衍生出ClientSet,LeaderElectionClient)
    • ClientSet
    • LeaderElectionClient
    • 安全相关组件配置文件
    • EventRecorder
  • app/options: 各种controller关心的参数,通过命令行参数获取,然后赋值给app/config
    • 每个controller对应一个options文件
    • 每个controller option 文件实现func一致
      • AddFlags 获取cmd 参数
      • ApplyTo将options参数赋值给pkg/controlelr/apis/config 参数
      • Validate 对参数进行校验,目前除了GenericController,其它controller Validate都是空
        • GenericController 校验内容是:k8s controllers 采用白名单机制运行,校验判断运行的controller是否合法
    • options.go: 是各种controller options 集合
      • 实现AddFlags、ApplyTo、Validate
      • Config(): 将options 赋值给app/config
  • app/*.go
    • 每个文件包含具体的controller run 入口。文件命令式根据api路径来的,比如batch.go 包含startJobController、startCronJobController
    • plugins.go: 获取各种Cloud providers插件,主要是针对volume plugins。
    • controllermanager.go: 是new cobra.Command 入口,包含controller run运行逻辑,是最重要的部分。
      • NewControllerManagerCommand():new cobra.Command
      • Run():start controllers具体运行逻辑
      • ControllerContext:context object for controller。包含controller-manager运行的所有内容,一般作为参数
      • KnownControllers():获取白名单中所有的controllers
      • StartControllers: 被Run() 调用
cmd/kube-controller-manager/
├── BUILD
├── OWNERS
├── app
│   ├── BUILD
│   ├── apps.go
│   ├── autoscaling.go
│   ├── batch.go
│   ├── bootstrap.go
│   ├── certificates.go
│   ├── cloudproviders.go
│   ├── config
│   │   ├── BUILD
│   │   └── config.go
│   ├── controllermanager.go
│   ├── core.go
│   ├── core_test.go
│   ├── import_known_versions.go
│   ├── options
│   │   ├── BUILD
│   │   ├── attachdetachcontroller.go
│   │   ├── csrsigningcontroller.go
│   │   ├── daemonsetcontroller.go
│   │   ├── deploymentcontroller.go
│   │   ├── deprecatedcontroller.go
│   │   ├── endpointcontroller.go
│   │   ├── garbagecollectorcontroller.go
│   │   ├── hpacontroller.go
│   │   ├── jobcontroller.go
│   │   ├── namespacecontroller.go
│   │   ├── nodeipamcontroller.go
│   │   ├── nodelifecyclecontroller.go
│   │   ├── options.go
│   │   ├── options_test.go
│   │   ├── persistentvolumebindercontroller.go
│   │   ├── podgccontroller.go
│   │   ├── replicasetcontroller.go
│   │   ├── replicationcontroller.go
│   │   ├── resourcequotacontroller.go
│   │   ├── serviceaccountcontroller.go
│   │   └── ttlafterfinishedcontroller.go
│   ├── plugins.go
│   ├── policy.go
│   ├── rbac.go
│   └── testing
│       ├── BUILD
│       └── testserver.go
└── controller-manager.go

2.1.3 pkg/controler

pkg/controller 是每个controller实现的具体逻辑。

pkg/controler目录结构如下:

  • pkg/controller/apis: 定义每个controller启动的参数
  • 其它每个文件夹实现各自controller 逻辑
wanlei$ tree pkg/controller/ -L 1
pkg/controller/
├── BUILD
├── OWNERS
├── apis
├── bootstrap
├── certificates
├── client_builder.go
├── client_builder_dynamic.go
├── cloud
├── clusterroleaggregation
├── controller_ref_manager.go
├── controller_ref_manager_test.go
├── controller_utils.go
├── controller_utils_test.go
├── cronjob
├── daemon
├── deployment
├── disruption
├── doc.go
├── endpoint
├── garbagecollector
├── history
├── informer_factory.go
├── job
├── lookup_cache.go
├── namespace
├── nodeipam
├── nodelifecycle
├── podautoscaler
├── podgc
├── replicaset
├── replication
├── resourcequota
├── route
├── service
├── serviceaccount
├── statefulset
├── testutil
├── ttl
├── ttlafterfinished
├── util
└── volume

2.2 kube-controller-manage 如何启动各种controllers

kube-controller-manager使用cobra.Command工具作为启动入口,cobra.Command可以自定义Run function(实现run controllers 逻辑),当执行command.Execute()时会调用前面的Run func。

在实现启动controllers时,kube-controller-manager使用白名单机制,所有的启动controllers都事先通过map[string]ControllerRunFunc 预先定义好。

2.2.1 cobra.Command 分析

kube-controller-manager 借助 cobra.Command工具,理解kube-controller-manager前提是清楚cobra.Command 运行逻辑。

2.2.1.1 main 分析

mian func 主要包含两部分:

  • NewControllerManagerCommand():new cobra.Command,其中cobra.Command.Run自定义实现启动controllers 逻辑
  • command.Execute(): 执行cobra.Command.Run
func main() {
    ...
    command := app.NewControllerManagerCommand()
    ...
    if err := command.Execute(); err != nil {
        fmt.Fprintf(os.Stderr, "%vn", err)
        os.Exit(1)
    }
}

2.2.1.2 NewControllerManagerCommand

NewControllerManagerCommand 主要是new cobra.Command。主干逻辑如下:

  • new options.NewKubeControllerManagerOptions
  • new cobra.Command
    • s.Config 将option 赋值给config
    • Run(c.Complete(), wait.NeverStop) 实现了Run controllers具体逻辑。将来被command.Execute()调用
  • s.Flags 解析cmd参数
// NewControllerManagerCommand creates a *cobra.Command object with default parameters
func NewControllerManagerCommand() *cobra.Command {
    s, err := options.NewKubeControllerManagerOptions()
    ...
    cmd := &cobra.Command{
        Use: "kube-controller-manager",
        Long: `The Kubernetes controller manager ....`,
        Run: func(cmd *cobra.Command, args []string) {
            ...
            c, err := s.Config(KnownControllers(), ControllersDisabledByDefault.List())
            if err != nil {
                fmt.Fprintf(os.Stderr, "%vn", err)
                os.Exit(1)
            }

            if err := Run(c.Complete(), wait.NeverStop); err != nil {
                fmt.Fprintf(os.Stderr, "%vn", err)
                os.Exit(1)
            }
        },
    }
    ...
    namedFlagSets := s.Flags(KnownControllers(), ControllersDisabledByDefault.List())
    ...
    return cmd
}

2.2.1.3 command.Execute()

command.Execute() 是cobra.Command实现的,kube-controller-mananger只是调用,实现了cobra.Command逻辑。

  • ExecuteC() 总是先执行父cobra.Command,然后返回子cobra.Command
  • Execute() 并没有递归执行cobra.Command,因此kube-controller-manage必须是root cobra.Command
  • cmd.execute(flags) 是每个cobra.Command 执行逻辑
func (c *Command) Execute() error {
    _, err := c.ExecuteC()
    return err
}
func (c *Command) ExecuteC() (cmd *Command, err error) {
    // Regardless of what command execute is called on, run on Root only
    if c.HasParent() {
        return c.Root().ExecuteC()
    }
    ...
    if c.TraverseChildren {
        cmd, flags, err = c.Traverse(args)
    } else {
        cmd, flags, err = c.Find(args)
    }
    ...
    err = cmd.execute(flags)
    ...
    return cmd, err
}

cmd.execute(flags):是当前cobra.Command 执行逻辑。执行逻辑有点像自动化测试执行过程,先PreRun,然后Run,最后PostRun。在执行PreRun和PostRun顺便执行Parents的PreRun和PostRun。

  • c.Run(c, argWoFlags) 就是我们NewControllerManagerCommand 定义的Run func
func (c *Command) execute(a []string) (err error) {
    ...
    for p := c; p != nil; p = p.Parent() {
        if p.PersistentPreRunE != nil {
            ...
        } else if p.PersistentPreRun != nil {
            ...
        }
    }
    if c.PreRunE != nil {
        ...
    } else if c.PreRun != nil {
        ...
    }
    ..
    if c.RunE != nil {
        ...
    } else {
        c.Run(c, argWoFlags)
    }
    if c.PostRunE != nil {
        ...
    } else if c.PostRun != nil {
        ...
    }
    for p := c; p != nil; p = p.Parent() {
        if p.PersistentPostRunE != nil {
            ...
        } else if p.PersistentPostRun != nil {
            ...
        }
    }
    return nil
}

2.2.2 cobra.Command.Run 分析

cobra.Command.Run 主要做了两件事情

  • Start the controller manager HTTP server,启动Profiling和Prometheus Exporter
  • 单点或者多点执行定义的run fuc(具体实现启动run controllers)
// Run runs the KubeControllerManagerOptions.  This should never exit.
func Run(c *config.CompletedConfig, stopCh <-chan struct{}) error {
    ....
    // Setup any healthz checks we will want to use.
    var checks []healthz.HealthzChecker
    var electionChecker *leaderelection.HealthzAdaptor
    if c.ComponentConfig.Generic.LeaderElection.LeaderElect {
        electionChecker = leaderelection.NewLeaderHealthzAdaptor(time.Second * 20)
        checks = append(checks, electionChecker)
    }

    // Start the controller manager HTTP server
    // unsecuredMux is the handler for these controller *after* authn/authz filters have been applied
    var unsecuredMux *mux.PathRecorderMux
    if c.SecureServing != nil {
        unsecuredMux = genericcontrollermanager.NewBaseHandler(&c.ComponentConfig.Generic.Debugging, checks...)
        handler := genericcontrollermanager.BuildHandlerChain(unsecuredMux, &c.Authorization, &c.Authentication)
        // TODO: handle stoppedCh returned by c.SecureServing.Serve
        if _, err := c.SecureServing.Serve(handler, 0, stopCh); err != nil {
            return err
        }
    }
    if c.InsecureServing != nil {
        unsecuredMux = genericcontrollermanager.NewBaseHandler(&c.ComponentConfig.Generic.Debugging, checks...)
        insecureSuperuserAuthn := server.AuthenticationInfo{Authenticator: &server.InsecureSuperuser{}}
        handler := genericcontrollermanager.BuildHandlerChain(unsecuredMux, nil, &insecureSuperuserAuthn)
        if err := c.InsecureServing.Serve(handler, 0, stopCh); err != nil {
            return err
        }
    }

    run := func(ctx context.Context) {
        ...
    }

    if !c.ComponentConfig.Generic.LeaderElection.LeaderElect {
        run(context.TODO())
        panic("unreachable")
    }
    ....
    leaderelection.RunOrDie(context.TODO(), leaderelection.LeaderElectionConfig{
        Lock:          rl,
        LeaseDuration: c.ComponentConfig.Generic.LeaderElection.LeaseDuration.Duration,
        RenewDeadline: c.ComponentConfig.Generic.LeaderElection.RenewDeadline.Duration,
        RetryPeriod:   c.ComponentConfig.Generic.LeaderElection.RetryPeriod.Duration,
        Callbacks: leaderelection.LeaderCallbacks{
            OnStartedLeading: run,
            OnStoppedLeading: func() {
                klog.Fatalf("leaderelection lost")
            },
        },
        WatchDog: electionChecker,
        Name:     "kube-controller-manager",
    })
    panic("unreachable")
}

run func:

  • 首先CreateControllerContext
  • 然后执行StartControllers
    • NewControllerInitializers() 获取白名单controllers map作为参数
  • 最后start InformerFactory和GenericInformerFactory
    • InformerFactory:用于常规 API 资源对象的控制器
    • GenericInformerFactory:用于 garbagecollector 和 resourcequota 控制器
run := func(ctx context.Context) {
        ....
        controllerContext, err := CreateControllerContext(c, rootClientBuilder, clientBuilder, ctx.Done())
        ...
        if err := StartControllers(controllerContext, saTokenControllerInitFunc, NewControllerInitializers(controllerContext.LoopMode), unsecuredMux); err != nil {
            klog.Fatalf("error starting controllers: %v", err)
        }

        controllerContext.InformerFactory.Start(controllerContext.Stop)
        controllerContext.GenericInformerFactory.Start(controllerContext.Stop)
        close(controllerContext.InformersStarted)

        select {}
    }

2.2.3 StartControllers 分析

for循环启动Controller

  • controllers: 参数是startController map
// StartControllers starts a set of controllers with a specified ControllerContext
func StartControllers(ctx ControllerContext, startSATokenController InitFunc, controllers map[string]InitFunc, unsecuredMux *mux.PathRecorderMux) error {
    ...
    for controllerName, initFn := range controllers {
        if !ctx.IsControllerEnabled(controllerName) {
            klog.Warningf("%q is disabled", controllerName)
            continue
        }

        time.Sleep(wait.Jitter(ctx.ComponentConfig.Generic.ControllerStartInterval.Duration, ControllerStartJitter))

        klog.V(1).Infof("Starting %q", controllerName)
        debugHandler, started, err := initFn(ctx)
        if err != nil {
            klog.Errorf("Error starting %q", controllerName)
            return err
        }
        if !started {
            klog.Warningf("Skipping %q", controllerName)
            continue
        }
        if debugHandler != nil && unsecuredMux != nil {
            basePath := "/debug/controllers/" + controllerName
            unsecuredMux.UnlistedHandle(basePath, http.StripPrefix(basePath, debugHandler))
            unsecuredMux.UnlistedHandlePrefix(basePath+"/", http.StripPrefix(basePath, debugHandler))
        }
        klog.Infof("Started %q", controllerName)
    }

    return nil
}

// NewControllerInitializers is a public map of named controller groups (you can start more than one in an init func)
// paired to their InitFunc.  This allows for structured downstream composition and subdivision.
func NewControllerInitializers(loopMode ControllerLoopMode) map[string]InitFunc {
    controllers := map[string]InitFunc{}
    controllers["endpoint"] = startEndpointController
    controllers["replicationcontroller"] = startReplicationController
    controllers["podgc"] = startPodGCController
    controllers["resourcequota"] = startResourceQuotaController
    controllers["namespace"] = startNamespaceController
    controllers["serviceaccount"] = startServiceAccountController
    controllers["garbagecollector"] = startGarbageCollectorController
    controllers["daemonset"] = startDaemonSetController
    controllers["job"] = startJobController
    controllers["deployment"] = startDeploymentController
    controllers["replicaset"] = startReplicaSetController
    controllers["horizontalpodautoscaling"] = startHPAController
    controllers["disruption"] = startDisruptionController
    controllers["statefulset"] = startStatefulSetController
    controllers["cronjob"] = startCronJobController
    controllers["csrsigning"] = startCSRSigningController
    controllers["csrapproving"] = startCSRApprovingController
    controllers["csrcleaner"] = startCSRCleanerController
    controllers["ttl"] = startTTLController
    controllers["bootstrapsigner"] = startBootstrapSignerController
    controllers["tokencleaner"] = startTokenCleanerController
    controllers["nodeipam"] = startNodeIpamController
    controllers["nodelifecycle"] = startNodeLifecycleController
    if loopMode == IncludeCloudLoops {
        controllers["service"] = startServiceController
        controllers["route"] = startRouteController
        controllers["cloud-node-lifecycle"] = startCloudNodeLifecycleController
        // TODO: volume controller into the IncludeCloudLoops only set.
    }
    controllers["persistentvolume-binder"] = startPersistentVolumeBinderController
    controllers["attachdetach"] = startAttachDetachController
    controllers["persistentvolume-expander"] = startVolumeExpandController
    controllers["clusterrole-aggregation"] = startClusterRoleAggregrationController
    controllers["pvc-protection"] = startPVCProtectionController
    controllers["pv-protection"] = startPVProtectionController
    controllers["ttl-after-finished"] = startTTLAfterFinishedController
    controllers["root-ca-cert-publisher"] = startRootCACertPublisher

    return controllers
}

2.2.3 startJobController

所有startController func都会被StartControllers调用,并且函数实现基本一致。
我们以startJobController为例,先校验Job资源是否存在,然后NewJobController,最后执行JobController Run func.

  • NewJobController() 参数是jobcontroller 需要的informers和kubeClient
  • Run() 参数是之前Config定义的各种参数,从而体现不同Controller差异性
    后面的过程是我们熟悉的单个Controller逻辑,不做表述。至此,启动不同Controller 逻辑分析结束。
func startJobController(ctx ControllerContext) (http.Handler, bool, error) {
    if !ctx.AvailableResources[schema.GroupVersionResource{Group: "batch", Version: "v1", Resource: "jobs"}] {
        return nil, false, nil
    }
    go job.NewJobController(
        ctx.InformerFactory.Core().V1().Pods(),
        ctx.InformerFactory.Batch().V1().Jobs(),
        ctx.ClientBuilder.ClientOrDie("job-controller"),
    ).Run(int(ctx.ComponentConfig.JobController.ConcurrentJobSyncs), ctx.Stop)
    return nil, true, nil
}

2.3 实现不同controllers差异

每个Controller实现的功能不同,必然要在实现过程中考虑到不同Controller的差异和共同点。

  • 共同点:
    • 本质上所有的控制器都是Controller,机制基本上是一致的,不同的是具体实现的策略。因此在定义Controller的Struct时,我们很多情况都能看到共通的地方。
    • Controller都需要的参数我们都可以抽象出来,比如ClientSet(或者kubeconfig)
  • 差异点:
    • 由于每个Controller实现的策略不同,需要的参数也不尽相同

2.3.1 参数差异

在实现不同Controller赋值不同参数时,kube-controller-manager 实现方式还是很讲究的。在分析代码时,起初对options和config 两部分的定位非常疑惑,因为两者有太多重复的地方,kube-controller-manage 使用了相当多的代码对option赋值给config,合并为一个struct是否更加合理?

分析之后,认为kube-controller-manage 这么做采用的是机制和策略分离原则。options参数主要是面向cmd.Flag的,而config参数只要是被controller调用。并且option和config参数并非是完全对应,不同的场景下,相同的option可能会有不同的config值。

  • option 赋值参考AddFlags逻辑
  • config 赋值参考ApplyTo逻辑

2.3.2 参数校验

kube-controller-manage主要是对options参数做校验,不同controller参数校验的规则可以不同。

  • 参考Validate逻辑

2.3.3 controller 返回结果

在controller-manage 中介绍了profiling和Prometheus Exporter被调用(http形式)。不同的controller暴露的http 路径和处理结果是不同的,如何实现这些差异。

  • 每个startController 返回结果都是固定的(http.Handler, bool, error),分别对应(controller-manager定义的hander,started,err)
  • StartControllers 根据每个startController返回结果进行处理
// StartControllers starts a set of controllers with a specified ControllerContext
func StartControllers(ctx ControllerContext, startSATokenController InitFunc, controllers map[string]InitFunc, unsecuredMux *mux.PathRecorderMux) error {
    ...
    for controllerName, initFn := range controllers {
        ...
        debugHandler, started, err := initFn(ctx)
        if err != nil {
            klog.Errorf("Error starting %q", controllerName)
            return err
        }
        if !started {
            klog.Warningf("Skipping %q", controllerName)
            continue
        }
        if debugHandler != nil && unsecuredMux != nil {
            basePath := "/debug/controllers/" + controllerName
            unsecuredMux.UnlistedHandle(basePath, http.StripPrefix(basePath, debugHandler))
            unsecuredMux.UnlistedHandlePrefix(basePath+"/", http.StripPrefix(basePath, debugHandler))
        }
        klog.Infof("Started %q", controllerName)
    }

    return nil
}

func startJobController(ctx ControllerContext) (http.Handler, bool, error) {
    ...
}

3. 架构分析

3.1 独立kube-contrller

  • controller-manage 定位在help功能,通过打桩的方式在其它包中引用,并非仅仅被kube-controller-manager 引用
    • NewBaseHandler() 被kube-Controller-manage和cloud-controller-manage引用
    • controller-manage 目前发现的代码都是被kube-Controller-manage和cloud-controller-manage引用。取名controller-manage可能也是因为被各种controller-manage共享

3.2 config 和 option独立

  • config 内容比option内容稍微丰富一点,config 主要负责controller启动,option 主要是负责获取cmd参数
  • option 参数赋值给config并非是一一对应,赋值过程中还包含一定的逻辑。机制和策略分开,config 和option负责不同模块,赋值过程中包含一定的逻辑。参考2.3.1描述。

3.3 将informer作为参数传递给 NewControllerStruct

  • 每个controller都是一个独立组件,controller之间通过apiserver(http)交互。controller不需要通过内存共享数据。
  • 不同controller关注不同的informer事件,同时添加各自的handfunc回调函数
  • InformerFactory.Start()可以start 所有k8s自定义的informer,不需要在各自controller 内部start

3.4 使用Cobra

对kube-controller-manage引入Cobra.Command的目的我依旧是带有疑问的。目前并没有看到Cobra.Command本身强大的功能而简化代码,反而提高了理解代码复杂性。

  • Cobra.Command支持父子先后执行Cobra.Command,但是kube-controller-manage 调用的是Cobra.Command.Execute(),该函数理论上要求kube-controller-manage 必须是 root Cobra.Command
  • Cobra.Command 支持PreFun,PostFunc。Kube-controller-manage 在创建Cobra.Command均没有定义
  • Cobra.Command 支持参数解析,但是目前看来options中获取cmd参数依然使用是传统的"http://github.com/spf13/pflag" pflag.Parse()方法

kube-controller-manage(release-1.15)处于一个过渡阶段,未来版本会更好的利用Cobra特性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值