天天看點

K8S篇之架構節點簡介系列文章目錄前言一、架構概述API 設計原則控制機制設計原則節點節點與控制面之間的通信控制器租約關于 cgroup v2容器運作時接口(CRI)垃圾收集

系列文章目錄

文章目錄

  • 系列文章目錄
  • 前言
  • 一、架構概述
  • API 設計原則
  • 控制機制設計原則
  • 節點
    • 管理
    • 節點名稱唯一性
    • 節點自注冊
    • 手動節點管理
    • 節點狀态
    • 位址
    • 狀況
    • 容量(Capacity)與可配置設定(Allocatable)
    • 資訊(Info)
    • 心跳
    • 節點控制器
    • 逐出速率限制
    • 資源容量跟蹤
    • 節點拓撲
    • 節點體面關閉
    • 基于 Pod 優先級的節點體面關閉
    • 節點非體面關閉
    • 交換記憶體管理
  • 節點與控制面之間的通信
    • 節點到控制面
    • 控制面到節點
    • API 伺服器到節點、Pod 和服務
    • SSH 隧道
    • Konnectivity 服務
  • 控制器
    • 控制器模式
    • **通過 API 伺服器來控制**
    • 直接控制
    • 期望狀态與目前狀态
    • 設計
    • 運作控制器的方式
  • 租約
    • 節點心跳
    • 上司者選舉
    • API 伺服器身份
    • 工作負載
  • 關于 cgroup v2
    • 什麼是 cgroup v2?
    • 使用 cgroup v2
    • 要求
    • Linux 發行版 cgroup v2 支援
    • 遷移到 cgroup v2
    • 識别 Linux 節點上的 cgroup 版本
  • 容器運作時接口(CRI)
    • API
    • 更新
  • 垃圾收集
    • 屬主與依賴
    • 級聯删除
    • 前台級聯删除
    • 背景級聯删除
    • 被遺棄的依賴對象
    • 未使用容器和鏡像的垃圾收集
    • 容器鏡像生命周期
    • 容器垃圾收集

前言

分析和了解 Kubernetes 的設計理念可以使我們更深入地了解 Kubernetes 系統,更好地利用它管理分布式部署的雲原生應用,另一方面也可以讓我們借鑒其在分布式系統設計方面的經驗。

一、架構概述

K8S的整體架構圖如下

K8S篇之架構節點簡介系列文章目錄前言一、架構概述API 設計原則控制機制設計原則節點節點與控制面之間的通信控制器租約關于 cgroup v2容器運作時接口(CRI)垃圾收集

Kubernetes 設計理念和功能其實就是一個類似 Linux 的分層架構,如下圖所示

K8S篇之架構節點簡介系列文章目錄前言一、架構概述API 設計原則控制機制設計原則節點節點與控制面之間的通信控制器租約關于 cgroup v2容器運作時接口(CRI)垃圾收集
  • 核心層:Kubernetes 最核心的功能,對外提供 API 建構高層的應用,對内提供插件式應用執行環境
  • 應用層:部署(無狀态應用、有狀态應用、批處理任務、叢集應用等)和路由(服務發現、DNS 解析等)
  • 管理層:系統度量(如基礎設施、容器和網絡的度量),自動化(如自動擴充、動态 Provision

    等)以及政策管理(RBAC、Quota、PSP、NetworkPolicy 等)

  • 接口層:kubectl 指令行工具、用戶端 SDK 以及叢集聯邦
  • 生态系統:在接口層之上的龐大容器叢集管理排程的生态系統,可以劃分為兩個範疇
  • Kubernetes 外部:日志、監控、配置管理、CI、CD、Workflow、FaaS、OTS 應用、ChatOps 等
  • Kubernetes 内部:CRI、CNI、CVI、鏡像倉庫、Cloud Provider、叢集自身的配置和管理等

API 設計原則

對于雲計算系統,系統 API 實際上處于系統設計的統領地位,正如本文前面所說,Kubernetes 叢集系統每支援一項新功能,引入一項新技術,一定會新引入對應的 API 對象,支援對該功能的管理操作,了解掌握的 API,就好比抓住了 Kubernetes 系統的牛鼻子。Kubernetes 系統 API 的設計有以下幾條原則:

所有 API 應該是聲明式的。

正如前文所說,聲明式的操作,相對于指令式操作,對于重複操作的效果是穩定的,這對于容易出現資料丢失或重複的分布式環境來說是很重要的。另外,聲明式操作更容易被使用者使用,可以使系統向使用者隐藏實作的細節,隐藏實作的細節的同時,也就保留了系統未來持續優化的可能性。此外,聲明式的 API,同時隐含了所有的 API 對象都是名詞性質的,例如 Service、Volume 這些 API 都是名詞,這些名詞描述了使用者所期望得到的一個目标分布式對象。

API 對象是彼此互補而且可組合的。

這裡面實際是鼓勵 API 對象盡量實作面向對象設計時的要求,即 “高内聚,松耦合”,對業務相關的概念有一個合适的分解,提高分解出來的對象的可重用性。事實上,Kubernetes 這種分布式系統管理平台,也是一種業務系統,隻不過它的業務就是排程和管理容器服務。

高層 API 以操作意圖為基礎設計。

如何能夠設計好 API,跟如何能用面向對象的方法設計好應用系統有相通的地方,高層設計一定是從業務出發,而不是過早的從技術實作出發。是以,針對 Kubernetes 的高層 API 設計,一定是以 Kubernetes 的業務為基礎出發,也就是以系統排程管理容器的操作意圖為基礎設計。

低層 API 根據高層 API 的控制需要設計。

設計實作低層 API 的目的,是為了被高層 API 使用,考慮減少備援、提高重用性的目的,低層 API 的設計也要以需求為基礎,要盡量抵抗受技術實作影響的誘惑。

盡量避免簡單封裝,不要有在外部 API 無法顯式知道的内部隐藏的機制。

簡單的封裝,實際沒有提供新的功能,反而增加了對所封裝 API 的依賴性。内部隐藏的機制也是非常不利于系統維護的設計方式,例如 StatefulSet 和 ReplicaSet,本來就是兩種 Pod 集合,那麼 Kubernetes 就用不同 API 對象來定義它們,而不會說隻用同一個 ReplicaSet,内部通過特殊的算法再來區分這個 ReplicaSet 是有狀态的還是無狀态。

API 操作複雜度與對象數量成正比。這一條主要是從系統性能角度考慮,要保證整個系統随着系統規模的擴大,性能不會迅速變慢到無法使用,那麼最低的限定就是 API 的操作複雜度不能超過 O(N),N 是對象的數量,否則系統就不具備水準伸縮性了。

API 對象狀态不能依賴于網絡連接配接狀态。

由于衆所周知,在分布式環境下,網絡連接配接斷開是經常發生的事情,是以要保證 API 對象狀态能應對網絡的不穩定,API 對象的狀态就不能依賴于網絡連接配接狀态。

盡量避免讓操作機制依賴于全局狀态

因為在分布式系統中要保證全局狀态的同步是非常困難的。

控制機制設計原則

控制邏輯應該隻依賴于目前狀态。

這是為了保證分布式系統的穩定可靠,對于經常出現局部錯誤的分布式系統,如果控制邏輯隻依賴目前狀态,那麼就非常容易将一個暫時出現故障的系統恢複到正常狀态,因為你隻要将該系統重置到某個穩定狀态,就可以自信的知道系統的所有控制邏輯會開始按照正常方式運作。

假設任何錯誤的可能,并做容錯處理。

在一個分布式系統中出現局部和臨時錯誤是大機率事件。錯誤可能來自于實體系統故障,外部系統故障也可能來自于系統自身的代碼錯誤,依靠自己實作的代碼不會出錯來保證系統穩定其實也是難以實作的,是以要設計對任何可能錯誤的容錯處理。

