概述

- 1. 研究背景
- 2. 源码分析
- 2.1 目录结构
- 2.1.1 controller-manager
- 2.1.2 kube-controller-manager
- 2.1.3 pkg/controler
- 2.1 目录结构
-
- 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.1 cobra.Command 分析
- 2.2 kube-controller-manage 如何启动各种controllers
-
-
- 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 返回结果
- 2.3 实现不同controllers差异
- 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
- options.go: 是各种controller options 集合
- 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特性。
最后
以上就是调皮纸飞机为你收集整理的一个controller怎么调用另个controller 带参数_kubernetes kube-controller-manager 源码分析...的全部内容,希望文章能够帮你解决一个controller怎么调用另个controller 带参数_kubernetes kube-controller-manager 源码分析...所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复