本文發表於一年多前。舊文章可能包含過時內容。請檢查頁面中的資訊自發布以來是否已變得不正確。

映象檔案系統:配置 Kubernetes 將容器儲存在單獨的檔案系統上

執行/操作 Kubernetes 叢集時的一個常見問題是磁碟空間耗盡。在配置節點時,您應該力求為容器映象和執行中的容器提供充足的儲存空間。 容器執行時通常寫入 /var 目錄。這可以位於單獨的分割槽上,也可以位於根檔案系統上。預設情況下,CRI-O 將其容器和映象寫入 /var/lib/containers,而 containerd 將其容器和映象寫入 /var/lib/containerd

在這篇博文中,我們想提請大家注意如何配置容器執行時,以便將其內容與預設分割槽分別儲存。
這使得配置 Kubernetes 更加靈活,並支援為容器儲存新增更大的磁碟,同時保持預設檔案系統不變。

有一個需要更多解釋的領域是 Kubernetes 在向哪個磁碟寫入什麼內容。

瞭解 Kubernetes 的磁碟使用情況

Kubernetes 有持久資料和臨時資料。kubelet 和本地 Kubernetes 特定儲存的基礎路徑是可配置的,但通常假定為 /var/lib/kubelet。在 Kubernetes 文件中,這有時被稱為根檔案系統或節點檔案系統。這些資料的大部分可分為

  • 臨時儲存
  • 日誌
  • 和容器執行時

這與大多數 POSIX 系統不同,因為根/節點檔案系統不是 /,而是 /var/lib/kubelet 所在的磁碟。

臨時儲存

Pod 和容器可能需要臨時或瞬態的本地儲存來進行操作。臨時儲存的生命週期不會超過單個 Pod 的生命週期,並且臨時儲存不能在 Pod 之間共享。

日誌

預設情況下,Kubernetes 將每個執行中容器的日誌作為檔案儲存在 /var/log 中。這些日誌是臨時的,由 kubelet 監控,以確保在 Pod 執行時它們不會變得太大。

你可以為每個節點自定義日誌輪換設定來管理這些日誌的大小,並配置日誌傳輸(使用第三方解決方案)以避免依賴節點本地儲存。

容器執行時

容器執行時有兩個不同的儲存區域用於容器和映象。

  • 只讀層:映象通常被表示為只讀層,因為它們在容器執行時不會被修改。只讀層可以由多個層組合成一個單一的只讀層。在容器之上有一個薄層,如果容器正在向檔案系統寫入,它會為容器提供臨時儲存。

  • 可寫層:根據你的容器執行時,本地寫入可能被實現為分層寫入機制(例如,Linux 上的 overlayfs 或 Windows 上的 CimFS)。這被稱為可寫層。本地寫入也可以使用一個可寫的檔案系統,該檔案系統以容器映象的完整克隆進行初始化;這用於某些基於虛擬機器管理程式虛擬化的執行時。

容器執行時檔案系統包含只讀層和可寫層。這在 Kubernetes 文件中被認為是 imagefs

容器執行時配置

CRI-O

CRI-O 使用 TOML 格式的儲存配置檔案,讓你控制容器執行時如何儲存持久和臨時資料。CRI-O 使用儲存庫
一些 Linux 發行版有儲存的手冊條目(man 5 containers-storage.conf)。儲存的主要配置位於 /etc/containers/storage.conf,可以控制臨時資料的位置和根目錄。
根目錄是 CRI-O 儲存持久資料的地方。

[storage]
# Default storage driver
driver = "overlay"
# Temporary storage location
runroot = "/var/run/containers/storage"
# Primary read/write location of container storage 
graphroot = "/var/lib/containers/storage"
  • graphroot
    • 來自容器執行時的持久化儲存資料
    • 如果啟用了 SELinux,這必須與 /var/lib/containers/storage 匹配
  • runroot
    • 容器的臨時讀/寫訪問
    • 建議將其放在臨時檔案系統上

這裡有一個快速的方法來重新標記你的 graphroot 目錄以匹配 /var/lib/containers/storage

semanage fcontext -a -e /var/lib/containers/storage <YOUR-STORAGE-PATH>
restorecon -R -v <YOUR-STORAGE-PATH>

containerd

containerd 執行時使用 TOML 配置檔案來控制持久和臨時資料的儲存位置。配置檔案的預設路徑位於 /etc/containerd/config.toml

containerd 儲存的相關欄位是 rootstate

  • root
    • containerd 元資料的根目錄
    • 預設為 /var/lib/containerd
    • 如果你的作業系統需要,Root 也需要 SELinux 標籤
  • state
    • containerd 的臨時資料
    • 預設為 /run/containerd

Kubernetes 節點壓力驅逐

