[Kubernetes] init ContainersでPythonの仮想環境をつくってみよう

みなさんはKubernetesでPodを作成するときに、アプリケーションのgit cloneなどPodの初期化処理はどのように行っていますか?Kubernetesにはinit Containersと呼ばれる初期化専用のコンテナがあります。

本記事では、init Containersを利用して、Pythonの仮想環境(venv)とモジュールのインストール(pip install)を行い、Python環境の初期化処理を行ってみます。

init Containersとは?

init ContainersはPodの初期化処理を行うために、一度だけ起動されるPod初期化専用のコンテナです。Podには複数のコンテナを動かすことができますが、コンテナが起動する順番を制御することはできません。init Containersはアプリケーションのコンテナが起動する前に起動するので、Podの初期化処理を行わせることができます。init Containersは通常のアプリケーションコンテナと同様に複数のinit Containerを動かすことができます。ただし、アプリケーションコンテナとは異なり、起動順番が制御されていて、1つのinit Containerの完了をまってから次のinit Containerが起動します。

init Containerの順番が制御されているのか、確認をしてみましょう。確認のため、2つのinit Containerを含むPodを作成します。初期化処理として単純に5秒間sleepを行います。

# file: initcontainers01.yaml
apiVersion: v1
kind: Pod
metadata:
  name: test-pod
  labels:
    app: test-pod
spec:
  initContainers:
  - name: init1
    image: alpine:3.9
    command:
    - "sh"
    - "-c"
    - "sleep 5"
  - name: init2
    image: alpine:3.9
    command:
    - "sh"
    - "-c"
    - "sleep 5"
  containers:
  - name: test-pod
    image: alpine:3.9
    command:
    - "sh"
    - "-c"
    - "touch a.b; tail -f a.b"

kubectl applyコマンドでPodを作成します。

$ kubectl apply -f initcontainers01.yaml
pod/test-pod created

コンテナの起動順番が制御されているかの確認を行います。Podの詳細情報で、コンテナの起動日時をみてみます。Podの詳細情報を確認するにはkubectl describeコマンドを利用します。以下の実行例はコンテナの起動日時の部分だけを抜粋したものです。

$ kubectl describe pods test-pod
...省略...
Init Containers:
  init1:
...省略...
      Started:      Tue, 11 Jun 2019 11:05:06 +0900
      Finished:     Tue, 11 Jun 2019 11:05:11 +0900
...省略...
  init2:
...省略...
      Started:      Tue, 11 Jun 2019 11:05:13 +0900
      Finished:     Tue, 11 Jun 2019 11:05:18 +0900
...省略...
Containers:
  test-pod:
...省略...
      Started:      Tue, 11 Jun 2019 11:05:19 +0900
...省略...

コンテナの起動日時を見てみると、init1 → init2 → test-podの順番に起動されていて、コンテナの起動順番は守られていることが確認できます。init Containersであるinit1とinit2が平行に起動されていなく、init1の完了を待ってinit2が起動されています。アプリケーションコンテナであるtest-podは、すべてのinit Containerが完了したあとに起動していることが確認できました。

containers.lifecycle.postStartとの違いは?

コンテナのライフサイクルフックにpostStartがあります。postStartはコンテナが作成された直後に何かしらの処理を実行させることができます。ただし、“コンテナが作成された直後”のため、コンテナのENTRYPOINTの実行前にpostStartが実行される保証はありません。つまり、ENTRYPOINT実行前に初期化処理をする目的でpostStartを実行するには、ENTRYPOPINTが実行されたかの確認をpostStartのコマンド内で実装する必要があります。

これに対してinit Containersはアプリケーションコンテナの起動前に実行されるので、確実にアプリケーションコンテナのENTRYPOINT実行前に初期化処理を行うことができます。

Pythonの仮想環境をinit Containerで作ってみる

検証構成

検証にはDocker Desktop for WindowsのKubernetes環境を利用しました。

$ kubectl version --short
Client Version: v1.14.1
Server Version: v1.10.11
$ kubectl config get-clusters
NAME
docker-for-desktop-cluster

