この記事は Kubernetes道場 Advent Calendar 2018 6日目の記事です。

今回はPodで指定できるInit ContainerとPodのLifecycleについて。

Init Containerについて

Init ContainerはPodの containers で指定したコンテナが起動する前に初期化処理を目的として起動することができる。

以下のような特徴がある。

  • Podのコンテナが起動する前に実行される
  • 複数のInit Containerが指定されている場合、順に実行される
  • Podの restartPolicyAlways の場合、Init Containerでは OnFailure が使用される

Init Containerでアプリケーションのコンテナと分離して実行する利点は以下のようなものがある。

  • セキュリティ的な理由からアプリケーションのコンテナとツール系を含むコンテナを分離
  • アプリケーションのコンテナに含まれていないツールやコードを使用して初期化処理をしたい場合

Init Containerは再起動や再実行されることがありえるため、初期化処理を実装する際は冪等性を考慮しなければならないことを注意しよう。

Init Containerを使ってみる

前回の 5日目のVolumeについての記事 でファイルを追記してそのファイルをtailするPodを作成した。

しかしそのPodだが、並行して実行されること考えるとtailコンテナが先に実行する可能性だってある。 その場合ファイルが存在しないためtailコンテナは失敗してしまう。 (しかし、Kubernetesが失敗を検知して再起動してくれる。素晴らしい!)

さて、このPodの初期化処理としてInit Containerでファイルを作成しよう。以下のようなManifestファイルを作成した。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
apiVersion: v1
kind: Pod
metadata:
  name: date-tail
spec:
  initContainers:
  - name: touch
    image: alpine
    command: ["sh", "-c"]
    args:
    - |
      echo Initialize... (Started at `date`)
      touch /var/log/date-tail/output.log
    volumeMounts:
    - name: log-volume
      mountPath: /var/log/date-tail
  containers:
  - name: date
    image: alpine
    command: ["sh", "-c"]
    args:
    - |
      echo Append log... (Started at `date`)
      exec >> /var/log/date-tail/output.log
      echo -n 'Start at: '
      while true; do
        date
        sleep 1
      done
    volumeMounts:
    - name: log-volume
      mountPath: /var/log/date-tail
  - name: tail
    image: alpine
    command: ["sh", "-c"]
    args:
    - |
      echo Following... (Started at `date`)
      tail -f /var/log/date-tail/output.log
    volumeMounts:
    - name: log-volume
      mountPath: /var/log/date-tail
  volumes:
  - name: log-volume
    emptyDir:
  terminationGracePeriodSeconds: 0

ありがたみは少ない感じがあるが。Init Containerで対象の出力先のファイルを作成している。 (結果もそんなに変わらないので省略させてもらう。)

もう一つ、Init Contaierが順番に実行されるのを確認してみよう。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
apiVersion: v1
kind: Pod
metadata:
  name: initcontainer-test
spec:
  initContainers:
  - name: step1
    image: alpine
    command: ["sh", "-c"]
    args:
    - |
      echo Start at `date`; sleep 5; echo End. 
  - name: step2
    image: alpine
    command: ["sh", "-c"]
    args:
    - |
      echo Start at `date`; sleep 5; echo End. 
  - name: step3
    image: alpine
    command: ["sh", "-c"]
    args:
    - |
      echo Start at `date`; sleep 5; echo End. 
  containers:
  - name: app1
    image: alpine
    command: ["sh", "-c"]
    args:
    - |
      echo Start at `date`; sleep 5; echo End. 
  - name: app2
    image: alpine
    command: ["sh", "-c"]
    args:
    - |
      echo Start at `date`; sleep 5; echo End. 
  restartPolicy: Never
  terminationGracePeriodSeconds: 0

このPodを作成して各コンテナのログを確認してみよう。

また、作成してすぐに kubectl get po -w を実行すると、どの様に実行されてるかわかりやすいと思う。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
$ kubectl apply -f initcontainer-test.yaml && kubectl get po -w
pod/initcontainer-test created
NAME                 READY     STATUS     RESTARTS   AGE
initcontainer-test   0/2       Init:0/3   0          0s
initcontainer-test   0/2       Init:0/3   0         3s
initcontainer-test   0/2       Init:1/3   0         8s
initcontainer-test   0/2       Init:1/3   0         11s
initcontainer-test   0/2       Init:2/3   0         16s
initcontainer-test   0/2       Init:2/3   0         18s
initcontainer-test   0/2       PodInitializing   0         23s
initcontainer-test   2/2       Running   0         26s
initcontainer-test   1/2       Running   0         30s
initcontainer-test   0/2       Completed   0         31s
^C
$ kubectl logs initcontainer-test -c step1
Start at Wed Dec 5 16:43:50 UTC 2018
End at Wed Dec 5 16:43:55 UTC 2018.
$ kubectl logs initcontainer-test -c step2
Start at Wed Dec 5 16:43:57 UTC 2018
End at Wed Dec 5 16:44:02 UTC 2018.
$ kubectl logs initcontainer-test -c step3
Start at Wed Dec 5 16:44:04 UTC 2018
End at Wed Dec 5 16:44:09 UTC 2018.
$ kubectl logs initcontainer-test -c app1
Start at Wed Dec 5 16:44:11 UTC 2018
End at Wed Dec 5 16:44:16 UTC 2018.
$ kubectl logs initcontainer-test -c app2
Start at Wed Dec 5 16:44:12 UTC 2018
End at Wed Dec 5 16:44:17 UTC 2018.

