kubernetes之初始容器(init container)

周國通發表於2019-06-12

系列目錄

理解初始容器

一個pod裡可以執行多個容器,它也可以執行一個或者多個初始容器,初始容器先於應用容器執行,除了以下兩點外,初始容器和普通容器沒有什麼兩樣:

  • 它們總是run to completion

  • 一個初始容器必須成功執行另一個才能執行

如果pod中的一個初始容器執行失敗,則kubernetes會嘗試重啟pod直到初始容器成功執行,如果pod的重啟策略設定為從不(never),則不會重啟.

建立容器時,在podspec裡新增initContainers欄位,則指定容器即為初始容器,它們的返回狀態作為陣列儲存在.status.initContainerStatuses裡(與普通容器狀態儲存欄位.status.containerStatuses類似)

初始容器和普通容器的不同:

初始容器支援所有普通容器的特徵,包括資源配額限制和儲存卷以及安全設定.但是對資源申請和限制處理初始容器略有不同,下面會介紹.此外,初始容器不支援可用性探針(readiness probe),因為它在ready之前必須run to completion

如果在一個pod裡指定了多個初始容器,則它們會依次啟動起來(pod內的普通容器並行啟動),並且只有上一個成功下一個才能啟動.當所有的初始容器都啟動了,kubernetes才開始啟普通應用容器.

初始容器能做什麼

由於初始容器和普通應用容器是分開的映象,因此他在做一些初始化工作很有優勢:

  • 它們可以包含並且執行一些出於安全考慮不適合和應用放在一塊的小工具.

  • 它們可以一些小工具和自定義程式碼來做些初始化工作,這樣就不需要在普通應用容器裡使用sed,awk,python或者dig來做初始化工作了

  • 應用構建者和釋出者可以獨立工作,而不必再聯合起來處理同一個pod

  • 它們使用linux namespaces因此它們和普通應用pod擁有不同的檔案系統檢視.因此他們可以被賦予普通應用容器獲取不到的secrets

  • 它們在應用容器啟動前執行,因此它們可以阻止或者延緩普通應用容器的初始化直到需要的條件滿足

示例:

  • 通過執行shell命令來等待一個服務建立完成,命令如下:
for i in {1..100}; do sleep 1; if dig myservice; then exit 0; fi; done; exit 1
  • 通過downward API把當前pod註冊到遠端伺服器,命令如下:
curl -X POST http://$MANAGEMENT_SERVICE_HOST:$MANAGEMENT_SERVICE_PORT/register -d 'instance=$(<POD_NAME>)&ip=$(<POD_IP>)'
  • 在容器啟動之前等待一定時間:例如sleep 60

  • 克隆一個git倉庫到儲存目錄

  • 通過模板工具動態把一些值寫入到主應用程式的配置檔案裡.

更多詳細示例請檢視pod應用環境佈置指南

初始容器使用

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;']

以上pod定義包含兩個初始容器,第一個等待myservice服務可用,第二個等待mydb服務可用,這兩個pod執行完成,應用容器開始執行.

下面是myservicemydb兩個服務的yaml檔案

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
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

當我們啟動mydbmyservice兩個服務後,我們可以看到初始容器完成並且myapp-pod 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

這些示例非常簡單但是應該能為你建立自己的初始容器提供一些靈感

行為細節

  • 在啟動pod的過程中,在儲存卷和網路建立以後,初始容器依次建立.上一個容器必須返回成功下一個才能啟動,如果由於執行時錯誤或者其它異常退出,它會依照restartPolicy來重試,然而,如果restartPolicy設定為Always,初始容器實際上使用的是OnFailure策略

  • 如果pod重啟了,則所有的初始容器要重新執行

  • 對初始容器的spec的更改僅限於映象(image)欄位的修改,更改了初始容器的映象欄位相當於重啟pod

  • 由於初始容器可以被重啟,重試和重新執行,因此它裡面的程式碼應當是冪等的,尤其是寫入檔案到EmptyDirs的程式碼應當注意檔案可能已經存在

  • 容器中的所有初始容器和普通容器名稱必須惟一.

資源

基於初始容器的執行順序,以下關於資源的規則適用:

  • 對於特定資源,所有初始容器申請的最高的生效

  • 對於pod,相同資源申請取以下兩者較高的一個:

    1) 所有普通應用容器申請的資源總和
    2) 初始容器申請的生效的資源(上面說到,初始容器申請資源取所有初始容器申請最大的一個)

  • 排程基於生效的初始請求,這就意味著初始容器可以申請預留資源,即便在pod以後的整個生命週期都用不到

pod重啟原因

一個pod基於以下列出的原因,會重啟,重新執行初始容器:

  • 使用者更新初始容器的PodSpec導致映象發生改變.普通應用容器改變只會使應用容器重啟

  • 由於restartPolicy被設定為Always,導致所有容器均被中止,強制重啟,由於垃圾回收初始容器的初始狀態記錄丟失

相關文章