kubelet是k8s叢集中一個元件,其作為一個agent的角色分佈在各個節點上,無論是master還是worker,功能繁多,邏輯複雜。主要功能有

  1. 節點狀態同步:kublet給api-server同步當前節點的狀態,會同步當前節點的CPU,記憶體及磁碟空間等資源到api-server,為scheduler排程pod時提供基礎資料支撐
  2. Pod的啟停及狀態管理:kubelet會啟動經scheduler排程到本節點的pod,同步它的狀態保障它執行,當Pod關閉時負責資源回收

主要模組

kublet有以下幾個監聽埠

  • 10250 kubelet API :用於與api-server通訊,訪問可獲得node的資源和狀態
  • 4194 cAdvisor :用於獲取節點的環境資訊和pod的狀態的指標資訊,
  • 10255 readonly API :以只讀形式獲取Pod和node的資訊
  • 10248 /healthz :用於健康檢查

kubelet包含的模組有以下

PLEG,cAdvisor,Container Manager,Volume Manager,Eviction Manager,OOMWatcher,ProbeManager,StatusManager,ImageGC,ContainerGC,ImageManager,CertificateManager,runtimeClassManager……如下圖所示

挑幾個介紹一下

  • PLEG(Pod Lifecycle Event Generator):kubelet的核心模組,一直呼叫 container runtime 獲取本節點 containers/sandboxes 的資訊,並與自身維護的 pods cache 資訊進行對比,生成對應的 PodLifecycleEvent,通過 eventChannel 傳送到 kubelet syncLoop 進行消費,然後由 kubelet syncPod 來觸發 pod 同步處理過程,最終達到使用者的期望狀態。
  • cAdvisor :google 開發的容器監控工具,整合在 kubelet 中,起到收集本節點和容器的監控資訊,對外提供了 interface 介面,該介面也被 imageManager,OOMWatcher,containerManager 等所使用。
  • OOMWatcher:系統 OOM 的監聽器,會與 cadvisor 模組之間建立 SystemOOM,通過 Watch方式從 cadvisor 那裡收到的 OOM 訊號,併產生相關事件。
  • statusManager : 負責維護狀態資訊,並把 pod 狀態更新到 apiserver,但是它並不負責監控 pod 狀態的變化,而是提供對應的介面供其他元件呼叫,比如 probeManager。
  • volumeManager : 負責 node 節點上 pod 所使用 volume 的管理,volume 與 pod 的生命週期關聯,負責 pod 建立刪除過程中 volume 的 mount/umount/attach/detach 流程
  • ProbeManager:依賴於 statusManager,livenessManager,containerRefManager,會定時去監控 pod 中容器的健康狀況,當前支援兩種型別的探針:livenessProbe 和readinessProbe。

啟動命令

kubelet是以二進位制執行在各個節點上,通過ps -ef可以看到其啟動命令,一般安裝叢集的時候會讓其託管到system service中,讓節點啟動的時候將kubelet也自動啟動。

進入/usr/lib/systemd/system/kubelet.service.d目錄開啟裡面的檔案檢視

cd /usr/lib/systemd/system/kubelet.service.d
ls
10-kubeadm.conf
cat 10-kubeadm.conf
Environment="KUBELET_KUBECONFIG_ARGS=--bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf"
Environment="KUBELET_CONFIG_ARGS=--config=/var/lib/kubelet/config.yaml"
# This is a file that "kubeadm init" and "kubeadm join" generates at runtime, populating the KUBELET_KUBEADM_ARGS variable dynamically
EnvironmentFile=-/var/lib/kubelet/kubeadm-flags.env
# This is a file that the user can use for overrides of the kubelet args as a last resort. Preferably, the user should use
# the .NodeRegistration.KubeletExtraArgs object in the configuration files instead. KUBELET_EXTRA_ARGS should be sourced from this file.
EnvironmentFile=-/etc/sysconfig/kubelet
ExecStart=
ExecStart=/usr/bin/kubelet $KUBELET_KUBECONFIG_ARGS $KUBELET_CONFIG_ARGS $KUBELET_KUBEADM_ARGS $KUBELET_EXTRA_ARGS