Pythonを動作させるアプリケーションサーバにはNginx Unitを利用しました。https://www.nginx.com/products/nginx-unit/

初期化処理の流れ

以下の初期化処理を行うPodを作成します。

init Conteiners

  1. GitHubからアプリケーションをcloneする。
  2. Pythonの仮想環境(venv)を作成する。
  3. Pythonの仮想環境を有効にする。
  4. pipでPythonのモジュール(Flask)をインストールする。

containers.lifecycle.postStart

  1. curlでNginx Unitの設定を反映する。

 

init Containerとアプリケーションコンテナで同じEmptyDirのボリュームをマウントすることで、init Containerで初期化した環境とアプリケーションをアプリケーションコンテナでも利用できるようにします。

ConfigMapを作成する

Nginx Unitの設定はConfigMapで定義しています。Pythonのアプリケーションは”path”に指定したディレクトリに配置します。今回の検証ではアプリケーションの配布をPodのinitContainersのgit cloneコマンドで行います。また、Python仮想環境のパスを”home”に設定しますが、Pythonの仮想環境もPodのinitContainersのコマンドで作成します。

#file:nginx-unit-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: nginx-unit-json
data:
  unit.conf.json: |-
    {
      "listeners": {
        "*:8300": {
          "pass": "applications/flask"
        }
      },  
      "applications": {
        "flask": {
          "type": "python 3.5",
          "processes": 1,
          "working_directory": "/app/python",
          "path": "/app/python/flask",
          "home": "/app/python/venv",
          "module": "index",
          "user": "root",
          "group": "root",
        }
      },
    }

ファイルを作成したら、kubectl applyコマンドでConfigMapを作成します。

$ kubectl apply -f nginx-unit-configmap.yaml
configmap/nginx-unit-json created

Podを作成する

Podの定義を行います。Podには初期化用のinit Containerとアプリケーション用のコンテナが含まれています。

# file: nginx-unit.yaml
apiVersion: v1
kind: Pod
metadata:
  name: nginx-unit
  labels:
    app: nginx-unit
spec:
  initContainers:
    - name: init-client
      image: python:3.5-alpine
      command:
        - "/bin/sh"
        - "-c"
        - "apk add git; \
           git clone https://github.com/easydoggie/testapp.git /tmp/git; \
           python -m venv /tmp/git/python/venv; \
           . /tmp/git/python/venv/bin/activate; \
           pip install Flask"
      volumeMounts:
        - name: nginx-unit-storage
          mountPath: /tmp/git
  containers:
    - name: nginx-unit
      image: nginx/unit:1.9.0-python3.5
      lifecycle:
        postStart:
            exec: 
                command:
                    - sh
                    - -c
                    - "while [ ! -e /var/run/control.unit.sock ]; do sleep 1; done  \
                       && curl -X PUT --data-binary @/tmp/unit.conf.json --unix-socket /var/run/control.unit.sock http://localhost/config"
      ports:
      - name: http
        containerPort: 8300
        protocol: TCP
      volumeMounts:
        - name: nginx-unit-storage
          mountPath: /app/python
          subPath: python
        - name: nginx-unit-config
          mountPath: /tmp
  volumes:
    - name: nginx-unit-storage
      emptyDir: {}
    - name: nginx-unit-config
      configMap:
        name: nginx-unit-json

init Containerで利用したpythonのベースイメージにはEntrypointは設定されていません。Entrypointが設定されていないので、initContainers.commandにPodの初期化処理を記述していきます。Gitのクローン元や必要なPythonパッケージは、利用する環境に合わせて修正してください。初期化処理の概要は、以下のコメントを参照してください。

- "apk add git; \\ #ベースイメージにGitがないのでGitをインストール
   git clone https://github.com/easydoggie/testapp.git /tmp/git; \ #アプリケーションをgit clone
   python -m venv /tmp/git/python/venv; \ #Pythonの仮想環境を作成
   . /tmp/git/python/venv/bin/activate; \ #Pythonの仮想環境を有効化
   pip install Flask" #Pythonのパッケージをインストール

