petitviolet blog

    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