Kubernetes源码阅读笔记——Controller Manager(之一)

Controller Manager是Kubernetes的核心组件之一。我们知道,Kubernetes对集群的管理采用的是控制器模式,即针对各种资源运行多个controller(控制器)。控制器的逻辑是运行永不结束的循环,通过apiserver组件时刻获取集群某种资源的状态,并确保资源的当前状态与期望的状态相符合。

下面我们就来通过阅读源码,看一下Kubernetes中Controller Manager的具体实现。

Kubernetes中与Controller Manager相关的包有2个,分别是cmd/cotroller-manager和cmd/kube-controller-manager(暂时不明白为什么要分成两部分)。

启动函数是kube-controller-manager下的controller-manager.go。下面我们先从启动函数入手。

一、启动函数controller-manager.go

启动函数很短:

func main() {
    rand.Seed(time.Now().UnixNano())

    command := app.NewControllerManagerCommand()

    // TODO: once we switch everything over to Cobra commands, we can go back to calling
    // utilflag.InitFlags() (by removing its pflag.Parse() call). For now, we have to set the
    // normalize func and add the go flag set by hand.
    // utilflag.InitFlags()
    logs.InitLogs()
    defer logs.FlushLogs()

    if err := command.Execute(); err != nil {
	fmt.Fprintf(os.Stderr, "%v\n", err)
	os.Exit(1)
    }
}

其中,第一行是生成随机数的代码,log相关的两行是处理日志,最核心的内容在于command。

进入NewControllerManagerCommand方法,我们发现这一方法位于cmd/kube-controller-manager/app/controllermanager.go中。这个方法的返回值是一个cobra.Command类型的指针。

这里稍微提一下cobra。cobra是一个开源项目,用于在命令行中注册新命令。可参考https://github.com/spf13/cobra。cobra的基本结构就是注册一个cobra.Command类型的指针,然后调用Execute命令执行。可以看到main函数就遵循了这样的结构。

二、NewControllerManagerCommand()

cobra.Command是一个结构体,我们看到NewControllerManagerCommand方法里定义了最核心的Use、Long、Run三个字段:

cmd/kube-controller-manager/app/controllermanager.go

func NewControllerManagerCommand(){
    cmd := &cobra.Command{
        Use: "kube-controller-manager"
        Long: `...`
        Run: func(cmd *cobra.Command, args []string) {
            verflag.PrintAndExitIfRequested()
	    utilflag.PrintFlags(cmd.Flags())

	    c, err := s.Config(KnownControllers(),ControllersDisabledByDefault.List())
	    if err != nil {
	        fmt.Fprintf(os.Stderr, "%v\n", err)
	        os.Exit(1)
	    }

	    if err := Run(c.Complete(), wait.NeverStop); err != nil {
	        fmt.Fprintf(os.Stderr, "%v\n", err)
	        os.Exit(1)
	    }
        },
    }
}

Use是命令本身,即在命令行中输入kube-controller-manager,即可运行。Long是对命令的详细说明,而Run则是命令的具体执行内容,也是核心。

Run后面有一段,是为kube-controller-manager命令配置flag的。

我们来仔细解读一下Run。

前两行PrintAndExitIfRequested和PrintFlags是处理打印版本和可用flag的,不重要。重点在后面的Config方法和Run方法。

在创建cmd之前,NewControllerManagerCommand方法其实还有一行代码:

cmd/kube-controller-manager/app/controllermanager.go

func NewControllerManagerCommand(){
    s, err := options.NewKubeControllerManagerOptions()
}

进入这个NewKubeControllerManagerOptions方法看,发现方法位于cmd/kube-controller-manager/app/options/options.go内,作用是创建了一个使用默认配置的KubeControllerManagerOptions结构体,包含了DeploymentController、ReplicationController等多个controller的配置。

利用这里创建的KubeControllerManagerOptions结构体,在Command的Run字段中执行了Config和Run两个操作。

Config方法是配置集群的kubeconfig等基础配置,第一个参数KnownControllers()值得关注。

KnownControllers是同一个go文件下的方法,作用是将NewControllerInitializers方法中返回的Map的键生成一个list。

