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。