init Containerでの初期化が完了した後、アプリケーションのコンテナが起動します。アプリケーションコンテナが起動するタイミングで、lifecycle.postStartでNginx Unitの設定を反映させています。postStartはコンテナのEntrypoint(プロセス実行)の前に実行される保証がないため、postStart内でNginx Unitの起動をwhileループで確認しています。Nginx Unitの起動を確認した後に、curlコマンドでNginx Unitへ設定を反映させます。

lifecycle:
  postStart:
    exec: 
      command:
        - sh
        - -c
        - "while [ ! -e /var/run/control.unit.sock ]; do sleep 1; done  \ #Nginx Unitの起動確認
           && curl -X PUT --data-binary @/tmp/unit.conf.json --unix-socket /var/run/control.unit.sock <http://localhost/config>"

ファイルを作成したら、kubectl applyコマンドでPodを作成します。

$ kubectl apply -f nginx-unit-pod.yaml
pod/nginx-unit created

init ContainersによるPodの初期化処理が最初に実行されるため、Podの起動までに多少の時間がかかります。kubectl get podsコマンドのSTATUSがInitの時は、初期化処理の実施中です。STATUSがRunningになれば、Podの起動が完了しています。

$ kubectl get pods
NAME         READY   STATUS     RESTARTS   AGE
nginx-unit   0/1     Init:0/1   0          15s
$ kubectl get pods
NAME         READY   STATUS    RESTARTS   AGE
nginx-unit   1/1     Running   0          19s

Serviceを作成する

アクセス確認をするためのServiceの設定を行います。今回の検証ではNodePortを利用しています。

apiVersion: v1
kind: Service
metadata:
  name: testservice
spec:
  type: NodePort
  ports:
  - protocol: TCP
    port: 8300
    targetPort: 8300
    nodePort: 30080
  selector:
    app: nginx-unit

ファイルを作成したら、kubectl applyコマンドでServiceを作成します。

$ kubectl apply -f nginx-unit-service.yaml
service/testservice created

動作確認

http://localhost:30080にアクセスして、動作確認をしてみます。画面に’Hello, World!’と表示されれば、init Containersの初期化処理で作成したPythonの仮想環境上のFlaskアプリケーションが正常に動作しています。

$ curl http://localhost:30080
Hello, World!

Helm Chart

本記事ではPythonアプリケーションを直接kubectlコマンドでインストールする方法を紹介しました。別の方法としてHelmを利用してKubernetesにアプリケーションをインストールする方法もあります。

GitHub上にNginx Unit + PHPアプリケーション(Adminer)を、本記事と同様にinit Containerで初期化するHelm Chartを公開しています。Helm Chartを使ってinit Containerを実装する際の参考にしてください。

https://github.com/easydoggie/EasyDoggie/tree/master/adminer/adminer

Helmのインストール方法は、別記事を参照してください。

さいごに

KUbernetesのアプリケーション初期化処理の一つの方法として、init ContainersとpostStartの利用例を紹介しました。postStartは処理が実行されるタイミングがコンテナ作成直後で、Entrypointの前に実行される保証がないことに注意が必要です。Podの初期化処理は、確実に起動順番が制御できるinit Containersで実装できれば確実です。コンテナのライフサイクルや実行順番の保証などを意識して、安全かつ安心して運用できるコンテナを作っていきましょう。

投稿者プロフィール

石川 淳
スカイアーチネットワークスで、新しいサービスの企画を行っています。
元SIer、元スマフォ向けゲームインフラの経験を生かして、新しいサービスをリリースしていきます。現在企画中のサービスはこちら。
https://github.com/easydoggie
コメントを頂けると嬉しいです。

コメントを残す

メールアドレスが公開されることはありません。

Time limit is exhausted. Please reload CAPTCHA.

ABOUTこの記事をかいた人

スカイアーチネットワークスで、新しいサービスの企画を行っています。 元SIer、元スマフォ向けゲームインフラの経験を生かして、新しいサービスをリリースしていきます。現在企画中のサービスはこちら。 https://github.com/easydoggie コメントを頂けると嬉しいです。