blog.petitviolet.net

KubernetesにおけるPodの初期化処理

2018-12-04

Qiitakubernetes

これはなに?

本記事では Kubernetes における Pod という形でアプリケーションを起動する際、どのように初期化処理を実行できるのかについて取り上げる。 なお終了処理は触れない。

lifecycle.postStart を使う

spec.containers.lifecycle.postStartを利用すればライフサイクルにおける”起動”直後に何かしら処理を差し込むことが可能。 これは Pod の起動、ENTRYPOINT や CMD と同時(非同期)に実行される。 ドキュメント: Attach Handlers to Container Lifecycle Events - Kubernetes

postStart で初期化処理をする deployment の yaml 例を書いてみる。 サンプルのファイルはGithubにおいてある。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: python
  labels:
    name: python
spec:
  replicas: 1
  selector:
    matchLabels:
      app: python
  template:
    metadata:
      labels:
        app: python
    spec:
      containers:
        - name: python
          image: python:3.7.1
          tty: true
          lifecycle:
            postStart:
              exec:
                command:
                  - sh
                  - -c
                  - "mkdir -p /var/log/backup/${HOSTNAME} \
                    && mv /var/log/python.log /var/log/backup/${HOSTNAME} \
                    && echo START! >> /var/log/python.log"
          volumeMounts:
            - name: log
              mountPath: /var/log
          command:
            - sh
            - -c
            - "python -m http.server 8080 --directory /var/log/ 1>/var/log/python.log 2>/var/log/python.log"
          volumeMounts:
            - name: log
              mountPath: /var/log
      volumes:
        - name: log
          hostPath:
            path: /var/log/python
            type: DirectoryOrCreate

Pod そのものは Python で Web サーバーを立ち上げるだけだが、postStart においてログファイルをバックアップとっている。 kubernetes のサンプルとしてこれが正しいかどうかはさておき…。

apply して様子を見てみる。

$ kubectl apply -f post_start.yaml
...
$ kubectl get pod
NAME                      READY     STATUS    RESTARTS   AGE
python-7d57c47cb9-d7zxr   1/1       Running   0          1m
$ kubectl exec -it python-7d57c47cb9-d7zxr -- ls -lh /var/log/backup
total 4.0K
drwxr-xr-x 2 root root 4.0K Dec  1 13:08 python-7d57c47cb9-d7zxr

kubectl execしてlsした結果、バックアップ処理が正常に動いているのがわかる。

postStart の問題点

ドキュメントによると以下のような事が書いてある。

Kubernetes sends the postStart event immediately after the Container is created. There is no guarantee, however, that the postStart handler is called before the Container’s entrypoint is called.

つまり、コンテナ(Pod)の entrypoint より先に実行される保証がないということ。 今回の例で考えて見ると、アプリケーションが起動する前に以前のログをバックアップとっておきたいが、postStart の実行タイミングによってはアプリケーション起動後に実行される可能性があるということ。 そこで欲しくなってくるのは ENTRYPOINT より前に実行されることが保証された初期化処理の設定。 また、postStart で使用したいソフトウェアは Pod の実行に本来不要なソフトウェアが必要になる可能性もある。

初期化処理を別コンテナで

postStart の欠点を補うことが出来るのが、Init Containers というもの。 Pod の初期化処理を実行するコンテナを実行することが出来る。 ドキュメント:Init Containers - Kubernetes

Pod 自体の機能としていくつかのコンテナをまとめて動かすことが出来るが、コンテナ群の初期化順は決まっておらず、さらに非同期に初期化されるため、初期化処理をするコンテナを Pod そのものに組み込んでも期待するような初期化を保証することが出来ない。 さらに何かしらの初期化処理だけで必要なソフトウェアがあるなどする場合に、そのためだけに本来不要なコマンドを Docker イメージに追加しておくなどが必要になるケースもある。 たとえば設定ファイルを外部から読み込んだり、Git リポジトリを clone してきたりしたいようなケース。 具体的に後者だと Web アプリを動かすのに Git コマンドは必要ないが、初期化のために必要になってしまう。