盡量避免複雜狀态機,控制邏輯不要依賴無法監控的内部狀态。

因為分布式系統各個子系統都是不能嚴格通過程式内部保持同步的,是以如果兩個子系統的控制邏輯如果互相有影響,那麼子系統就一定要能互相通路到影響控制邏輯的狀态,否則,就等同于系統裡存在不确定的控制邏輯。

假設任何操作都可能被任何操作對象拒絕,甚至被錯誤解析。

由于分布式系統的複雜性以及各子系統的相對獨立性,不同子系統經常來自不同的開發團隊,是以不能奢望任何操作被另一個子系統以正确的方式處理,要保證出現錯誤的時候,操作級别的錯誤不會影響到系統穩定性。

每個子產品都可以在出錯後自動恢複。

由于分布式系統中無法保證系統各個子產品是始終連接配接的,是以每個子產品要有自我修複的能力,保證不會因為連接配接不到其他子產品而自我崩潰。

每個子產品都可以在必要時優雅地降級服務。

所謂優雅地降級服務,是對系統魯棒性的要求,即要求在設計實作子產品時劃厘清楚基本功能和進階功能,保證基本功能不會依賴進階功能,這樣同時就保證了不會因為進階功能出現故障而導緻整個子產品崩潰。根據這種理念實作的系統,也更容易快速地增加新的進階功能,因為不必擔心引入進階功能影響原有的基本功能。

節點

Kubernetes 通過将容器放入在節點(Node)上運作的 Pod 中來執行你的工作負載。 節點可以是一個虛拟機或者實體機器,取決于所在的叢集配置。 每個節點包含運作 Pod 所需的服務; 這些節點由控制面負責管理。

節點上的元件包括 kubelet、 容器運作時以及 kube-proxy。

管理

向 API 伺服器添加節點的方式主要有兩種:

  1. 節點上的 kubelet 向控制面執行自注冊;
  2. 你(或者别的什麼人)手動添加一個 Node 對象。

在你建立了 Node 對象或者節點上的 kubelet 執行了自注冊操作之後,控制面會檢查新的 Node 對象是否合法。 例如,如果你嘗試使用下面的 JSON 對象來建立 Node 對象:

{
  "kind": "Node",
  "apiVersion": "v1",
  "metadata": {
    "name": "10.240.79.157",
    "labels": {
      "name": "my-first-k8s-node"
    }
  }
}
           

Kubernetes 會在内部建立一個 Node 對象作為節點的表示。Kubernetes 檢查 kubelet 向 API 伺服器注冊節點時使用的 metadata.name 字段是否比對。 如果節點是健康的(即所有必要的服務都在運作中),則該節點可以用來運作 Pod。 否則,直到該節點變為健康之前,所有的叢集活動都會忽略該節點。

Kubernetes 會一直儲存着非法節點對應的對象,并持續檢查該節點是否已經變得健康。

你,或者某個控制器必須顯式地删除該 Node 對象以停止健康檢查操作。

Node 對象的名稱必須是合法的 DNS 子域名。

節點名稱唯一性

節點的名稱用來辨別 Node 對象。 沒有兩個 Node 可以同時使用相同的名稱。 Kubernetes 還假定名字相同的資源是同一個對象。 就 Node 而言,隐式假定使用相同名稱的執行個體會具有相同的狀态(例如網絡配置、根磁盤内容) 和類似節點标簽這類屬性。這可能在節點被更改但其名稱未變時導緻系統狀态不一緻。 如果某個 Node 需要被替換或者大量變更,需要從 API 伺服器移除現有的 Node 對象, 之後再在更新之後重新将其加入。

節點自注冊

當 kubelet 标志 --register-node 為 true(預設)時,它會嘗試向 API 服務注冊自己。 這是首選模式,被絕大多數發行版選用。

對于自注冊模式,kubelet 使用下列參數啟動:

  • –kubeconfig - 用于向 API 伺服器執行身份認證所用的憑據的路徑。
  • –cloud-provider - 與某雲驅動 進行通信以讀取與自身相關的中繼資料的方式。
  • –register-node - 自動向 API 服務注冊。
  • –register-with-taints - 使用所給的污點清單 (逗号分隔的 =:)注冊節點。當 register-node 為 false 時無效。
  • –node-ip - 節點 IP 位址。
  • –node-labels - 在叢集中注冊節點時要添加的标簽。 (參見 NodeRestriction 準入控制插件所實施的标簽限制)。
  • –node-status-update-frequency - 指定 kubelet 向 API 伺服器發送其節點狀态的頻率。

當 Node 鑒權模式和 NodeRestriction 準入插件被啟用後, 僅授權 kubelet 建立/修改自己的 Node 資源。

正如節點名稱唯一性一節所述,當 Node 的配置需要被更新時, 一種好的做法是重新向 API 伺服器注冊該節點。例如,如果 kubelet 重新開機時其 --node-labels 是新的值集,但同一個 Node 名稱已經被使用,則所作變更不會起作用, 因為節點标簽是在 Node 注冊時完成的。

如果在 kubelet 重新開機期間 Node 配置發生了變化,已經被排程到某 Node 上的 Pod 可能會出現行為不正常或者出現其他問題

例如,已經運作的 Pod 可能通過污點機制設定了與 Node 上新設定的标簽相排斥的規則,也有一些其他 Pod, 本來與此 Pod 之間存在不相容的問題,也會因為新的标簽設定而被調到同一節點。節點重新注冊操作可以確定節點上所有 Pod 都被排空并被正确地重新排程。
           

手動節點管理

你可以使用 kubectl 來建立和修改 Node 對象。

如果你希望手動建立節點對象時,請設定 kubelet 标志 --register-node=false。

你可以修改 Node 對象(忽略 --register-node 設定)。 例如,你可以修改節點上的标簽或并标記其為不可排程。

你可以結合使用 Node 上的标簽和 Pod 上的選擇算符來控制排程。 例如,你可以限制某 Pod 隻能在符合要求的節點子集上運作。

如果标記節點為不可排程(unschedulable),将阻止新 Pod 排程到該 Node 之上, 但不會影響任何已經在其上的 Pod。 這是重新開機節點或者執行其他維護操作之前的一個有用的準備步驟。

要标記一個 Node 為不可排程,執行以下指令:

被 DaemonSet 控制器建立的 Pod 能夠容忍節點的不可排程屬性。 DaemonSet 通常提供節點本地的服務,即使節點上的負載應用已經被騰空, 這些服務也仍需運作在節點之上。

節點狀态

一個節點的狀态包含以下資訊:

  • 位址(Addresses)
  • 狀況(Condition)
  • 容量與可配置設定(Capacity)
  • 資訊(Info)

你可以使用 kubectl 來檢視節點狀态和其他細節資訊:

位址

這些字段的用法取決于你的雲服務商或者實體機配置。

  • HostName:由節點的核心報告。可以通過 kubelet 的 --hostname-override 參數覆寫。
  • ExternalIP:通常是節點的可外部路由(從叢集外可通路)的 IP 位址。
  • InternalIP:通常是節點的僅可在叢集内部路由的 IP 位址

狀況

conditions 字段描述了所有 Running 節點的狀況。狀況的示例包括:

節點狀況 描述
Ready 如節點是健康的并已經準備好接收 Pod 則為 True;False 表示節點不健康而且不能接收 Pod;Unknown 表示節點控制器在最近 node-monitor-grace-period 期間(預設 40 秒)沒有收到節點的消息
DiskPressure True 表示節點存在磁盤空間壓力,即磁盤可用量低, 否則為 False
MemoryPressure True 表示節點存在記憶體壓力,即節點記憶體可用量低,否則為 False
PIDPressure True 表示節點存在程序壓力,即節點上程序過多;否則為 False

