マルチコンテナPod — init、サイドカー、アンバサダー、アダプタ
目標
1つのPodの中に複数のコンテナを置くべき理由をパターンごとに区別し、initコンテナ・ネイティブサイドカー・アンバサダー・アダプターをマニフェストとして自分で書けるようになります。
なぜ重要なのか
Podの中にコンテナを2つ置くという判断は、無料ではありません。2つのコンテナは一緒にスケジュールされ、一緒に再起動され、一緒に削除されます。そのため、「同じノードにある必要があるか」と「ライフサイクルが同じである必要があるか」の両方を満たすときだけ、1つのPodに入れます。ログ収集ツールはアプリが書いたファイルを読む必要があるので同じノードでなければならず、アプリが消えれば読むものがなくなるのでライフサイクルも同じです。一方、キャッシュサーバーは複数のアプリが共有するので、別のPodにします。
initコンテナとサイドカーを分ける基準は、終わるかどうかです。終わるべき作業(マイグレーション、設定の準備、先行サービスの待機)はinitで、終わってはいけない作業(ログ転送、プロキシ)はサイドカーです。以前はサイドカーをcontainersに入れるしかなく、起動順序が保証されず、Jobが終わらない問題がありました。いまはinitContainersに入れてrestartPolicy: Alwaysを指定すると、「先に起動し、動き続け、メインが終わると一緒に終わる」コンテナになります。
アンバサダーとアダプターは向きが違います。アンバサダーは外へ出ていく通信を、アダプターは外へ出ていくデータの形式を代わりに処理します。どちらもアプリのコードに手を入れずに、環境の違いを吸収することが目的です。
ステップ
- ネームスペース
ckad-multiを作成してください。 - Pod
app-initを作成してください。initコンテナprep(イメージbusybox:1.36、command: ["/bin/sh","-c","echo ready > /work/state"])とメインコンテナapp(イメージnginx:1.27)が、emptyDirボリュームworkdirをどちらも/workにマウントします。 - Pod
ordered-initを作成してください。initコンテナを順番にwait-config、migrateの2つ置き(どちらもbusybox:1.36、commandは["/bin/sh","-c","true"])、メインコンテナappはnginx:1.27にしてください。 - Pod
log-shipperを作成してください。ネイティブサイドカーloggerをinitContainersに入れてrestartPolicy: Alwaysを指定します(イメージbusybox:1.36、command: ["/bin/sh","-c","tail -F /var/log/nginx/access.log"])。メインコンテナwebはnginx:1.27です。emptyDirボリュームvarlogを両方のコンテナで/var/log/nginxにマウントしてください。 - Pod
ambassador-podを作成してください。メインコンテナapp(busybox:1.36、command: ["/bin/sh","-c","sleep 3600"])に環境変数REDIS_ADDR=localhost:6379を指定し、2つ目のコンテナproxy(nginx:1.27)にcontainerPort: 6379、name: redis-proxyを指定してください。 - Pod
adapter-podを作成してください。メインコンテナapp(busybox:1.36、command: ["/bin/sh","-c","sleep 3600"])とアダプターコンテナmetrics-adapter(nginx:1.27)が、emptyDirボリュームapplogをどちらも同じパス(/var/log/app)にマウントします。アダプターにはcontainerPort: 9113、name: metricsを指定してください。 - Pod
gracefulを作成してください。ネイティブサイドカーdrainer(initContainersの中、restartPolicy: Always、busybox:1.36、command: ["/bin/sh","-c","sleep 3600"])とメインコンテナweb(nginx:1.27)を置き、PodのterminationGracePeriodSecondsを45に設定してください。メインコンテナwebには、preStopフックとしてexecコマンド["/bin/sh","-c","sleep 10"]を設定してください。
参考
- ボリュームは
spec.volumesに1回宣言し、各コンテナのvolumeMounts[].nameで同じ名前を使います。名前が違うとマウントされません。 kubectl explain pod.spec.initContainers.restartPolicyで、ネイティブサイドカーのフィールドを確認できます。- よくある間違い1:
restartPolicy: AlwaysをPodレベルにだけ書き、サイドカーのコンテナの中に書かないことです。コンテナの中にあって初めてネイティブサイドカーになります。 - よくある間違い2: アダプター/サイドカーが同じボリュームを別のパスにマウントすることです。ファイルをやり取りするには、パスも同じでなければ意味がありません。
kubectl get pod ordered-init -n ckad-multi -o jsonpath='{.spec.initContainers[*].name}'で順序を確認できます。
ラボ用ネームスペースを作成する
ネームスペースckad-multiを作成してください。
kubectl create namespaceの1行で済みます。以降のリソースはすべてこのネームスペースの中に作成します。
initコンテナと共有emptyDir
Pod app-initを作成してください。initコンテナprep(イメージbusybox:1.36、command: ["/bin/sh","-c","echo ready > /work/state"])とメインコンテナapp(イメージnginx:1.27)が、emptyDirボリュームworkdirをどちらも/workにマウントします。
spec.initContainersはspec.containersと同じ高さの配列です。2つのコンテナが同じボリュームを見るには、spec.volumesに1回宣言し、各コンテナのvolumeMountsで同じボリューム名を参照します。
2つのinitコンテナの実行順序
Pod ordered-initを作成してください。initコンテナを順番にwait-config、migrateの2つ置き(どちらもbusybox:1.36、commandは["/bin/sh","-c","true"])、メインコンテナappはnginx:1.27にしてください。
initコンテナは配列に書かれた順に1つずつ実行され、前のものが成功しないと次が始まりません。順序が採点対象なので、配列のインデックスを確認します。
ネイティブサイドカー(restartPolicy: Always)
Pod log-shipperを作成してください。ネイティブサイドカーloggerをinitContainersに入れてrestartPolicy: Alwaysを指定します(イメージbusybox:1.36、command: ["/bin/sh","-c","tail -F /var/log/nginx/access.log"])。メインコンテナwebはnginx:1.27です。emptyDirボリュームvarlogを両方のコンテナで/var/log/nginxにマウントしてください。
ネイティブサイドカーはinitContainersのリストに入れたうえで、そのコンテナにだけrestartPolicy: Alwaysを指定します。コンテナレベルのrestartPolicyは、この用途にだけ使われます。
アンバサダーパターン: 外へ出る経路を代わりに開く
Pod ambassador-podを作成してください。メインコンテナapp(busybox:1.36、command: ["/bin/sh","-c","sleep 3600"])に環境変数REDIS_ADDR=localhost:6379を指定し、2つ目のコンテナproxy(nginx:1.27)にcontainerPort: 6379、name: redis-proxyを指定してください。
メインコンテナは常にlocalhostの固定ポートにだけ接続し、隣のコンテナが実際の接続先を知っています。Pod内のコンテナ同士はネットワークネームスペースを共有するので、localhostでお互いに届きます。
アダプターパターン: 出力形式を外に合わせる
Pod adapter-podを作成してください。メインコンテナapp(busybox:1.36、command: ["/bin/sh","-c","sleep 3600"])とアダプターコンテナmetrics-adapter(nginx:1.27)が、emptyDirボリュームapplogをどちらも同じパス(/var/log/app)にマウントします。アダプターにはcontainerPort: 9113、name: metricsを指定してください。
メインが書くファイルをアダプターが読み、別の形式で公開します。2つのコンテナが同じパスに同じボリュームをマウントする必要があり、アダプターには公開するポートを名前付きで指定します。
終了時間の予算とpreStop(総合)
Pod gracefulを作成してください。ネイティブサイドカーdrainer(initContainersの中、restartPolicy: Always、busybox:1.36、command: ["/bin/sh","-c","sleep 3600"])とメインコンテナweb(nginx:1.27)を置き、PodのterminationGracePeriodSecondsを45に設定してください。メインコンテナwebには、preStopフックとしてexecコマンド["/bin/sh","-c","sleep 10"]を設定してください。
terminationGracePeriodSecondsはPodレベルのフィールドで、SIGTERMからSIGKILLまでの秒数です。preStopフックはコンテナのlifecycleの下にあり、このフックが動く時間もその予算の中で消費されます。