Kubernetes 會自動檢測容器檔案系統是否與節點檔案系統分離。當檔案系統分離時,Kubernetes 負責監控節點檔案系統和容器執行時檔案系統。Kubernetes 文件將節點檔案系統和容器執行時檔案系統稱為 nodefs 和 imagefs。如果 nodefs 或 imagefs 的磁碟空間不足,則整個節點被認為存在磁碟壓力。Kubernetes 會首先透過刪除未使用的容器和映象來回收空間,然後才會驅逐 Pod。在一個有 nodefs 和 imagefs 的節點上,kubelet 會在 imagefs 上垃圾回收未使用的容器映象,並從 nodefs 中移除死掉的 Pod 及其容器。如果只有一個 nodefs,那麼 Kubernetes 的垃圾回收包括死掉的容器、死掉的 Pod 和未使用的映象。

Kubernetes 允許更多的配置來確定你的磁碟是否已滿。
kubelet 中的驅逐管理器有一些配置設定,可以讓你控制相關的閾值。對於檔案系統,相關的度量是 nodefs.availablenodefs.inodesfreeimagefs.availableimagefs.inodesfree。如果沒有專門用於容器執行時的磁碟,則忽略 imagefs。

使用者可以使用現有的預設值

  • memory.available < 100MiB
  • nodefs.available < 10%
  • imagefs.available < 15%
  • nodefs.inodesFree < 5% (Linux 節點)

Kubernetes 允許你在 kubelet 配置檔案中的 EvictionHardEvictionSoft 中設定使用者定義的值。

EvictionHard
定義了限制;一旦超過這些限制,Pod 將被立即驅逐,沒有任何寬限期。
EvictionSoft
定義了限制;一旦超過這些限制,Pod 將在寬限期後被驅逐,寬限期可以按訊號設定。

如果你為 EvictionHard 指定一個值,它將替換預設值。
這意味著在你的配置中設定所有訊號非常重要。

例如,以下 kubelet 配置可用於配置驅逐訊號和寬限期選項。

apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
address: "192.168.0.8"
port: 20250
serializeImagePulls: false
evictionHard:
    memory.available:  "100Mi"
    nodefs.available:  "10%"
    nodefs.inodesFree: "5%"
    imagefs.available: "15%"
    imagefs.inodesFree: "5%"
evictionSoft:
    memory.available:  "100Mi"
    nodefs.available:  "10%"
    nodefs.inodesFree: "5%"
    imagefs.available: "15%"
    imagefs.inodesFree: "5%"
evictionSoftGracePeriod:
    memory.available:  "1m30s"
    nodefs.available:  "2m"
    nodefs.inodesFree: "2m"
    imagefs.available: "2m"
    imagefs.inodesFree: "2m"
evictionMaxPodGracePeriod: 60s

問題

Kubernetes 專案建議你要麼使用驅逐的預設設定,要麼設定驅逐的所有欄位。你可以使用預設設定或指定自己的 evictionHard 設定。如果你遺漏了一個訊號,那麼 Kubernetes 將不會監控該資源。管理員或使用者可能遇到的一個常見錯誤配置是,將新的檔案系統掛載到 /var/lib/containers/storage/var/lib/containerd。Kubernetes 會檢測到一個單獨的檔案系統,所以如果你這樣做了,你要確保檢查 imagefs.inodesfreeimagefs.available 是否符合你的需求。

另一個令人困惑的地方是,如果你為節點定義了映象檔案系統,臨時儲存的報告不會改變。映象檔案系統(imagefs)用於儲存容器映象層;如果一個容器寫入其自己的根檔案系統,該本地寫入不計入容器映象的大小。容器執行時儲存這些本地修改的地方是由執行時定義的,但通常是映象檔案系統。如果 Pod 中的容器正在寫入一個由檔案系統支援的 emptyDir 卷,那麼這會使用 nodefs 檔案系統的空間。kubelet 總是基於 nodefs 所代表的檔案系統來報告臨時儲存容量和分配;當臨時寫入實際上是寫入映象檔案系統時,這可能會令人困惑。

未來的工作

為了解決臨時儲存報告的侷限性併為容器執行時提供更多的配置選項,SIG Node 正在研究 KEP-4191。在 KEP-4191 中,Kubernetes 將檢測可寫層是否與只讀層(映象)分離。這將允許我們將所有臨時儲存,包括可寫層,放在同一個磁碟上,並允許為映象使用一個單獨的磁碟。

參與進來

如果你想參與進來,可以加入 Kubernetes Node 特別興趣小組 (SIG)。

如果你想分享反饋,可以在我們的 #sig-node Slack 頻道上進行。如果你還不是該 Slack 工作區的成員,可以訪問 https://slack.k8s.io/ 獲取邀請。

特別感謝所有提供精彩評論、分享寶貴見解或提出主題創意的貢獻者。

  • Peter Hunt
  • Mrunal Patel
  • Ryan Phillips
  • Gaurav Singh