1. 程式人生 > >Kubernetes原始碼閱讀筆記——Controller Manager(之一)

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。