NetworkUnavailable |True 表示節點網絡配置不正确;否則為 False

如果使用指令行工具來列印已保護(Cordoned)節點的細節,其中的 Condition 字段可能包括 SchedulingDisabled。SchedulingDisabled 不是 Kubernetes API 中定義的 Condition,被保護起來的節點在其規約中被标記為不可排程(Unschedulable)。

在 Kubernetes API 中,節點的狀況表示節點資源中 .status 的一部分。 例如,以下 JSON 結構描述了一個健康節點:

"conditions": [
  {
    "type": "Ready",
    "status": "True",
    "reason": "KubeletReady",
    "message": "kubelet is posting ready status",
    "lastHeartbeatTime": "2019-06-05T18:38:35Z",
    "lastTransitionTime": "2019-06-05T11:41:27Z"
  }
]
           

如果 Ready 狀況的 status 處于 Unknown 或者 False 狀态的時間超過了 pod-eviction-timeout 值(一個傳遞給 kube-controller-manager 的參數),節點控制器會對節點上的所有 Pod 觸發 API 發起的驅逐。 預設的逐出逾時時長為 5 分鐘。

某些情況下,當節點不可達時,API 伺服器不能和其上的 kubelet 通信。 删除 Pod 的決定不能傳達給 kubelet,直到它重建立立和 API 伺服器的連接配接為止。 與此同時,被計劃删除的 Pod 可能會繼續在遊離的節點上運作。

節點控制器在确認 Pod 在叢集中已經停止運作前,不會強制删除它們。 你可以看到可能在這些無法通路的節點上運作的 Pod 處于 Terminating 或者 Unknown 狀态。 如果 Kubernetes 不能基于下層基礎設施推斷出某節點是否已經永久離開了叢集, 叢集管理者可能需要手動删除該節點對象。 從 Kubernetes 删除節點對象将導緻 API 伺服器删除節點上所有運作的 Pod 對象并釋放它們的名字。

當節點上出現問題時,Kubernetes 控制面會自動建立與影響節點的狀況對應的 污點。 排程器在将 Pod 指派到某 Node 時會考慮 Node 上的污點設定。 Pod 也可以設定容忍度, 以便能夠在設定了特定污點的 Node 上運作。

容量(Capacity)與可配置設定(Allocatable)

這兩個值描述節點上的可用資源:CPU、記憶體和可以排程到節點上的 Pod 的個數上限。

capacity 塊中的字段标示節點擁有的資源總量。 allocatable 塊訓示節點上可供普通 Pod 消耗的資源量。

可以在學習如何在節點上預留計算資源 的時候了解有關容量和可配置設定資源的更多資訊。

資訊(Info)

Info 指的是節點的一般資訊,如核心版本、Kubernetes 版本(kubelet 和 kube-proxy 版本)、 容器運作時詳細資訊,以及節點使用的作業系統。 kubelet 從節點收集這些資訊并将其釋出到 Kubernetes API。

心跳

Kubernetes 節點發送的心跳幫助你的叢集确定每個節點的可用性,并在檢測到故障時采取行動。

對于節點,有兩種形式的心跳:

  • 更新節點的 .status
  • kube-node-lease 名字空間中的 Lease(租約)對象。 每個節點都有一個關聯的 Lease 對象。

與 Node 的 .status 更新相比,Lease 是一種輕量級資源。 使用 Lease 來表達心跳在大型叢集中可以減少這些更新對性能的影響。

kubelet 負責建立和更新節點的 .status,以及更新它們對應的 Lease。

  • 當節點狀态發生變化時,或者在配置的時間間隔内沒有更新事件時,kubelet 會更新 .status。 .status 更新的預設間隔為 5分鐘(比節點不可達事件的 40 秒預設逾時時間長很多)。
  • kubelet 會建立并每 10 秒(預設更新間隔時間)更新 Lease 對象。 Lease 的更新獨立于 Node 的 .status更新而發生。 如果 Lease 的更新操作失敗,kubelet 會采用指數回退機制,從 200 毫秒開始重試, 最長重試間隔為 7 秒鐘

節點控制器

節點控制器是 Kubernetes 控制面元件, 管理節點的方方面面。

節點控制器在節點的生命周期中扮演多個角色。 第一個是當節點注冊時為它配置設定一個 CIDR 區段(如果啟用了 CIDR 配置設定)。

第二個是保持節點控制器内的節點清單與雲服務商所提供的可用機器清單同步。 如果在雲環境下運作,隻要某節點不健康,節點控制器就會詢問雲服務是否節點的虛拟機仍可用。 如果不可用,節點控制器會将該節點從它的節點清單删除。

第三個是監控節點的健康狀況。節點控制器負責:

  • 在節點不可達的情況下,在 Node 的 .status 中更新 Ready 狀況。 在這種情況下,節點控制器将 NodeReady 狀況更新為 Unknown。
  • 如果節點仍然無法通路:對于不可達節點上的所有 Pod 觸發 API 發起的逐出操作。 預設情況下,節點控制器在将節點标記為 Unknown後等待 5 分鐘送出第一個驅逐請求。

預設情況下,節點控制器每 5 秒檢查一次節點狀态,可以使用 kube-controller-manager 元件上的 --node-monitor-period 參數來配置周期。

逐出速率限制

大部分情況下,節點控制器把逐出速率限制在每秒 --node-eviction-rate 個(預設為 0.1)。 這表示它每 10 秒鐘内至多從一個節點驅逐 Pod。

當一個可用區域(Availability Zone)中的節點變為不健康時,節點的驅逐行為将發生改變。 節點控制器會同時檢查可用區域中不健康(Ready 狀況為 Unknown 或 False) 的節點的百分比:

  • 如果不健康節點的比例超過 --unhealthy-zone-threshold (預設為 0.55), 驅逐速率将會降低。
  • 如果叢集較小(意即小于等于 --large-cluster-size-threshold 個節點 - 預設為 50), 驅逐操作将會停止。
  • 否則驅逐速率将降為每秒 --secondary-node-eviction-rate 個(預設為 0.01)。

在逐個可用區域中實施這些政策的原因是, 當一個可用區域可能從控制面脫離時其它可用區域可能仍然保持連接配接。 如果你的叢集沒有跨越雲服務商的多個可用區域,那(整個叢集)就隻有一個可用區域。

跨多個可用區域部署你的節點的一個關鍵原因是當某個可用區域整體出現故障時, 工作負載可以轉移到健康的可用區域。 是以,如果一個可用區域中的所有節點都不健康時,節點控制器會以正常的速率 --node-eviction-rate 進行驅逐操作。 在所有的可用區域都不健康(也即叢集中沒有健康節點)的極端情況下, 節點控制器将假設控制面與節點間的連接配接出了某些問題,它将停止所有驅逐動作 (如果故障後部分節點重新連接配接,節點控制器會從剩下不健康或者不可達節點中驅逐 Pod)。

節點控制器還負責驅逐運作在擁有 NoExecute 污點的節點上的 Pod, 除非這些 Pod 能夠容忍此污點。 節點控制器還負責根據節點故障(例如節點不可通路或沒有就緒) 為其添加污點。 這意味着排程器不會将 Pod 排程到不健康的節點上

資源容量跟蹤

Node 對象會跟蹤節點上資源的容量(例如可用記憶體和 CPU 數量)。 通過自注冊機制生成的 Node 對象會在注冊期間報告自身容量。 如果你手動添加了 Node, 你就需要在添加節點時手動設定節點容量。

Kubernetes 排程器 保證節點上有足夠的資源供其上的所有 Pod 使用。 它會檢查節點上所有容器的請求的總和不會超過節點的容量。 總的請求包括由 kubelet 啟動的所有容器,但不包括由容器運作時直接啟動的容器, 也不包括不受 kubelet 控制的其他程序。