进入同文件下的NewControllerInitializers方法,我们发现:

cmd/kube-controller-manager/app/controllermanager.go

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
    ...
    ...
    return controllers
}

这一方法,将controller-manager中的所有controller都注册了进来。每个controller都以名字为键,启动函数为值,存储在Map中。因此可以说,这个NewControllerInitializers方法维护了controller-manager的元数据,是controller-manager的重要方法之一。

将这些controller加载上配置后,就是下面核心的Run方法了。

三、Run()

Run方法也在cmd/kube-controller-manager/app/controllermanager.go中,接收2个参数。第一个参数调用Config的Complete方法,对config再进行一次包装,第二个参数是一个单向channel,用于使方法阻塞,从而保持运行状态。

cmd/kube-controller-manager/app/controllermanager.go

func Run(c *config.CompletedConfig, stopCh <-chan struct{}) error { // To help debugging, immediately log version ...
// Setup any healthz checks we will want to use. ...
// Start the controller manager HTTP server // unsecuredMux is the handler for these controller *after* authn/authz filters have been applied ...

   run := func(ctx context.Context) {
rootClientBuilder := controller.SimpleControllerClientBuilder{
ClientConfig: c.Kubeconfig,
}
var clientBuilder controller.ControllerClientBuilder
if c.ComponentConfig.KubeCloudShared.UseServiceAccountCredentials {
if len(c.ComponentConfig.SAController.ServiceAccountKeyFile) == 0 {
// It'c possible another controller process is creating the tokens for us.
// If one isn't, we'll timeout and exit when our client builder is unable to create the tokens.
klog.Warningf("--use-service-account-credentials was specified without providing a --service-account-private-key-file")
}
clientBuilder = controller.SAControllerClientBuilder{
ClientConfig: restclient.AnonymousClientConfig(c.Kubeconfig),
CoreClient: c.Client.CoreV1(),
AuthenticationClient: c.Client.AuthenticationV1(),
Namespace: "kube-system",
}
} else {
clientBuilder = rootClientBuilder
}
controllerContext, err := CreateControllerContext(c, rootClientBuilder, clientBuilder, ctx.Done())
if err != nil {
klog.Fatalf("error building controller context: %v", err)
}
saTokenControllerInitFunc := serviceAccountTokenControllerStarter{rootClientBuilder: rootClientBuilder}.startServiceAccountTokenController

if err := StartControllers(controllerContext, saTokenControllerInitFunc, NewControllerInitializers(controllerContext.LoopMode), unsecuredMux); err != nil {
klog.Fatalf("error starting controllers: %v", err)
}

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

select {}
}

    if !c.ComponentConfig.Generic.LeaderElection.LeaderElect {
run(context.TODO())
panic("unreachable")
}
}

前面几段分别是处理日志、健康检查、以及启动HTTP server,不提。重点在后面的run。

run中,前面一大段都在为controller的运行准备环境。直到StartControllers开始正式运行controller。

StartControllers方法位于同一文件中,执行逻辑很直观,就是将之前保存在NewControllerInitializers中的controller全部运行起来(除了特殊的ServiceAccountTokenController,它在前面的环境准备中先运行起来),方法是分别调用这些controller的启动函数。关于它们的启动函数,将在下一篇文章中分析。

这样,controller-manager模块就算是正式启动了。

run的后面还有一段,是处理高可用controller-manager的节点选举相关的,暂时不提。

四、Informer

Informer是Kubernetes中一个重要的概念。它本质上是一个监听机制,能够使得controller及时监听到资源发生的变化。

每个controller都会启动自己的informer。Run方法最后controllerContext.InformerFactory.Start(controllerContext.Stop)这一行代码就是将controller的准备环境创建的InformerFactory启动起来。

关于Informer,下一篇文章会再详细分析。

五、总结

总而言之,Controller Manager的大致逻辑就是,通过cobra创建一个kube-controller-manager命令,并运行它。这个命令的内容是启动Controller Manager。这个Controller Manager管理Kubernetes中所有的controller,在manager启动时,会调用这些controller的启动函数,启动这些controller。

猜你喜欢

转载自www.cnblogs.com/00986014w/p/10251844.html