これはなに?
本記事では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とは別のコンテナ
それぞれキックされるタイミング等が異なるので用途に応じて使い分けることが大事。