節點拓撲

如果啟用了 TopologyManager 特性門控, kubelet 可以在作出資源配置設定決策時使用拓撲提示。 參考控制節點上拓撲管理政策了解詳細資訊。

節點體面關閉

特性狀态: Kubernetes v1.21 [beta]

kubelet 會嘗試檢測節點系統關閉事件并終止在節點上運作的所有 Pod。

在節點終止期間,kubelet 保證 Pod 遵從正常的 Pod 終止流程。

節點體面關閉特性依賴于 systemd,因為它要利用 systemd 抑制器鎖機制, 在給定的期限内延遲節點關閉。

節點體面關閉特性受 GracefulNodeShutdown 特性門控控制, 在 1.21 版本中是預設啟用的。

注意,預設情況下,下面描述的兩個配置選項,shutdownGracePeriod 和 shutdownGracePeriodCriticalPods 都是被設定為 0 的,是以不會激活節點體面關閉功能。 要激活此功能特性,這兩個 kubelet 配置選項要适當配置,并設定為非零值。

在體面關閉節點過程中,kubelet 分兩個階段來終止 Pod:

  1. 終止在節點上運作的正常 Pod。
  2. 終止在節點上運作的關鍵 Pod。

節點體面關閉的特性對應兩個 KubeletConfiguration 選項:

  • shutdownGracePeriod:指定節點應延遲關閉的總持續時間。此時間是 Pod 體面終止的時間總和,不區分正常 Pod 還是關鍵Pod。
  • shutdownGracePeriodCriticalPods:在節點關閉期間指定用于終止關鍵 Pod 的持續時間。該值應小于

    shutdownGracePeriod。

例如,如果設定了 shutdownGracePeriod=30s 和 shutdownGracePeriodCriticalPods=10s, 則 kubelet 将延遲 30 秒關閉節點。 在關閉期間,将保留前 20(30 - 10)秒用于體面終止正常 Pod, 而保留最後 10 秒用于終止關鍵 Pod。

當 Pod 在正常節點關閉期間被驅逐時,它們會被标記為關閉。 運作 kubectl get pods 時,被驅逐的 Pod 的狀态顯示為 Terminated。 并且 kubectl describe pod 表示 Pod 因節點關閉而被驅逐:

Reason: Terminated

Message: Pod was terminated in response to imminent node shutdown.

基于 Pod 優先級的節點體面關閉

特性狀态: Kubernetes v1.23 [alpha]

為了在節點體面關閉期間提供更多的靈活性,尤其是處理關閉期間的 Pod 排序問題, 節點體面關閉機制能夠關注 Pod 的 PriorityClass 設定,前提是你已經在叢集中啟用了此功能特性。 此功能特性允許叢集管理者基于 Pod 的優先級類(Priority Class) 顯式地定義節點體面關閉期間 Pod 的處理順序。

前文所述的節點體面關閉特性能夠分兩個階段關閉 Pod, 首先關閉的是非關鍵的 Pod,之後再處理關鍵 Pod。 如果需要顯式地以更細粒度定義關閉期間 Pod 的處理順序,需要一定的靈活度, 這時可以使用基于 Pod 優先級的體面關閉機制。

當節點體面關閉能夠處理 Pod 優先級時,節點體面關閉的處理可以分為多個階段, 每個階段關閉特定優先級類的 Pod。kubelet 可以被配置為按确切的階段處理 Pod, 且每個階段可以獨立設定關閉時間。

假設叢集中存在以下自定義的 Pod 優先級類。

Pod 優先級類名稱 Pod 優先級類數值
custom-class-a 100000
custom-class-b 10000
custom-class-c 1000
regular/unset
在 kubelet 配置中, shutdownGracePeriodByPodPriority 可能看起來是這樣:
Pod 優先級類數值 關閉期限
100000 10 秒
10000 180 秒
1000 120 秒
60 秒

對應的 kubelet 配置 YAML 将會是:

shutdownGracePeriodByPodPriority:
  - priority: 100000
    shutdownGracePeriodSeconds: 10
  - priority: 10000
    shutdownGracePeriodSeconds: 180
  - priority: 1000
    shutdownGracePeriodSeconds: 120
  - priority: 0
    shutdownGracePeriodSeconds: 60
           

上面的表格表明,所有 priority 值大于等于 100000 的 Pod 會得到 10 秒鐘期限停止, 所有 priority 值介于 10000 和 100000 之間的 Pod 會得到 180 秒鐘期限停止, 所有 priority 值介于 1000 和 10000 之間的 Pod 會得到 120 秒鐘期限停止, 所有其他 Pod 将獲得 60 秒的時間停止。

使用者不需要為所有的優先級類都設定數值。例如,你也可以使用下面這種配置:

Pod 優先級類數值 關閉期限
100000 300 秒
1000 120 秒
60 秒

在上面這個場景中,優先級類為 custom-class-b 的 Pod 會與優先級類為 custom-class-c 的 Pod 在關閉時按相同期限處理。

如果在特定的範圍内不存在 Pod,則 kubelet 不會等待對應優先級範圍的 Pod。 kubelet 會直接跳到下一個優先級數值範圍進行處理。

如果此功能特性被啟用,但沒有提供配置資料,則不會出現排序操作。

使用此功能特性需要啟用 GracefulNodeShutdownBasedOnPodPriority 特性門控, 并将 kubelet 配置 中的 shutdownGracePeriodByPodPriority 設定為期望的配置, 其中包含 Pod 的優先級類數值以及對應的關閉期限。

在節點體面關閉期間考慮 Pod 優先級的能力是作為 Kubernetes v1.23 中的 Alpha 功能引入的。 在 Kubernetes 1.26 中該功能是 Beta 版,預設啟用。

kubelet 子系統中會生成 graceful_shutdown_start_time_seconds 和 graceful_shutdown_end_time_seconds 度量名額以便監視節點關閉行為。

節點非體面關閉

特性狀态: Kubernetes v1.26 [beta]

節點關閉的操作可能無法被 kubelet 的節點關閉管理器檢測到, 是因為該指令不會觸發 kubelet 所使用的抑制鎖定機制,或者是因為使用者錯誤的原因, 即 ShutdownGracePeriod 和 ShutdownGracePeriodCriticalPod 配置不正确。 請參考以上節點體面關閉部分了解更多詳細資訊。

當某節點關閉但 kubelet 的節點關閉管理器未檢測到這一事件時, 在那個已關閉節點上、屬于 StatefulSet 的 Pod 将停滞于終止狀态,并且不能移動到新的運作節點上。 這是因為已關閉節點上的 kubelet 已不存在,亦無法删除 Pod, 是以 StatefulSet 無法建立同名的新 Pod。 如果 Pod 使用了卷,則 VolumeAttachments 不會從原來的已關閉節點上删除, 是以這些 Pod 所使用的卷也無法挂接到新的運作節點上。 是以,那些以 StatefulSet 形式運作的應用無法正常工作。 如果原來的已關閉節點被恢複,kubelet 将删除 Pod,新的 Pod 将被在不同的運作節點上建立。 如果原來的已關閉節點沒有被恢複,那些在已關閉節點上的 Pod 将永遠滞留在終止狀态。

為了緩解上述情況,使用者可以手動将具有 NoExecute 或 NoSchedule 效果的 node.kubernetes.io/out-of-service 污點添加到節點上,标記其無法提供服務。 如果在 kube-controller-manager 上啟用了 NodeOutOfServiceVolumeDetach 特性門控, 并且節點被通過污點标記為無法提供服務,如果節點 Pod 上沒有設定對應的容忍度, 那麼這樣的 Pod 将被強制删除,并且該在節點上被終止的 Pod 将立即進行卷分離操作。 這樣就允許那些在無法提供服務節點上的 Pod 能在其他節點上快速恢複。