kubelet啟動有四個環境變數拼湊成引數,前兩個環境變數已在1,2行提供。按照註釋KUBELET_CONFIG_ARGS是“kubeadm init”和“kubeadm join”在執行時生成的檔案,檔案的路徑是/var/lib/kubelet/kubeadm-flags.env;最後一個引數KUBELET_EXTRA_ARGS是使用者用於覆蓋kubelet的最後手段,檔案的路徑在/etc/sysconfig/kubelet。按照實驗機器內容如下

cat /var/lib/kubelet/kubeadm-flags.env
KUBELET_KUBEADM_ARGS="--cgroup-driver=systemd --cluster-dns=10.96.0.10 --docker-endpoint=unix:///var/run/docker.sock --hostname-override=master1 --network-plugin=cni --pod-infra-container-image=deploy.deepexi.com/kubeadm/pause:3.2 --root-dir=/var/lib/kubelet --v=4"
cat /etc/sysconfig/kubelet
-bash: cd: /etc/sysconfig/kubelet: No such file or directory

最終組合而成的kubelet的啟動命令是

/usr/bin/kubelet --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf --config=/var/lib/kubelet/config.yaml --cgroup-driver=systemd --cluster-dns=10.96.0.10 --docker-endpoint=unix:///var/run/docker.sock --hostname-override=master1 --network-plugin=cni --pod-infra-container-image=deploy.deepexi.com/kubeadm/pause:3.2 --root-dir=/var/lib/kubelet --v=4

與ps -ef|grep kubelet得到的結果是一致的

ps -ef|grep kubelet
root 1302 1 3 9月08 ? 16:17:27 /usr/bin/kubelet --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf --config=/var/lib/kubelet/config.yaml --cgroup-driver=systemd --cluster-dns=10.96.0.10 --docker-endpoint=unix:///var/run/docker.sock --hostname-override=master1 --network-plugin=cni --pod-infra-container-image=deploy.deepexi.com/kubeadm/pause:3.2 --root-dir=/var/lib/kubelet --v=4

上述引數中還有config引數傳遞的是一個yaml檔案,開啟它發現還有相當一部分的引數,由於篇幅太長則不在這展示

kubelet啟動流程

原始碼版本:1.19

kubelet通過cobra框架來處理啟動命令和啟動引數,從main函式進來直接跳到cobra的Run函式註冊則可,大致做下面幾件事情

  1. 初始化啟動命令和引數
  2. 初始化FeatureGate
  3. 校驗命令列引數
  4. 載入KubeletConfigFile並驗證之,即“啟動命令”一節中提到的config引數傳入的檔案
  5. 載入使用動態配置,如果有啟用
  6. 構造kubeletServer及kubeletDeps
  7. 呼叫Run函式執行kubelet

    程式碼位於/cmd/kubelet/app/server.go
func(cmd *cobra.Command, args []string) {
//1. 初始化啟動命令和引數
if err := cleanFlagSet.Parse(args); err != nil {
}
//2. 初始化FeatureGate
if err := utilfeature.DefaultMutableFeatureGate.SetFromMap(kubeletConfig.FeatureGates); err != nil {
}
//3. 校驗命令列引數
if err := options.ValidateKubeletFlags(kubeletFlags); err != nil {
}
//4. 載入KubeletConfigFile並驗證之
if configFile := kubeletFlags.KubeletConfigFile; len(configFile) > 0 {
kubeletConfig, err = loadConfigFile(configFile)
}
if err := kubeletconfigvalidation.ValidateKubeletConfiguration(kubeletConfig); err != nil {
klog.Fatal(err)
} ////5. 載入使用動態配置的部分略 //6. 構造kubeletServer及kubeletDeps
// construct a KubeletServer from kubeletFlags and kubeletConfig
kubeletServer := &options.KubeletServer{
KubeletFlags: *kubeletFlags,
KubeletConfiguration: *kubeletConfig,
} // use kubeletServer to construct the default KubeletDeps
kubeletDeps, err := UnsecuredDependencies(kubeletServer, utilfeature.DefaultFeatureGate)
if err != nil {
klog.Fatal(err)
} //7. 呼叫Run函式執行kubelet
if err := Run(ctx, kubeletServer, kubeletDeps, utilfeature.DefaultFeatureGate); err != nil {
klog.Fatal(err)
} }