上記のようにInit Containerの処理では initContainers で指定した順番で処理が実行されていることが分かる。 Init Containerでは1つずつコンテナが実行されており、完了してから次のコンテナが起動し処理をしている。

また、アプリケーションのコンテナでは並行して処理されていることも確認できる。

Init Containerについては一旦ここまで。

Lifecycleについて

今度はPodのLifecycleについて。

Podにはいくつかの phase がある。

  • Pending : Kubernetesによって承認されたが、一つ以上のコンテナが作成されていない状態。ImageのPullやInit Containerの処理中がこの状態にあたる
  • Running : コンテナが作成され、少なくとも1つのコンテナが実行されているか開始・再起動中の状態
  • Succeeded: Pod内の全てのコンテナが正常に終了し、再起動されていない状態
  • Failed : Pod内の全てのコンテナが終了し、少なくとも1つ以上のコンテナの終了ステータスが0以外だったか、Kubernetesによって強制終了させられた場合この状態になる
  • Unknown : 何らかの理由によりPodのサーバーと通信が失敗し、Podの状態を取得できなかった際の状態

Lifecycle Events

また、コンテナが開始された際とPodが削除される際にHookして任意の処理を実行することが出来る。

  • postStart : コンテナが作成された後、即座に実行される。このHandlerが失敗した場合終了させ、 restartPolicy に従い再起動させる
  • preStop : コンテナが終了される前に実行される。コンテナのルートプロセスに対して SIGTERM が送出される前にこのHandlerが実行される。

大まかなPod起動までの流れとLifecycle Eventのタイミングは以下の通り。 (ちょっと雑すぎる気もするけど 😅)

終了処理の部分の preStop についてもう少し。

preStop が定義されている場合それを実行する。 ここでコンテナの終了処理を行うが、 preStop が先に終了してしまうとコンテナ側が処理中だとしてもKubernetesはコンテナに対してSIGTERMを送信してしまう。 これを防ぎたい場合は、 preStop 側でSleepさせるなどの待機処理を入れて待たせる必要がある。

また、 terminationGracePeriodSeconds や kubectlで指定されたGracePeriodの指定時間を超えても preStop が終了しない場合は、 GracePeriodを少し伸ばし(2秒)、コンテナのプロセスSIGTERMを送信する。

このPodの終了処理の部分については以下の記事が非常に詳しく解説されているので是非参考に。

Kubernetes: 詳解 Pods の終了 - Qiita

Lifecycle Handler

postStartpreStop に指定できるHandlerの種類は以下の通り。

  • exec : コンテナ内でコマンドを実行
  • httpGet : HTTPのGETリクエストを発行

exec を指定し、異常終了した際には kubectl describe コマンドにてHandlerのログが確認できる。正常終了時には確認できないため要注意。

以下に lifecycle を使ったManifestを置いておく。 exit コマンドの数値を変えてログの確認や挙動の確認など試してみると良いと思う。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
apiVersion: v1
kind: Pod
metadata:
  name: lifecycle-check
spec:
  initContainers:
  - name: init
    image: alpine
    command: ["sh", "-c"]
    args:
    - |
      echo Start at `date`; sleep 3; echo End at `date`. ; exit 0
  containers:
  - name: app
    image: alpine
    command: ["sh", "-c"]
    args:
    - |
      trap 'echo "Trapped SIGTERM"; sleep 3; echo "See you!!"; exit 0' TERM

      echo Start.

      while true
      do
          date
          sleep 1
      done
    lifecycle:
      postStart:
        exec:
          command:
            - sh
            - -c
            - |
              echo hook postStart.
              date
              exit 0
      preStop:
        exec:
          command:
            - sh
            - -c
            - |
              echo hook preStop.
              date
              exit 0
  restartPolicy: Never
  terminationGracePeriodSeconds: 30

というわけで今回はここまで。

次回は Resource Requirements と Security Context について見ていこう。

それでは。