在非體面關閉期間,Pod 分兩個階段終止:

  • 強制删除沒有比對的 out-of-service 容忍度的 Pod。
  • 立即對此類 Pod 執行分離卷操作。

在添加 node.kubernetes.io/out-of-service 污點之前, 應該驗證節點已經處于關閉或斷電狀态(而不是在重新啟動中)。

将 Pod 移動到新節點後,使用者需要手動移除停止服務的污點, 并且使用者要檢查關閉節點是否已恢複,因為該使用者是最初添加污點的使用者。

交換記憶體管理

特性狀态: Kubernetes v1.22 [alpha]

在 Kubernetes 1.22 之前,節點不支援使用交換記憶體,并且預設情況下, 如果在節點上檢測到交換記憶體配置,kubelet 将無法啟動。 在 1.22 以後,可以逐個節點地啟用交換記憶體支援。

要在節點上啟用交換記憶體,必須啟用 kubelet 的 NodeSwap 特性門控, 同時使用 --fail-swap-on 指令行參數或者将 failSwapOn 配置設定為 false。

當記憶體交換功能被啟用後,Kubernetes 資料(如寫入 tmpfs 的 Secret 對象的内容)可以被交換到磁盤。

使用者還可以選擇配置 memorySwap.swapBehavior 以指定節點使用交換記憶體的方式。例如:

memorySwap:
  swapBehavior: LimitedSwap
           

可用的 swapBehavior 的配置選項有:

  • LimitedSwap:Kubernetes 工作負載的交換記憶體會受限制。 不受 Kubernetes管理的節點上的工作負載仍然可以交換。
  • UnlimitedSwap:Kubernetes 工作負載可以使用盡可能多的交換記憶體請求, 一直到達到系統限制為止。

如果啟用了特性門控但是未指定 memorySwap 的配置,預設情況下 kubelet 将使用 LimitedSwap 設定。

LimitedSwap 這種設定的行為取決于節點運作的是 v1 還是 v2 的控制組(也就是 cgroups):

  • cgroupsv1: Kubernetes 工作負載可以使用記憶體和交換,上限為 Pod 的記憶體限制值(如果設定了的話)。
  • cgroupsv2: Kubernetes 工作負載不能使用交換記憶體。

節點與控制面之間的通信

本文列舉控制面節點(确切說是 API 伺服器)和 Kubernetes 叢集之間的通信路徑。 目的是為了讓使用者能夠自定義他們的安裝,以實作對網絡配置的加強, 使得叢集能夠在不可信的網絡上(或者在一個雲服務商完全公開的 IP 上)運作。

節點到控制面

Kubernetes 采用的是中心輻射型(Hub-and-Spoke)API 模式。 所有從節點(或運作于其上的 Pod)發出的 API 調用都終止于 API 伺服器。 其它控制面元件都沒有被設計為可暴露遠端服務。 API 伺服器被配置為在一個安全的 HTTPS 端口(通常為 443)上監聽遠端連接配接請求, 并啟用一種或多種形式的用戶端身份認證機制。 一種或多種用戶端鑒權機制應該被啟用, 特别是在允許使用匿名請求 或服務賬戶令牌的時候。

應該使用叢集的公共根證書開通節點,這樣它們就能夠基于有效的用戶端憑據安全地連接配接 API 伺服器。 一種好的方法是以用戶端證書的形式将用戶端憑據提供給 kubelet。 請檢視 kubelet TLS 啟動引導 以了解如何自動提供 kubelet 用戶端證書。

想要連接配接到 API 伺服器的 Pod 可以使用服務賬号安全地進行連接配接。 當 Pod 被執行個體化時,Kubernetes 自動把公共根證書和一個有效的持有者令牌注入到 Pod 裡。 kubernetes 服務(位于 default 名字空間中)配置了一個虛拟 IP 位址, 用于(通過 kube-proxy)轉發請求到 API 伺服器的 HTTPS 末端。

控制面元件也通過安全端口與叢集的 API 伺服器通信。

這樣,從叢集節點和節點上運作的 Pod 到控制面的連接配接的預設操作模式即是安全的, 能夠在不可信的網絡或公網上運作。

控制面到節點

從控制面(API 伺服器)到節點有兩種主要的通信路徑。 第一種是從 API 伺服器到叢集中每個節點上運作的 kubelet 程序。 第二種是從 API 伺服器通過它的代理功能連接配接到任何節點、Pod 或者服務。

API 伺服器到 kubelet

從 API 伺服器到 kubelet 的連接配接用于:

  • 擷取 Pod 日志
  • 挂接(通過 kubectl)到運作中的 Pod
  • 提供 kubelet 的端口轉發功能。

這些連接配接終止于 kubelet 的 HTTPS 末端。 預設情況下,API 伺服器不檢查 kubelet 的服務證書。這使得此類連接配接容易受到中間人攻擊, 在非受信網絡或公開網絡上運作也是 不安全的。

為了對這個連接配接進行認證,使用 --kubelet-certificate-authority 标志給 API 伺服器提供一個根證書包,用于 kubelet 的服務證書。

如果無法實作這點,又要求避免在非受信網絡或公共網絡上進行連接配接,可在 API 伺服器和 kubelet 之間使用 SSH 隧道。

最後,應該啟用 Kubelet 認證/鑒權 來保護 kubelet API。

API 伺服器到節點、Pod 和服務

從 API 伺服器到節點、Pod 或服務的連接配接預設為純 HTTP 方式,是以既沒有認證,也沒有加密。 這些連接配接可通過給 API URL 中的節點、Pod 或服務名稱添加字首 https: 來運作在安全的 HTTPS 連接配接上。 不過這些連接配接既不會驗證 HTTPS 末端提供的證書,也不會提供用戶端證書。 是以,雖然連接配接是加密的,仍無法提供任何完整性保證。 這些連接配接 目前還不能安全地 在非受信網絡或公共網絡上運作。

SSH 隧道

Kubernetes 支援使用 SSH 隧道來保護從控制面到節點的通信路徑。在這種配置下, API 伺服器建立一個到叢集中各節點的 SSH 隧道(連接配接到在 22 端口監聽的 SSH 伺服器) 并通過這個隧道傳輸所有到 kubelet、節點、Pod 或服務的請求。 這一隧道保證通信不會被暴露到叢集節點所運作的網絡之外。

SSH 隧道目前已被廢棄。除非你了解個中細節,否則不應使用。 Konnectivity 服務是 SSH 隧道的替代方案。

Konnectivity 服務

特性狀态: Kubernetes v1.18 [beta]

作為 SSH 隧道的替代方案,Konnectivity 服務提供 TCP 層的代理,以便支援從控制面到叢集的通信。 Konnectivity 服務包含兩個部分:Konnectivity 伺服器和 Konnectivity 代理, 分别運作在控制面網絡和節點網絡中。 Konnectivity 代理建立并維持到 Konnectivity 伺服器的網絡連接配接。 啟用 Konnectivity 服務之後,所有控制面到節點的通信都通過這些連接配接傳輸。

控制器

在機器人技術和自動化領域,控制回路(Control Loop)是一個非終止回路,用于調節系統狀态。

這是一個控制環的例子:房間裡的溫度自動調節器。

當你設定了溫度,告訴了溫度自動調節器你的期望狀态(Desired State)。 房間的實際溫度是目前狀态(Current State)。 通過對裝置的開關控制,溫度自動調節器讓其目前狀态接近期望狀态。

在 Kubernetes 中,控制器通過監控叢集 的公共狀态,并緻力于将目前狀态轉變為期望的狀态。

控制器模式

