Init container 為「初始化相關的任務」提供了一條獨立於主應用容器之外的生命週期,藉此達成關注點分離。這是 Kubernetes 的基礎概念,許多其他模式在需要初始化邏輯時都會用到它。

問題#

初始化是許多程式語言共通的關注點:有些語言把它納入語言本身,有些則用命名慣例與模式來標示某個建構是初始化器。以 Java 為例,要實例化一個需要前置設定的物件,我們用建構子(更花俏的場合則用靜態區塊)。建構子保證是物件內最先執行的東西,且由管理執行期保證只執行一次;我們也用建構子驗證前置條件(例如必填參數),或用傳入的參數與預設值初始化實例欄位。

若你的 Pod 中有一個或多個代表主應用的容器,這些容器在啟動前可能有前置需求:

  • 在檔案系統上設定特殊權限
  • 建立資料庫綱要(schema)
  • 安裝應用的種子資料

而且這些初始化邏輯可能需要無法包進應用映像檔的工具與函式庫;基於安全考量,應用映像檔也可能沒有執行初始化活動的權限。另一種情況是,你想延後應用啟動,直到某個外部依賴被滿足

解法#

Kubernetes 的 init container 是 Pod 定義的一部分,它把 Pod 內所有容器分成兩組:init container應用容器

圖 14-1:Pod 中的 init container 與應用容器

特性與注意事項#

Init container 通常應該小、跑得快、且成功完成——除非它被用來延遲 Pod 啟動、等待某個依賴,這種情況下它可能要等依賴滿足後才終止。

一方面,init container 擁有與應用容器完全相同的能力:所有容器同屬一個 Pod,共享資源上限、volume 與安全設定,最終也被放在同一個節點上。另一方面,它們的健康檢查與資源處理語意略有不同——init container 沒有就緒檢查,因為它們必須全部成功終止,Pod 啟動流程才會進入應用容器階段。

Init container 也影響 Pod 資源需求在排程、自動擴縮與配額管理上的計算方式。由於執行有先後(先依序跑 init container,再平行跑所有應用容器),有效的 Pod 層級 request 與 limit 值取以下兩者的較大者

  • 所有 init container 中最高的 request/limit 值
  • 所有應用容器 request/limit 的總和

這個行為的後果是:若你的 init container 資源需求高、應用容器需求低,影響排程的 Pod 層級 request/limit 就會取決於 init container 那個較高的值。這種配置並不節省資源——即使 init container 只跑一小段時間、節點大部分時間都有餘裕容量,其他 Pod 也用不到它

範例:關注點分離#

Init container 讓容器維持單一目的:應用工程師撰寫應用容器、只專注於應用邏輯;部署工程師撰寫 init container、只專注於組態與初始化任務。

以下範例中,應用容器是一個提供檔案的 HTTP 伺服器,它只提供通用的 HTTP 服務能力,不假設要服務的檔案從哪來;同一 Pod 中的 init container 提供 Git 客戶端能力,唯一目的是複製一個 Git repo。兩者同屬一個 Pod,因此能存取同一個 volume 來共享資料:

apiVersion: v1
kind: Pod
metadata:
  name: www
  labels:
    app: www
spec:
  initContainers:
    - name: download
      image: axeclbr/git
      command: # 把外部 Git 儲存庫複製到掛載的目錄中
        - git
        - clone
        - https://github.com/mdn/beginner-html-site-scripted
        - /var/lib/data
      volumeMounts: # init container 與應用容器共用的 volume
        - mountPath: /var/lib/data
          name: source
  containers:
    - name: run
      image: docker.io/centos/httpd
      ports:
        - containerPort: 80
      volumeMounts:
        - mountPath: /var/www/html
          name: source
  volumes:
    - emptyDir: {} # 節點上用來共享資料的空目錄
      name: source

要除錯 init container 的執行結果,可以暫時把應用容器的指令換成一個假的 sleep 指令,好讓你有時間檢查狀況。當 init container 啟動失敗、或應用因組態缺失或損壞而無法啟動時,這招特別有用。在 Pod 宣告中加入以下指令,就有一小時可以用 kubectl exec -it <Pod> sh 進入 Pod 檢查掛載的 volume:

command:
  - /bin/sh
  - "-c"
  - "sleep 3600"

與 Sidecar 的取捨#

用「邊車」也能達成類似效果——讓 HTTP 伺服器容器與 Git 容器並排作為應用容器執行。但邊車模式無法得知哪個容器會先跑,而且邊車的用意是讓容器持續並行運作(例如 Git 同步器持續更新本地資料夾)。

若同時需要有保證的初始化資料的持續更新,可以把 Init Container 與 Sidecar 一起使用。

延伸:其他初始化技術

Init container 是 Pod 層級的建構,在 Pod 啟動之後才被啟用。以下幾種初始化 Kubernetes 資源的相關技術則與它不同,為求完整一併列出:

Admission controllers(准入控制器)

一組外掛,在物件被持久化之前攔截每個送往 Kubernetes API Server 的請求,可以變更(mutate)或驗證(validate)它。有許多控制器負責套用檢查、強制限制與設定預設值,但它們全都編譯進 kube-apiserver 二進位檔,由叢集管理員在 API Server 啟動時設定。這個外掛系統不夠靈活,因此 Kubernetes 才加入了 admission webhook。

Admission webhooks

外部的准入控制器,對任何相符的請求執行 HTTP 回呼。分兩種:mutating webhook(可修改資源以套用自訂預設值)與 validating webhook(可拒絕資源以強制自訂准入政策)。這種外部控制器的概念,讓 admission webhook 能在 Kubernetes 之外開發、並於執行期設定。

Initializers

透過在每個物件的 metadata 中儲存一份「待處理的預初始化任務清單」,讓管理員強制執行政策或注入預設值。接著由名稱與任務名稱對應的自訂 initializer 控制器執行這些任務。只有在所有初始化任務完全完成後,該 API 物件才會對一般控制器可見。

PodPresets

由另一個 admission controller 評估,在 Pod 建立時把相符 PodPreset 中指定的欄位注入 Pod,欄位可包含 volume、volume mount 或環境變數。它使用標籤選擇器指定套用對象,讓 Pod 範本作者能自動化地為多個 Pod 加上重複性的資訊。

關鍵差異:上述技術都在建立時驗證與變更資源(例如可用來為任何尚未有 init container 的 Pod 注入一個);而本章的 Init Container 模式則是在 Pod 啟動期間啟用並履行職責。最重要的區別是:init container 是給部署在 Kubernetes 上的開發者用的,而上述技術是幫助管理員控制與管理容器初始化流程的。

討論#

為什麼要把 Pod 中的容器分成兩組?需要初始化時,直接用應用容器做不就好了?答案是:這兩組容器有不同的生命週期、不同的目的,有時甚至有不同的作者。

Init container 先於應用容器執行,更重要的是它們分階段執行、且只有當前階段成功完成才會前進。這意味著初始化的每一步,你都能確信前一步已成功完成,才進入下一階段。相對地,應用容器平行執行,不提供同樣的保證。

有了這個區分,我們就能建立各自專注於單一初始化任務或應用任務的容器,並把它們組織進 Pod。

更多資訊#

  • Init Container Example
  • Init Containers
  • Configuring Pod Initialization
  • The Initializer Pattern in JavaScript
  • Object Initialization in Swift
  • Using Admission Controllers
  • Dynamic Admission Control
  • How Kubernetes Initializers Work
  • Pod Preset
  • Inject Information into Pods Using a PodPreset
  • Kubernetes Initializer Tutorial