Init 容器
該特性在自 Kubernetes 1.6 版本推出 beta 版本。Init 容器可以在 PodSpec 中同應用程序的 containers
數組一起來指定。此前 beta 注解的值仍將保留,並覆蓋 PodSpec 字段值。
本文講解 Init 容器的基本概念,這是一種專用的容器,在應用程序容器啟動之前運行,用來包含一些應用鏡像中不存在的實用工具或安裝腳本。
理解 Init 容器
Pod 能夠具有多個容器,應用運行在容器里面,但是它也可能有一個或多個先於應用容器啟動的 Init 容器。
Init 容器與普通的容器非常像,除了如下兩點:
- Init 容器總是運行到成功完成為止。
- 每個 Init 容器都必須在下一個 Init 容器啟動之前成功完成。
如果 Pod 的 Init 容器失敗,Kubernetes 會不斷地重啟該 Pod,直到 Init 容器成功為止。然而,如果 Pod 對應的 restartPolicy
為 Never,它不會重新啟動。
指定容器為 Init 容器,在 PodSpec 中添加 initContainers
字段,以 v1.Container 類型對象的 JSON 數組的形式,還有 app 的 containers
數組。 Init 容器的狀態在 status.initContainerStatuses
字段中以容器狀態數組的格式返回(類似 status.containerStatuses
字段)。
與普通容器的不同之處
Init 容器支持應用容器的全部字段和特性,包括資源限制、數據卷和安全設置。 然而,Init 容器對資源請求和限制的處理稍有不同,在下面 資源 處有說明。 而且 Init 容器不支持 Readiness Probe,因為它們必須在 Pod 就緒之前運行完成。
如果為一個 Pod 指定了多個 Init 容器,那些容器會按順序一次運行一個。只有當前面的 Init 容器必須運行成功后,才可以運行下一個 Init 容器。當所有的 Init 容器運行完成后,Kubernetes 才初始化 Pod 和運行應用容器。
Init 容器能做什么?
因為 Init 容器具有與應用程序容器分離的單獨鏡像,所以它們的啟動相關代碼具有如下優勢:
- 它們可以包含並運行實用工具,但是出於安全考慮,是不建議在應用程序容器鏡像中包含這些實用工具的。
- 它們可以包含使用工具和定制化代碼來安裝,但是不能出現在應用程序鏡像中。例如,創建鏡像沒必要
FROM
另一個鏡像,只需要在安裝過程中使用類似sed
、awk
、python
或dig
這樣的工具。 - 應用程序鏡像可以分離出創建和部署的角色,而沒有必要聯合它們構建一個單獨的鏡像。
- Init 容器使用 Linux Namespace,所以相對應用程序容器來說具有不同的文件系統視圖。因此,它們能夠具有訪問 Secret 的權限,而應用程序容器則不能。
- 它們必須在應用程序容器啟動之前運行完成,而應用程序容器是並行運行的,所以 Init 容器能夠提供了一種簡單的阻塞或延遲應用容器的啟動的方法,直到滿足了一組先決條件。
示例
下面列舉了 Init 容器的一些用途:
-
等待一個 Service 創建完成,通過類似如下 shell 命令:
for i in {1..100}; do sleep 1; if dig myservice; then exit 0; fi; exit 1
-
將 Pod 注冊到遠程服務器,通過在命令中調用 API,類似如下:
curl -X POST http://$MANAGEMENT_SERVICE_HOST:$MANAGEMENT_SERVICE_PORT/register -d 'instance=$(<POD_NAME>)&ip=$(<POD_IP>)'
-
在啟動應用容器之前等一段時間,使用類似
sleep 60
的命令。 -
克隆 Git 倉庫到數據卷。
-
將配置值放到配置文件中,運行模板工具為主應用容器動態地生成配置文件。例如,在配置文件中存放 POD_IP 值,並使用 Jinja 生成主應用配置文件。
更多詳細用法示例,可以在 StatefulSet 文檔 和 生產環境 Pod 指南 中找到。
使用 Init 容器
下面是 Kubernetes 1.5 版本 yaml 文件,展示了一個具有 2 個 Init 容器的簡單 Pod。 第一個等待 myservice
啟動,第二個等待 mydb
啟動。 一旦這兩個 Service 都啟動完成,Pod 將開始啟動。
apiVersion: v1 kind: Pod metadata: name: myapp-pod labels: app: myapp annotations: pod.beta.kubernetes.io/init-containers: '[ { "name": "init-myservice", "image": "busybox", "command": ["sh", "-c", "until nslookup myservice; do echo waiting for myservice; sleep 2; done;"] }, { "name": "init-mydb", "image": "busybox", "command": ["sh", "-c", "until nslookup mydb; do echo waiting for mydb; sleep 2; done;"] } ]' spec: containers: - name: myapp-container image: busybox command: ['sh', '-c', 'echo The app is running! && sleep 3600']
這是 Kubernetes 1.6 版本的新語法,盡管老的 annotation 語法仍然可以使用。我們已經把 Init 容器的聲明移到 spec
中:
apiVersion: v1 kind: Pod metadata: name: myapp-pod labels: app: myapp spec: containers: - name: myapp-container image: busybox command: ['sh', '-c', 'echo The app is running! && sleep 3600'] initContainers: - name: init-myservice image: busybox command: ['sh', '-c', 'until nslookup myservice; do echo waiting for myservice; sleep 2; done;'] - name: init-mydb image: busybox command: ['sh', '-c', 'until nslookup mydb; do echo waiting for mydb; sleep 2; done;']
注意:版本兼容性問題
1.5 版本的語法在 1.6 和 1.7 版本中仍然可以使用,但是我們推薦使用 1.6 版本的新語法。Kubernetes 1.8 以后的版本只支持新語法。在 Kubernetes 1.6 版本中,Init 容器在 API 中新建了一個字段。 雖然期望使用 beta 版本的 annotation,但在未來發行版將會被廢棄掉。
下面的 YAML 文件展示了 mydb
和 myservice
兩個 Service:
kind: Service apiVersion: v1 metadata: name: myservice spec: ports: - protocol: TCP port: 80 targetPort: 9376 --- kind: Service apiVersion: v1 metadata: name: mydb spec: ports: - protocol: TCP port: 80 targetPort: 9377
這個 Pod 可以使用下面的命令進行啟動和調試:
$ kubectl create -f myapp.yaml
pod "myapp-pod" created $ kubectl get -f myapp.yaml NAME READY STATUS RESTARTS AGE myapp-pod 0/1 Init:0/2 0 6m $ kubectl describe -f myapp.yaml Name: myapp-pod Namespace: default [...] Labels: app=myapp Status: Pending [...] Init Containers: init-myservice: [...] State: Running [...] init-mydb: [...] State: Waiting Reason: PodInitializing Ready: False [...] Containers: myapp-container: [...] State: Waiting Reason: PodInitializing Ready: False [...] Events: FirstSeen LastSeen Count From SubObjectPath Type Reason Message --------- -------- ----- ---- ------------- -------- ------ ------- 16s 16s 1 {default-scheduler } Normal Scheduled Successfully assigned myapp-pod to 172.17.4.201 16s 16s 1 {kubelet 172.17.4.201} spec.initContainers{init-myservice} Normal Pulling pulling image "busybox" 13s 13s 1 {kubelet 172.17.4.201} spec.initContainers{init-myservice} Normal Pulled Successfully pulled image "busybox" 13s 13s 1 {kubelet 172.17.4.201} spec.initContainers{init-myservice} Normal Created Created container with docker id 5ced34a04634; Security:[seccomp=unconfined] 13s 13s 1 {kubelet 172.17.4.201} spec.initContainers{init-myservice} Normal Started Started container with docker id 5ced34a04634 $ kubectl logs myapp-pod -c init-myservice # Inspect the first init container $ kubectl logs myapp-pod -c init-mydb # Inspect the second init container
一旦我們啟動了 mydb
和 myservice
這兩個 Service,我們能夠看到 Init 容器完成,並且 myapp-pod
被創建:
$ kubectl create -f services.yaml
service "myservice" created service "mydb" created $ kubectl get -f myapp.yaml NAME READY STATUS RESTARTS AGE myapp-pod 1/1 Running 0 9m
這個例子非常簡單,但是應該能夠為我們創建自己的 Init 容器提供一些啟發。
具體行為
在 Pod 啟動過程中,Init 容器會按順序在網絡和數據卷初始化之后啟動。每個容器必須在下一個容器啟動之前成功退出。如果由於運行時或失敗退出,將導致容器啟動失敗,它會根據 Pod 的 restartPolicy
指定的策略進行重試。然而,如果 Pod 的 restartPolicy
設置為 Always,Init 容器失敗時會使用 RestartPolicy
策略。
在所有的 Init 容器沒有成功之前,Pod 將不會變成 Ready
狀態。Init 容器的端口將不會在 Service 中進行聚集。 正在初始化中的 Pod 處於 Pending
狀態,但應該會將 Initializing
狀態設置為 true。
如果 Pod 重啟,所有 Init 容器必須重新執行。
對 Init 容器 spec 的修改被限制在容器 image 字段,修改其他字段都不會生效。更改 Init 容器的 image 字段,等價於重啟該 Pod。
因為 Init 容器可能會被重啟、重試或者重新執行,所以 Init 容器的代碼應該是冪等的。特別地當寫到 EmptyDirs
文件中的代碼,應該對輸出文件可能已經存在做好准備。
Init 容器具有應用容器的所有字段。除了 readinessProbe
,因為 Init 容器無法定義不同於完成(completion)的就緒(readiness)之外的其他狀態。這會在驗證過程中強制執行。
在 Pod 上使用 activeDeadlineSeconds
,在容器上使用 livenessProbe
,這樣能夠避免 Init 容器一直失敗。 這就為 Init 容器活躍設置了一個期限。
在 Pod 中的每個 app 和 Init 容器的名稱必須唯一;與任何其它容器共享同一個名稱,會在驗證時拋出錯誤。
資源
為 Init 容器指定順序和執行邏輯,下面對資源使用的規則將被應用:
- 在所有 Init 容器上定義的,任何特殊資源請求或限制的最大值,是 有效初始請求/限制
-
Pod 對資源的有效請求/限制要高於:
- 所有應用容器對某個資源的請求/限制之和
- 對某個資源的有效初始請求/限制
-
基於有效請求/限制完成調度,這意味着 Init 容器能夠為初始化預留資源,這些資源在 Pod 生命周期過程中並沒有被使用。
-
Pod 的 有效 QoS 層,是 Init 容器和應用容器相同的 QoS 層。
基於有效 Pod 請求和限制來應用配額和限制。Pod 級別的 cgroups 是基於有效 Pod 請求和限制,和調度器相同。
Pod 重啟的原因
Pod 重啟,會導致 Init 容器重新執行,主要有如下幾個原因:
- 用戶更新 PodSpec 導致 Init 容器鏡像發生改變。應用容器鏡像的變更只會重啟應用容器。
- Pod 基礎設施容器被重啟。這不多見,但某些具有 root 權限可訪問 Node 的人可能會這樣做。
- 當
restartPolicy
設置為 Always,Pod 中所有容器會終止,強制重啟,由於垃圾收集導致 Init 容器完整的記錄丟失。
支持與兼容性
API Server 版本為 1.6 或更高版本的集群,通過使用 spec.initContainers
字段來支持 Init 容器。之前的版本可以使用 alpha 和 beta 注解支持 Init 容器。spec.initContainers
字段也被加入到 alpha 和 beta 注解中,所以 Kubernetes 1.3.0 版本或更高版本可以執行 Init 容器,並且 1.6 版本的 API Server 能夠安全地回退到 1.5.x 版本,而不會使已創建的 Pod 失去 Init 容器的功能。