分布式系統中服務端會通過心跳機制確認客戶端是否存活,在 k8s 中,kubelet 也會定時上報心跳到 apiserver,以此判斷該 node 是否存活,若 node 超過一定時間沒有上報心跳,其狀態會被置為 NotReady,宿主上容器的狀態也會被置為 Nodelost 或者 Unknown 狀態。kubelet 自身會定期更新狀態到 apiserver,通過參數 --node-status-update-frequency
指定上報頻率,默認是 10s 上報一次,kubelet 不止上報心跳信息還會上報自身的一些數據信息
一、kubelet 上報哪些狀態
在 k8s 中,一個 node 的狀態包含以下幾個信息:
1、Addresses
主要包含以下幾個字段:
- HostName:Hostname 。可以通過 kubelet 的
--hostname-override
參數進行覆蓋。 - ExternalIP:通常是可以外部路由的 node IP 地址(從集群外可訪問)。
- InternalIP:通常是僅可在集群內部路由的 node IP 地址。
2、Condition
conditions
字段描述了所有 Running
nodes 的狀態。
3、Capacity
描述 node 上的可用資源:CPU、內存和可以調度到該 node 上的最大 pod 數量。
4、Info
描述 node 的一些通用信息,例如內核版本、Kubernetes 版本(kubelet 和 kube-proxy 版本)、Docker 版本 (如果使用了)和系統版本,這些信息由 kubelet 從 node 上獲取到。
二、kubelet 狀態異常時的影響
如果一個 node 處於非 Ready 狀態超過 pod-eviction-timeout
的值(默認為 5 分鍾,在 kube-controller-manager 中定義),在 v1.5 之前的版本中 kube-controller-manager 會 force delete pod
然后調度該宿主上的 pods 到其他宿主,在 v1.5 之后的版本中,kube-controller-manager 不會 force delete pod
,pod 會一直處於Terminating
或Unknown
狀態直到 node 被從 master 中刪除或 kubelet 狀態變為 Ready。在 node NotReady 期間,Daemonset 的 Pod 狀態變為 Nodelost,Deployment、Statefulset 和 Static Pod 的狀態先變為 NodeLost,然后馬上變為 Unknown。Deployment 的 pod 會 recreate,Static Pod 和 Statefulset 的 Pod 會一直處於 Unknown 狀態。
當 kubelet 變為 Ready 狀態時,Daemonset的pod不會recreate,舊pod狀態直接變為Running,Deployment的則是將kubelet進程停止的pod刪除,Statefulset的Pod會重新recreate,Staic Pod 會被刪除。
三、kubelet 狀態上報的實現
kubelet 有兩種上報狀態的方式,第一種定期向 apiserver 發送心跳消息,簡單理解就是啟動一個 goroutine 然后定期向 APIServer 發送消息。
第二中被稱為 NodeLease,在 v1.13 之前的版本中,節點的心跳只有 NodeStatus,從 v1.13 開始,NodeLease feature 作為 alpha 特性引入。當啟用 NodeLease feature 時,每個節點在“kube-node-lease”名稱空間中都有一個關聯的“Lease”對象,該對象由節點定期更新,NodeStatus 和 NodeLease 都被視為來自節點的心跳。NodeLease 不會頻繁更新,而只有在 NodeStatus 發生改變或者超過了一定時間(默認值為1分鍾,node-monitor-grace-period 的默認值為 40s),才會將 NodeStatus 上報給 master。由於 NodeLease 比 NodeStatus 更輕量級,該特性在集群規模擴展性和性能上有明顯提升。本文主要分析第一種上報方式的實現。
kubelet 上報狀態的代碼大部分在 kubernetes/pkg/kubelet/kubelet_node_status.go
中實現。狀態上報的功能是在 kubernetes/pkg/kubelet/kubelet.go#Run
方法以 goroutine 形式中啟動的,kubelet 中多個重要的功能都是在該方法中啟動的。
kl.syncNodeStatus 便是上報狀態的,此處 kl.nodeStatusUpdateFrequency 使用的是默認設置的 10s,也就是說節點間同步狀態的函數 kl.syncNodeStatus 每 10s 執行一次。
syncNodeStatus 是狀態上報的入口函數,其后所調用的多個函數也都是在同一個文件中實現的
syncNodeStatus 調用 updateNodeStatus, 然后又調用 tryUpdateNodeStatus 來進行上報操作,而最終調用的是 setNodeStatus。這里還進行了同步狀態判斷,如果是注冊節點,則執行 registerWithAPIServer,否則,執行 updateNodeStatus。
updateNodeStatus 主要是調用 tryUpdateNodeStatus 進行后續的操作,該函數中定義了狀態上報重試的次數,nodeStatusUpdateRetry 默認定義為 5 次。
tryUpdateNodeStatus 中調用 setNodeStatus 設置 node 的狀態。setNodeStatus 會獲取一次 node 的所有狀態,然后會將 kubelet 中保存的所有狀態改為最新的值,也就是會重置 node status 中的所有字段。
setNodeStatus 通過 setNodeStatusFuncs 方法覆蓋 node 結構體中所有的字段,setNodeStatusFuncs 是在
NewMainKubelet(pkg/kubelet/kubelet.go) 中初始化的。
defaultNodeStatusFuncs 是生成狀態的函數,通過獲取 node 的所有狀態指標后使用工廠函數生成狀態
defaultNodeStatusFuncs 可以看到 node 上報的所有信息,主要有 MemoryPressureCondition、DiskPressureCondition、PIDPressureCondition、ReadyCondition 等。每一種 nodestatus 都返回一個 setters,所有 setters 的定義在 pkg/kubelet/nodestatus/setters.go 文件中。
對於二次開發而言,如果我們需要 APIServer 掌握更多的 Node 信息,可以在此處添加自定義函數,例如,上報磁盤信息等。
tryUpdateNodeStatus 中最后調用 PatchNodeStatus 上報 node 的狀態到 master
在 PatchNodeStatus 會調用已注冊的那些方法將狀態把狀態發給 APIServer。
四、總結
本文主要講述了 kubelet 上報狀態的方式及其實現,node 狀態上報的方式目前有兩種,本文僅分析了第一種狀態上報的方式。在大規模集群中由於節點數量比較多,所有 node 都頻繁報狀態對 etcd 會有一定的壓力,當 node 與 master 通信時由於網絡導致心跳上報失敗也會影響 node 的狀態,為了避免類似問題的出現才有 NodeLease 方式,對於該功能的實現后文會繼續進行分析。