run函式

run函式主要為kubelet啟動做一些環境檢查,準備及校驗操作

  1. 將當前的配置檔案註冊到 http server /configz URL 中
  2. 初始化各種客戶端
  3. 初始化 auth,cgroupRoot,cadvisor,ContainerManager
  4. 為 kubelet 程序設定 oom 分數
  5. 初始化Runtime Server,設定CRI
  6. 呼叫 RunKubelet 方法執行後續的啟動操作
  7. 啟動 Healthz http server

    程式碼位於 /cmd/kubelet/server/server.go
func run(ctx context.Context, s *options.KubeletServer, kubeDeps *kubelet.Dependencies, featureGate featuregate.FeatureGate) (err error) {
//1 將當前的配置檔案註冊到 http server /configz URL 中
err = initConfigz(&s.KubeletConfiguration)
//2 初始化各種客戶端,主要是非standalone模式下會進入這個,否則會將所有客戶端都置為空
switch {
case kubeDeps.KubeClient == nil, kubeDeps.EventClient == nil, kubeDeps.HeartbeatClient == nil:
kubeDeps.KubeClient, err = clientset.NewForConfig(clientConfig)
kubeDeps.EventClient, err = v1core.NewForConfig(&eventClientConfig)
kubeDeps.HeartbeatClient, err = clientset.NewForConfig(&heartbeatClientConfig)
}
//3 初始化 auth,cgroupRoot,cadvisor,ContainerManager
if kubeDeps.Auth == nil {
auth, runAuthenticatorCAReload, err := BuildAuth(nodeName, kubeDeps.KubeClient, s.KubeletConfiguration)
}
nodeAllocatableRoot := cm.NodeAllocatableRoot(s.CgroupRoot, s.CgroupsPerQOS, s.CgroupDriver)
if kubeDeps.CAdvisorInterface == nil {
imageFsInfoProvider := cadvisor.NewImageFsInfoProvider(s.ContainerRuntime, s.RemoteRuntimeEndpoint)
}
if kubeDeps.ContainerManager == nil {
kubeDeps.ContainerManager, err = cm.NewContainerManager(...)
}
//4. 為 kubelet 程序設定 oom 分數
if err := oomAdjuster.ApplyOOMScoreAdj(0, int(s.OOMScoreAdj)); err != nil {
}
//5. 初始化Runtime Server,設定CRI
err = kubelet.PreInitRuntimeService(...)
//6. 呼叫 RunKubelet 方法執行後續的啟動操作
if err := RunKubelet(s, kubeDeps, s.RunOnce); err != nil {
return err
}
//7. 啟動 Healthz http server
if s.HealthzPort > 0 {
go wait.Until(func() {
err := http.ListenAndServe(net.JoinHostPort(s.HealthzBindAddress, strconv.Itoa(int(s.HealthzPort))), mux)
}, 5*time.Second, wait.NeverStop)
}
}

RunKubelet

RunKubelet函式核心就兩個

  1. 初始化kubelet物件
  2. 將kubelet及相關kubelet的api跑起來
func RunKubelet(kubeServer *options.KubeletServer, kubeDeps *kubelet.Dependencies, runOnce bool) error {
k, err := createAndInitKubelet(...)
if runOnce {
}else{
startKubelet(k, podCfg, &kubeServer.KubeletConfiguration, kubeDeps, kubeServer.EnableCAdvisorJSONEndpoints, kubeServer.EnableServer)
}
}
createAndInitKubelet