一個控制器至少追蹤一種類型的 Kubernetes 資源。這些 對象 有一個代表期望狀态的 spec 字段。 該資源的控制器負責確定其目前狀态接近期望狀态。

控制器可能會自行執行操作;在 Kubernetes 中更常見的是一個控制器會發送資訊給 API 伺服器,這會有副作用。 具體可參看後文的例子。

通過 API 伺服器來控制

Job 控制器是一個 Kubernetes 内置控制器的例子。 内置控制器通過和叢集 API 伺服器互動來管理狀态。

Job 是一種 Kubernetes 資源,它運作一個或者多個 Pod, 來執行一個任務然後停止。 (一旦被排程了,對 kubelet 來說 Pod 對象就會變成了期望狀态的一部分)。

在叢集中,當 Job 控制器拿到新任務時,它會保證一組 Node 節點上的 kubelet 可以運作正确數量的 Pod 來完成工作。 Job 控制器不會自己運作任何的 Pod 或者容器。Job 控制器是通知 API 伺服器來建立或者移除 Pod。 控制面中的其它元件 根據新的消息作出反應(排程并運作新 Pod)并且最終完成工作。

建立新 Job 後,所期望的狀态就是完成這個 Job。Job 控制器會讓 Job 的目前狀态不斷接近期望狀态:建立為 Job 要完成工作所需要的 Pod,使 Job 的狀态接近完成。

控制器也會更新配置對象。例如:一旦 Job 的工作完成了,Job 控制器會更新 Job 對象的狀态為 Finished。

(這有點像溫度自動調節器關閉了一個燈,以此來告訴你房間的溫度現在到你設定的值了)。

直接控制

相比 Job 控制器,有些控制器需要對叢集外的一些東西進行修改。

例如,如果你使用一個控制回路來保證叢集中有足夠的 節點,那麼控制器就需要目前叢集外的 一些服務在需要時建立新節點。

和外部狀态互動的控制器從 API 伺服器擷取到它想要的狀态,然後直接和外部系統進行通信 并使目前狀态更接近期望狀态。

(實際上有一個控制器 可以水準地擴充叢集中的節點。)

這裡的重點是,控制器做出了一些變更以使得事物更接近你的期望狀态, 之後将目前狀态報告給叢集的 API 伺服器。 其他控制回路可以觀測到所彙報的資料的這種變化并采取其各自的行動。

在溫度計的例子中,如果房間很冷,那麼某個控制器可能還會啟動一個防凍加熱器。 就 Kubernetes 叢集而言,控制面間接地與 IP 位址管理工具、存儲服務、雲驅動 APIs 以及其他服務協作,通過擴充 Kubernetes 來實作這點。

期望狀态與目前狀态

Kubernetes 采用了系統的雲原生視圖,并且可以處理持續的變化。

在任務執行時,叢集随時都可能被修改,并且控制回路會自動修複故障。 這意味着很可能叢集永遠不會達到穩定狀态。

隻要叢集中的控制器在運作并且進行有效的修改,整體狀态的穩定與否是無關緊要的。

設計

作為設計原則之一,Kubernetes 使用了很多控制器,每個控制器管理叢集狀态的一個特定方面。 最常見的一個特定的控制器使用一種類型的資源作為它的期望狀态, 控制器管理控制另外一種類型的資源向它的期望狀态演化。 例如,Job 的控制器跟蹤 Job 對象(以發現新的任務)和 Pod 對象(以運作 Job,然後檢視任務何時完成)。 在這種情況下,新任務會建立 Job,而 Job 控制器會建立 Pod。

使用簡單的控制器而不是一組互相連接配接的單體控制回路是很有用的。 控制器會失敗,是以 Kubernetes 的設計正是考慮到了這一點。

說明: 可以有多個控制器來建立或者更新相同類型的對象。 在背景,Kubernetes 控制器確定它們隻關心與其控制資源相關聯的資源。

例如,你可以建立 Deployment 和 Job;它們都可以建立 Pod。 Job 控制器不會删除 Deployment 所建立的

Pod,因為有資訊 (标簽)讓控制器可以區分這些 Pod。

運作控制器的方式

Kubernetes 内置一組控制器,運作在 kube-controller-manager 内。 這些内置的控制器提供了重要的核心功能。

Deployment 控制器和 Job 控制器是 Kubernetes 内置控制器的典型例子。 Kubernetes 允許你運作一個穩定的控制平面,這樣即使某些内置控制器失敗了, 控制平面的其他部分會接替它們的工作。

你會遇到某些控制器運作在控制面之外,用以擴充 Kubernetes。 或者,如果你願意,你也可以自己編寫新控制器。 你可以以一組 Pod 來運作你的控制器,或者運作在 Kubernetes 之外。 最合适的方案取決于控制器所要執行的功能是什麼。

租約

分布式系統通常需要租約(Lease);租約提供了一種機制來鎖定共享資源并協調集合成員之間的活動。 在 Kubernetes 中,租約概念表示為 coordination.k8s.io API 組中的 Lease 對象, 常用于類似節點心跳群組件級上司者選舉等系統核心能力。

節點心跳

Kubernetes 使用 Lease API 将 kubelet 節點心跳傳遞到 Kubernetes API 伺服器。 對于每個 Node,在 kube-node-lease 名字空間中都有一個具有比對名稱的 Lease 對象。 在此基礎上,每個 kubelet 心跳都是對該 Lease 對象的 update 請求,更新該 Lease 的 spec.renewTime 字段。 Kubernetes 控制平面使用此字段的時間戳來确定此 Node 的可用性。

上司者選舉

Kubernetes 也使用 Lease 確定在任何給定時間某個元件隻有一個執行個體在運作。 這在高可用配置中由 kube-controller-manager 和 kube-scheduler 等控制平面元件進行使用, 這些元件隻應有一個執行個體激活運作,而其他執行個體待機。

API 伺服器身份

特性狀态: Kubernetes v1.26 [beta]

從 Kubernetes v1.26 開始,每個 kube-apiserver 都使用 Lease API 将其身份釋出到系統中的其他位置。 雖然它本身并不是特别有用,但為用戶端提供了一種機制來發現有多少個 kube-apiserver 執行個體正在操作 Kubernetes 控制平面。kube-apiserver 租約的存在使得未來可以在各個 kube-apiserver 之間協調新的能力。

你可以檢查 kube-system 名字空間中名為 kube-apiserver- 的 Lease 對象來檢視每個 kube-apiserver 擁有的租約。你還可以使用标簽選擇算符 k8s.io/component=kube-apiserver:

kubectl -n kube-system get lease -l k8s.io/component=kube-apiserver
           
NAME                                        HOLDER                                                                           AGE
kube-apiserver-c4vwjftbvpc5os2vvzle4qg27a   kube-apiserver-c4vwjftbvpc5os2vvzle4qg27a_9cbf54e5-1136-44bd-8f9a-1dcd15c346b4   5m33s
kube-apiserver-dz2dqprdpsgnm756t5rnov7yka   kube-apiserver-dz2dqprdpsgnm756t5rnov7yka_84f2a85d-37c1-4b14-b6b9-603e62e4896f   4m23s
kube-apiserver-fyloo45sdenffw2ugwaz3likua   kube-apiserver-fyloo45sdenffw2ugwaz3likua_c5ffa286-8a9a-45d4-91e7-61118ed58d2e   4m43s
           

租約名稱中使用的 SHA256 哈希基于 API 伺服器所看到的作業系統主機名生成。 每個 kube-apiserver 都應該被配置為使用叢集中唯一的主機名。 使用相同主機名的 kube-apiserver 新執行個體将使用新的持有者身份接管現有 Lease,而不是執行個體化新的 Lease 對象。 你可以通過檢查 kubernetes.io/hostname 标簽的值來檢視 kube-apisever 所使用的主機名:

kubectl -n kube-system get lease kube-apiserver-c4vwjftbvpc5os2vvzle4qg27a -o yaml
           
apiVersion: coordination.k8s.io/v1
kind: Lease
metadata:
  creationTimestamp: "2022-11-30T15:37:15Z"
  labels:
    k8s.io/component: kube-apiserver
    kubernetes.io/hostname: kind-control-plane
  name: kube-apiserver-c4vwjftbvpc5os2vvzle4qg27a
  namespace: kube-system
  resourceVersion: "18171"
  uid: d6c68901-4ec5-4385-b1ef-2d783738da6c
spec:
  holderIdentity: kube-apiserver-c4vwjftbvpc5os2vvzle4qg27a_9cbf54e5-1136-44bd-8f9a-1dcd15c346b4
  leaseDurationSeconds: 3600
  renewTime: "2022-11-30T18:04:27.912073Z"
           

kube-apiserver 中不再存續的已到期租約将在到期 1 小時後被新的 kube-apiserver 作為垃圾收集。

你可以通過禁用 APIServerIdentity 特性門控來禁用 API 伺服器身份租約。

工作負載

你自己的工作負載可以定義自己使用的 Lease。例如, 你可以運作自定義的控制器, 讓主要成員或上司者成員在其中執行其對等方未執行的操作。 你定義一個 Lease,以便控制器副本可以使用 Kubernetes API 進行協調以選擇或選舉一個上司者。 如果你使用 Lease,良好的做法是為明顯關聯到産品或元件的 Lease 定義一個名稱。 例如,如果你有一個名為 Example Foo 的元件,可以使用名為 example-foo 的 Lease。

如果叢集操作員或其他終端使用者可以部署一個元件的多個執行個體, 則選擇名稱字首并挑選一種機制(例如 Deployment 名稱的哈希)以避免 Lease 的名稱沖突。

你可以使用另一種方式來達到相同的效果:不同的軟體産品不互相沖突。

關于 cgroup v2

在 Linux 上,控制組限制配置設定給程序的資源。

kubelet 和底層容器運作時都需要對接 cgroup 來強制執行為 Pod 和容器管理資源, 這包括為容器化工作負載配置 CPU/記憶體請求和限制。

Linux 中有兩個 cgroup 版本:cgroup v1 和 cgroup v2。cgroup v2 是新一代的 cgroup API。

什麼是 cgroup v2?

特性狀态: Kubernetes v1.25 [stable]

cgroup v2 是 Linux cgroup API 的下一個版本。cgroup v2 提供了一個具有增強資源管理能力的統一控制系統。

cgroup v2 對 cgroup v1 進行了多項改進,例如:

  • API 中單個統一的層次結構設計
  • 更安全的子樹委派給容器
  • 更新的功能特性, 例如壓力阻塞資訊(Pressure Stall Information,PSI)
  • 跨多個資源的增強資源配置設定管理和隔離
  • 統一核算不同類型的記憶體配置設定(網絡記憶體、核心記憶體等)
  • 考慮非即時資源變化,例如頁面緩存回寫

一些 Kubernetes 特性專門使用 cgroup v2 來增強資源管理和隔離。 例如,MemoryQoS 特性改進了記憶體 QoS 并依賴于 cgroup v2 原語。

使用 cgroup v2

使用 cgroup v2 的推薦方法是使用一個預設啟用 cgroup v2 的 Linux 發行版。

要檢查你的發行版是否使用 cgroup v2,請參閱識别 Linux 節點上的 cgroup 版本。

要求

cgroup v2 具有以下要求:

  • 作業系統發行版啟用 cgroup v2
  • Linux 核心為 5.8 或更高版本
  • 容器運作時支援 cgroup v2。例如:
  • containerd v1.4 和更高版本
  • cri-o v1.20 和更高版本
  • kubelet 和容器運作時被配置為使用 systemd cgroup 驅動

Linux 發行版 cgroup v2 支援

有關使用 cgroup v2 的 Linux 發行版的清單, 請參閱 cgroup v2 文檔。

  • Container-Optimized OS(從 M97 開始)
  • Ubuntu(從 21.10 開始,推薦 22.04+)
  • Debian GNU/Linux(從 Debian 11 Bullseye 開始)
  • Fedora(從 31 開始)
  • Arch Linux(從 2021 年 4 月開始)
  • RHEL 和類似 RHEL 的發行版(從 9 開始)

要檢查你的發行版是否使用 cgroup v2, 請參閱你的發行版文檔或遵循識别 Linux 節點上的 cgroup 版本中的訓示說明。

你還可以通過修改核心 cmdline 引導參數在你的 Linux 發行版上手動啟用 cgroup v2。 如果你的發行版使用 GRUB,則應在 /etc/default/grub 下的 GRUB_CMDLINE_LINUX 中添加 systemd.unified_cgroup_hierarchy=1, 然後執行 sudo update-grub。不過,推薦的方法仍是使用一個預設已啟用 cgroup v2 的發行版。

遷移到 cgroup v2

要遷移到 cgroup v2,需確定滿足要求,然後更新到一個預設啟用 cgroup v2 的核心版本。

kubelet 能夠自動檢測作業系統是否運作在 cgroup v2 上并相應調整其操作,無需額外配置。

切換到 cgroup v2 時,使用者體驗應沒有任何明顯差異,除非使用者直接在節點上或從容器内通路 cgroup 檔案系統。

cgroup v2 使用一個與 cgroup v1 不同的 API,是以如果有任何應用直接通路 cgroup 檔案系統, 則需要将這些應用更新為支援 cgroup v2 的版本。例如:

  • 一些第三方監控和安全代理可能依賴于 cgroup 檔案系統。你要将這些代理更新到支援 cgroup v2 的版本。
  • 如果以獨立的 DaemonSet 的形式運作 cAdvisor 以監控 Pod 和容器, 需将其更新到 v0.43.0 或更高版本。
  • 如果你使用 JDK,推薦使用 JDK 11.0.16 及更高版本或 JDK 15 及更高版本, 以便完全支援 cgroup v2。

識别 Linux 節點上的 cgroup 版本

cgroup 版本取決于正在使用的 Linux 發行版和作業系統上配置的預設 cgroup 版本。 要檢查你的發行版使用的是哪個 cgroup 版本,請在該節點上運作 stat -fc %T /sys/fs/cgroup/ 指令:

stat -fc %T /sys/fs/cgroup/
           

對于 cgroup v2,輸出為 cgroup2fs。

對于 cgroup v1,輸出為 tmpfs。

容器運作時接口(CRI)

CRI 是一個插件接口,它使 kubelet 能夠使用各種容器運作時,無需重新編譯叢集元件。

你需要在叢集中的每個節點上都有一個可以正常工作的容器運作時, 這樣 kubelet 能啟動 Pod 及其容器。

容器運作時接口(CRI)是 kubelet 和容器運作時之間通信的主要協定。

Kubernetes 容器運作時接口(Container Runtime Interface;CRI)定義了主要 gRPC 協定, 用于叢集元件 kubelet 和 容器運作時之間的通信。

API

特性狀态: Kubernetes v1.23 [stable]

當通過 gRPC 連接配接到容器運作時,kubelet 将充當用戶端。運作時和鏡像服務端點必須在容器運作時中可用, 可以使用 --image-service-endpoint 和 --container-runtime-endpoint 指令行标志在 kubelet 中單獨配置。

對 Kubernetes v1.26,kubelet 偏向于使用 CRI v1 版本。 如果容器運作時不支援 CRI 的 v1 版本,那麼 kubelet 會嘗試協商較老的、仍被支援的所有版本。 v1.26 版本的 kubelet 也可協商 CRI v1alpha2 版本,但該版本被視為已棄用。 如果 kubelet 無法協商出可支援的 CRI 版本,則 kubelet 放棄并且不會注冊為節點。

