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