そういった時に Init Containers が使える。

Init Containers を使った初期化を行ってみる。 こちらのサンプル用ファイルもGithubにあげてある。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: python
  labels:
    name: python
spec:
  replicas: 1
  selector:
    matchLabels:
      app: python
  template:
    metadata:
      labels:
        app: python
    spec:
      initContainers:
        - name: git
          image: alpine/git
          command:
            - git
            - clone
            - https://github.com/petitviolet/kubernetes_prac.git
            - /tmp/git/
          volumeMounts:
            - name: storage
              mountPath: /tmp/git/
      containers:
        - name: python
          image: python:3.7.1
          tty: true
          command:
            - sh
            - -c
            - "python -m http.server 8080 --directory /www/public"
          volumeMounts:
            - name: storage
              mountPath: /www/public
      volumes:
        - name: storage
          emptyDir: {}

先ほどとは少し違って、git cloneをしている。 必要なファイルを git リポジトリから取得してくる、というような初期化処理。

起動してみると、正しく動作していることが確認できる。

$ kubectl apply -f ./init_container.yaml
kudeployment "python" created
service "python-service" created
$
$ kubectl get po -w
NAME                      READY     STATUS    RESTARTS   AGE
python-86cc996576-glntt   0/1       Pending   0          0s
python-86cc996576-glntt   0/1       Init:0/1   0         0s
python-86cc996576-glntt   0/1       PodInitializing   0         17s
python-86cc996576-glntt   1/1       Running   0         18s
^C%
$
$ curl $(minikube service python-service --url)/message.txt
Hello, initContainer!

最後に curl でとってきているのはGithub に置いた message.txt。 ちゃんと Github からgit clone出来ていることがわかる。

また、kubectl get po -wの様子から、Pod の STATUS が Pending→Init→PodInitializing→Running と遷移しているのがわかる。 ここで注目したいのが Init という STATUS があること。 lifecycle.postStartと異なり、初期化用のステータスがあり、それが終わらないと本体の Pod の起動に遷移しないことが保証できている。 ちなみにInit:0/1となっているのは Init Containers が 1 個あってそのうち 0 個が完了していることを示す。 なので Init Containers を複数個用意すれば分母は変わる。

また、Init に使われたコンテナのログや詳細なステータスkubectlコマンド経由で取得することは出来る。

$ kubectl logs python-86cc996576-glntt -c git
Cloning into '/tmp/git'...
$ kubectl get pod python-86cc996576-glntt -ojson | jq '.status.initContainerStatuses'
[
  {
    "containerID": "docker://3685acb1a79188c840d6723f2b29b4aaaa876f561ab832732c455512cd2d9ff1",
    "image": "alpine/git:latest",
    "imageID": "docker-pullable://alpine/git@sha256:d76d5ab84de3a35514f8621df4550c59680c3bb623e8c1332ed7af39e33afb0b",
    "lastState": {},
    "name": "git",
    "ready": true,
    "restartCount": 0,
    "state": {
      "terminated": {
        "containerID": "docker://3685acb1a79188c840d6723f2b29b4aaaa876f561ab832732c455512cd2d9ff1",
        "exitCode": 0,
        "finishedAt": "2018-12-02T02:13:50Z",
        "reason": "Completed",
        "startedAt": "2018-12-02T02:13:49Z"
      }
    }
  }
]

使いどころとしては

  • 確実に初期化処理を本体の Pod 起動前に完了することを保証したい場合
  • 本体の Pod に持たせたくないソフトウェアが初期化に必要な場合

というあたり。

まとめ

Pod の初期化処理に使えるのは 2 つ。

  • spec.containers.lifecycle.postStart

    • Pod の起動と同時に実行される
    • 実行は Pod の各コンテナ内
  • Init Containers

    • Pod の本体のコンテナ起動前の Pending フェーズで実行される
    • 実行は Pod とは別のコンテナ

それぞれキックされるタイミング等が異なるので用途に応じて使い分けることが大事。

from: https://qiita.com/petitviolet/items/41aa9abe106a29ba4667