createAndInitKubelet先構造出kubelet物件,NewMainKubelet的函式傳入的引數也很多,函式裡面包含了前文中“主要模組”的初始化操作。構造完畢後呼叫BirthCry方法往k8s發一個Starting kubelet.的event。然後就馬上啟動containerGC

    func createAndInitKubelet(......) {
k, err = kubelet.NewMainKubelet(...) k.BirthCry()
k.StartGarbageCollection()
return k, nil
}
startKubelet

startKubelet函式是通過呼叫kubelet的Run方法將kubelet跑起來,kubelet.Run包含了一部分“主要模組”中提及的manager的start方法呼叫,意味著kubelet的各個模組從此開始執行起來,此外還包括了kubelet的核心迴圈syncLoop在這裡開始呼叫

運行了kubelet後,kubelet api、readonly API等server也在這裡開始執行

func startKubelet(...) {
// start the kubelet
go k.Run(podCfg.Updates()) // start the kubelet server
if enableServer {
go k.ListenAndServe(net.ParseIP(kubeCfg.Address), uint(kubeCfg.Port), kubeDeps.TLSOptions, kubeDeps.Auth,
enableCAdvisorJSONEndpoints, kubeCfg.EnableDebuggingHandlers, kubeCfg.EnableContentionProfiling, kubeCfg.EnableSystemLogHandler) }
if kubeCfg.ReadOnlyPort > 0 {
go k.ListenAndServeReadOnly(net.ParseIP(kubeCfg.Address), uint(kubeCfg.ReadOnlyPort), enableCAdvisorJSONEndpoints)
}
if utilfeature.DefaultFeatureGate.Enabled(features.KubeletPodResources) {
go k.ListenAndServePodResources()
}
}

至此,kubelet執行起來了,開始執行它節點資源上報,Pod的啟停及狀態管理等工作。

啟動流程的呼叫鏈

通過下面呼叫鏈大致回顧整個啟動流程

main                                                                             // cmd/kubelet/kubelet.go
|--NewKubeletCommand // cmd/kubelet/app/server.go
|--Run // cmd/kubelet/app/server.go
|--initForOS // cmd/kubelet/app/server.go
|--run // cmd/kubelet/app/server.go
|--initConfigz // cmd/kubelet/app/server.go
|--BuildAuth
|--cm.NodeAllocatableRoot
|--cadvisor.NewImageFsInfoProvider
|--NewContainerManager
|--ApplyOOMScoreAdj
|--PreInitRuntimeService
|--RunKubelet // cmd/kubelet/app/server.go
| |--k = createAndInitKubelet // cmd/kubelet/app/server.go
| | |--NewMainKubelet
| | | |--watch k8s Service
| | | |--watch k8s Node
| | | |--klet := &Kubelet{}
| | | |--init klet fields
| | |
| | |--k.BirthCry()
| | |--k.StartGarbageCollection()
| |
| |--startKubelet(k) // cmd/kubelet/app/server.go
| |--go k.Run() // -> pkg/kubelet/kubelet.go
| | |--go cloudResourceSyncManager.Run()
| | |--initializeModules
| | |--go volumeManager.Run()
| | |--go nodeLeaseController.Run()
| | |--initNetworkUtil() // setup iptables
| | |--go Until(PerformPodKillingWork, 1*time.Second, neverStop)
| | |--statusManager.Start()
| | |--runtimeClassManager.Start
| | |--pleg.Start()
| | |--syncLoop(updates, kl) // pkg/kubelet/kubelet.go
| |
| |--k.ListenAndServe
|
|--go http.ListenAndServe(healthz)

參考文章

kubelet 架構淺析

kubelet 啟動流程分析

萬字長文:K8s 建立 pod 時,背後到底發生了什麼?