みなさんは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を行います。
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 |
# 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を作成します。
1 2 |
$ kubectl apply -f initcontainers01.yaml pod/test-pod created |
コンテナの起動順番が制御されているかの確認を行います。Podの詳細情報で、コンテナの起動日時をみてみます。Podの詳細情報を確認するにはkubectl describe
コマンドを利用します。以下の実行例はコンテナの起動日時の部分だけを抜粋したものです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
$ 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環境を利用しました。
1 2 3 4 5 6 |
$ 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
- GitHubからアプリケーションをcloneする。
- Pythonの仮想環境(venv)を作成する。
- Pythonの仮想環境を有効にする。
- pipでPythonのモジュール(Flask)をインストールする。
containers.lifecycle.postStart
- curlでNginx Unitの設定を反映する。
init Containerとアプリケーションコンテナで同じEmptyDirのボリュームをマウントすることで、init Containerで初期化した環境とアプリケーションをアプリケーションコンテナでも利用できるようにします。
ConfigMapを作成する
Nginx Unitの設定はConfigMapで定義しています。Pythonのアプリケーションは"path"に指定したディレクトリに配置します。今回の検証ではアプリケーションの配布をPodのinitContainersのgit cloneコマンドで行います。また、Python仮想環境のパスを"home"に設定しますが、Pythonの仮想環境もPodのinitContainersのコマンドで作成します。
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 |
#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を作成します。
1 2 |
$ kubectl apply -f nginx-unit-configmap.yaml configmap/nginx-unit-json created |
Podを作成する
Podの定義を行います。Podには初期化用のinit Containerとアプリケーション用のコンテナが含まれています。
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 49 |
# 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パッケージは、利用する環境に合わせて修正してください。初期化処理の概要は、以下のコメントを参照してください。
1 2 3 4 5 |
- "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へ設定を反映させます。
1 2 3 4 5 6 7 8 |
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を作成します。
1 2 |
$ kubectl apply -f nginx-unit-pod.yaml pod/nginx-unit created |
init ContainersによるPodの初期化処理が最初に実行されるため、Podの起動までに多少の時間がかかります。kubectl get pods
コマンドのSTATUSがInitの時は、初期化処理の実施中です。STATUSがRunningになれば、Podの起動が完了しています。
1 2 3 4 5 6 |
$ 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を利用しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
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を作成します。
1 2 |
$ kubectl apply -f nginx-unit-service.yaml service/testservice created |
動作確認
http://localhost:30080にアクセスして、動作確認をしてみます。画面に'Hello, World!'と表示されれば、init Containersの初期化処理で作成したPythonの仮想環境上のFlaskアプリケーションが正常に動作しています。
1 2 |
$ 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で実装できれば確実です。コンテナのライフサイクルや実行順番の保証などを意識して、安全かつ安心して運用できるコンテナを作っていきましょう。
投稿者プロフィール
最新の投稿
- AWS2021年12月2日AWS Graviton3 プロセッサを搭載した EC2 C7g インスタンスが発表されました。
- セキュリティ2021年7月14日ゼロデイ攻撃とは
- セキュリティ2021年7月14日マルウェアとは
- WAF2021年7月13日クロスサイトスクリプティングとは?