更新

更新 Kubernetes 時,kubelet 會嘗試在元件重新開機時自動選擇最新的 CRI 版本。 如果失敗,則将如上所述進行回退。如果由于容器運作時已更新而需要 gRPC 重撥, 則容器運作時還必須支援最初選擇的版本,否則重撥預計會失敗。 這需要重新啟動 kubelet。

垃圾收集

垃圾收集(Garbage Collection)是 Kubernetes 用于清理叢集資源的各種機制的統稱。 垃圾收集允許系統清理如下資源:

  • 終止的 Pod
  • 已完成的 Job
  • 不再存在屬主引用的對象
  • 未使用的容器和容器鏡像
  • 動态制備的、StorageClass 回收政策為 Delete 的 PV 卷
  • 阻滞或者過期的 CertificateSigningRequest (CSR)
  • 在以下情形中删除了的節點對象:
  • 當叢集使用雲控制器管理器運作于雲端時;
  • 當叢集使用類似于雲控制器管理器的插件運作在本地環境中時。
  • 節點租約對象

屬主與依賴

Kubernetes 中很多對象通過屬主引用 連結到彼此。屬主引用(Owner Reference)可以告訴控制面哪些對象依賴于其他對象。 Kubernetes 使用屬主引用來為控制面以及其他 API 用戶端在删除某對象時提供一個清理關聯資源的機會。 在大多數場合,Kubernetes 都是自動管理屬主引用的。

屬主關系與某些資源所使用的标簽和選擇算符不同。 例如,考慮一個建立 EndpointSlice 對象的 Service。 Service 使用标簽來允許控制面确定哪些 EndpointSlice 對象被該 Service 使用。 除了标簽,每個被 Service 托管的 EndpointSlice 對象還有一個屬主引用屬性。 屬主引用可以幫助 Kubernetes 中的不同元件避免幹預并非由它們控制的對象。

說明:

根據設計,系統不允許出現跨名字空間的屬主引用。名字空間作用域的依賴對象可以指定叢集作用域或者名字空間作用域的屬主。

名字空間作用域的屬主必須存在于依賴對象所在的同一名字空間。

如果屬主位于不同名字空間,則屬主引用被視為不存在,而當檢查發現所有屬主都已不存在時,依賴對象會被删除。

叢集作用域的依賴對象隻能指定叢集作用域的屬主。 在 1.20

及更高版本中,如果一個叢集作用域的依賴對象指定了某個名字空間作用域的類别作為其屬主,

則該對象被視為擁有一個無法解析的屬主引用,因而無法被垃圾收集處理。

在 1.20 及更高版本中,如果垃圾收集器檢測到非法的跨名字空間 ownerReference, 或者某叢集作用域的依賴對象的

ownerReference 引用某名字空間作用域的類别, 系統會生成一個警告事件,其原因為

OwnerRefInvalidNamespace,involvedObject 設定為非法的依賴對象。你可以通過運作 kubectl get

events -A --field-selector=reason=OwnerRefInvalidNamespace

來檢查是否存在這類事件。

級聯删除

Kubernetes 會檢查并删除那些不再擁有屬主引用的對象,例如在你删除了 ReplicaSet 之後留下來的 Pod。當你删除某個對象時,你可以控制 Kubernetes 是否去自動删除該對象的依賴對象, 這個過程稱為 級聯删除(Cascading Deletion)。 級聯删除有兩種類型,分别如下:

  • 前台級聯删除
  • 背景級聯删除

你也可以使用 Kubernetes Finalizers 來控制垃圾收集機制如何以及何時删除包含屬主引用的資源。

前台級聯删除

在前台級聯删除中,正在被你删除的屬主對象首先進入 deletion in progress 狀态。 在這種狀态下,針對屬主對象會發生以下事情:

  • Kubernetes API 伺服器将某對象的 metadata.deletionTimestamp字段設定為該對象被标記為要删除的時間點。
  • Kubernetes API 伺服器也會将 metadata.finalizers 字段設定為 foregroundDeletion。
  • 在删除過程完成之前,通過 Kubernetes API 仍然可以看到該對象。

當屬主對象進入删除過程中狀态後,控制器删除其依賴對象。控制器在删除完所有依賴對象之後, 删除屬主對象。這時,通過 Kubernetes API 就無法再看到該對象。

在前台級聯删除過程中,唯一可能阻止屬主對象被删除的是那些帶有 ownerReference.blockOwnerDeletion=true 字段的依賴對象。 參閱使用前台級聯删除 以了解進一步的細節。

背景級聯删除

在背景級聯删除過程中,Kubernetes 伺服器立即删除屬主對象,控制器在背景清理所有依賴對象。 預設情況下,Kubernetes 使用背景級聯删除方案,除非你手動設定了要使用前台删除, 或者選擇遺棄依賴對象。

參閱使用背景級聯删除 以了解進一步的細節。

被遺棄的依賴對象

當 Kubernetes 删除某個屬主對象時,被留下來的依賴對象被稱作被遺棄的(Orphaned)對象。 預設情況下,Kubernetes 會删除依賴對象。要了解如何重載這種預設行為,可參閱 删除屬主對象和遺棄依賴對象。

未使用容器和鏡像的垃圾收集

kubelet 會每五分鐘對未使用的鏡像執行一次垃圾收集, 每分鐘對未使用的容器執行一次垃圾收集。 你應該避免使用外部的垃圾收集工具,因為外部工具可能會破壞 kubelet 的行為,移除應該保留的容器。

要配置對未使用容器和鏡像的垃圾收集選項,可以使用一個 配置檔案,基于 KubeletConfiguration 資源類型來調整與垃圾收集相關的 kubelet 行為。

容器鏡像生命周期

Kubernetes 通過其鏡像管理器(Image Manager) 來管理所有鏡像的生命周期, 該管理器是 kubelet 的一部分,工作時與 cadvisor 協同。 kubelet 在作出垃圾收集決定時會考慮如下磁盤用量限制:

  • HighThresholdPercent
  • LowThresholdPercent

磁盤用量超出所配置的 HighThresholdPercent 值時會觸發垃圾收集, 垃圾收集器會基于鏡像上次被使用的時間來按順序删除它們,首先删除的是最近未使用的鏡像。 kubelet 會持續删除鏡像,直到磁盤用量到達 LowThresholdPercent 值為止。

容器垃圾收集

kubelet 會基于如下變量對所有未使用的容器執行垃圾收集操作,這些變量都是你可以定義的:

  • MinAge:kubelet 可以垃圾回收某個容器時該容器的最小年齡。設定為 0 表示禁止使用此規則。
  • MaxPerPodContainer:每個 Pod 可以包含的已死亡的容器個數上限。設定為小于 0 的值表示禁止使用此規則。
  • MaxContainers:叢集中可以存在的已死亡的容器個數上限。設定為小于 0 的值意味着禁止應用此規則。

除以上變量之外,kubelet 還會垃圾收集除無辨別的以及已删除的容器,通常從最近未使用的容器開始。

當保持每個 Pod 的最大數量的容器(MaxPerPodContainer)會使得全局的已死亡容器個數超出上限 (MaxContainers)時,MaxPerPodContainers 和 MaxContainers 之間可能會出現沖突。 在這種情況下,kubelet 會調整 MaxPerPodContainer 來解決這一沖突。 最壞的情形是将 MaxPerPodContainer 降格為 1,并驅逐最近未使用的容器。 此外,當隸屬于某已被删除的 Pod 的容器的年齡超過 MinAge 時,它們也會被删除。

說明: kubelet 僅會回收由它所管理的容器。