Certified Kubernetes Application Developer (CKAD) #3 マルチコンテナパターン: Init container、sidecar、ambassador、adapter
#2 Pod とコンテナのライフサイクル では単一コンテナ Pod のライフサイクルと再起動の挙動を身につけました。しかし実務や試験では、1 つの Pod の中にコンテナが複数 入るケースがよく出てきます。今回の記事では、このように複数のコンテナが 1 つの Pod に集まるときの代表的な協調パターンを整理します。
Kubernetes の Pod モデル において、Pod の中のコンテナは 同じネットワークネームスペースと同じストレージボリューム を共有します。つまりコンテナ同士が localhost で通信し、同じディレクトリでファイルをやり取りできます。この共有の性質のおかげで、init container、sidecar、ambassador、adapter という 4 つのパターンが成り立ちます。
Init container: メインコンテナの前に終わらなければならない事前作業 #
init container は メインコンテナが起動する前に実行され、最後まで完了しなければならない コンテナです。複数置くと定義された順に 1 つずつ実行され、前のコンテナが成功で終わってから次のコンテナが始まります。すべての init container が完了した後にはじめて通常のコンテナが起動します。
典型的な使いどころは次のとおりです。
- メインアプリが依存するサービス (データベース、バックエンドの Service) が準備できるまで待機する
- アプリ起動前にデータベーススキーマのマイグレーションを実行する
- 設定ファイルや静的アセットを共有ボリュームにあらかじめダウンロードしておく
- 権限が必要な初期化をメインコンテナと分離して実行する
init container が失敗すると、kubelet が restartPolicy に従って再試行します。restartPolicy が Never でなければ成功するまで繰り返し再起動するため、Pod は init 段階で止まったまま Init:Error または Init:CrashLoopBackOff の状態に留まります。init container のログを個別に確認するには k logs myapp -c wait-for-db のようにコンテナ名を指定し、進行段階は k describe pod myapp で確認します。
次は、メインコンテナの前にバックエンドの Service が立ち上がるまで待機する init container の例です。
apiVersion: v1
kind: Pod
metadata:
name: myapp
spec:
initContainers:
- name: wait-for-db
image: busybox:1.36
command:
- sh
- -c
- "until nslookup db-service; do echo waiting for db; sleep 2; done"
containers:
- name: app
image: nginx:1.27
ports:
- containerPort: 80initContainers は containers と同じ spec レベルに置く別のフィールドです。試験では k run myapp --image=nginx:1.27 $do > myapp.yaml で Pod の骨格を作ってから、このフィールドを直接追加する流れが速いです。
Sidecar: メインと一緒に動く補助コンテナ #
sidecar はメインコンテナと 同じ Pod の中で並んで動き続ける 補助コンテナです。init container がメインの前に終わって消えるのとは異なり、sidecar はメインが動いている間ずっと一緒に動作します。代表的な使いどころはログ収集、メトリクスの公開、プロキシ、設定の同期です。
最もよくある形は ログ収集 sidecar です。メインコンテナが共有ボリュームにログファイルを書くと、sidecar が同じボリュームを読んで標準出力へ流したり外部の収集器へ送ったりします。2 つのコンテナが同じファイルを共有する核心は、後で扱う emptyDir volume です。
1.28+ ネイティブ sidecar #
Kubernetes 1.28 から、sidecar を restartPolicy が Always の init container として宣言するネイティブな方式が導入されました (1.29 でデフォルト有効化)。こう定義された init container は通常の init container のようにメインコンテナより先に起動しますが、終了せずにメインと一緒に動き続け、Pod の終了時にはメインの次に片付けられます。つまり sidecar がメインより先に準備されている必要がある場合に適しています。試験のバージョンが 1.28 以上なら、この形も知っておく方が安全です。
apiVersion: v1
kind: Pod
metadata:
name: native-sidecar
spec:
initContainers:
- name: log-agent
image: busybox:1.36
restartPolicy: Always # この行が init container を sidecar にする
command: ["sh", "-c", "tail -F /var/log/app/access.log"]
volumeMounts:
- name: logs
mountPath: /var/log/app
containers:
- name: app
image: nginx:1.27
volumeMounts:
- name: logs
mountPath: /var/log/app
volumes:
- name: logs
emptyDir: {}Ambassador: 外部接続をローカルプロキシで抽象化 #
ambassador パターンは、メインコンテナが外部サービスと直接接続せず、同じ Pod の中のプロキシコンテナを経由して localhost だけで通信させる構造です。メインアプリは常に localhost:ポート だけを見ており、実際のルーティングや再試行、シャーディング、認証といった複雑な接続ロジックは ambassador コンテナが担当します。
使いどころはメインアプリの接続コードをシンプルに保つことです。たとえばデータベースの読み書き分離や環境別エンドポイントの切り替えを、アプリコードの変更なしに ambassador の設定だけで処理できます。アプリの立場からは、外部が常に 1 か所にあるように見えます。
Adapter: 出力フォーマットを標準化 #
adapter パターンは ambassador と方向が逆です。ambassador が 出ていく接続 を抽象化するのに対し、adapter はメインコンテナの 出力を外部システムが期待する標準フォーマットへ変換 します。メインアプリは自分のやり方でログやメトリクスを出し、adapter コンテナがそれをモニタリングシステムが読める形に加工します。
代表的な使いどころはメトリクスの公開です。アプリが独自の形式で状態を記録すると、adapter がそれを Prometheus が収集できる形式に変換して公開します。アプリごとにばらばらな出力をクラスタ標準の 1 つに揃えるのが adapter の役割です。
コンテナ間の共有 #
この 4 つのパターンが動作する土台は、Pod の中のコンテナがリソースを共有するという点です。実技で直接使う共有手段は 2 つあります。
emptyDir volume の共有 #
emptyDir は Pod がノードにスケジュールされるときに作成され、Pod が消えるまで存在する一時ボリュームです。同じ Pod の複数のコンテナがこのボリュームをそれぞれ volumeMounts でマウントすると 同じディレクトリを共有 します。あるコンテナが書いたファイルを別のコンテナがすぐに読めるため、ログ収集 sidecar と init container のファイル受け渡しはどちらもこれに頼っています。
shared process namespace #
spec.shareProcessNamespace を true にすると、Pod の中のコンテナが プロセスネームスペースまで共有 し、あるコンテナから別のコンテナのプロセスを見たりシグナルを送ったりできます。デバッグやプロセス管理用の sidecar で使われます。
YAML 例 #
init container とメインコンテナ #
次は、init container が共有ボリュームに静的ページを置き、メインの nginx がそのボリュームをサーブする例です。
apiVersion: v1
kind: Pod
metadata:
name: web-with-init
spec:
initContainers:
- name: fetch-content
image: busybox:1.36
command:
- sh
- -c
- "echo '<h1>ready</h1>' > /usr/share/nginx/html/index.html"
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
containers:
- name: web
image: nginx:1.27
ports:
- containerPort: 80
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
volumes:
- name: html
emptyDir: {}sidecar でログを共有 #
次は、メインコンテナが emptyDir にログを書き、sidecar が同じボリュームを読んで標準出力へ流す例です。
apiVersion: v1
kind: Pod
metadata:
name: app-with-logging
spec:
containers:
- name: app
image: busybox:1.36
command:
- sh
- -c
- "while true; do echo \"$(date) request\" >> /var/log/app/access.log; sleep 3; done"
volumeMounts:
- name: logs
mountPath: /var/log/app
- name: log-shipper
image: busybox:1.36
command:
- sh
- -c
- "tail -F /var/log/app/access.log"
volumeMounts:
- name: logs
mountPath: /var/log/app
volumes:
- name: logs
emptyDir: {}2 つのコンテナが同じ logs ボリュームをマウントしているおかげで、app が残したログを log-shipper がそのまま読んで出力します。sidecar のログは k logs app-with-logging -c log-shipper のようにコンテナ名で確認します。
試験ポイント #
- init container は
containersではなくinitContainersに置きます。同じspecレベルの別フィールドである点を取り違えないようにします。 - init container は 順番に、成功するまで 実行されます。Pod が
Init:0/2のような状態で止まっていたら、どの init container でつまずいたかをk describe podで確認します。 - マルチコンテナ Pod のログは必ず
-c コンテナ名を付けないと、目的のコンテナを見られません。名前を省くと、どのコンテナのログか曖昧になります。 - sidecar と init container の ファイル共有は
emptyDir、通信の共有はlocalhostという 2 つの軸を覚えます。 - 1.28 以上では、sidecar を
restartPolicy: Alwaysの init container として宣言するネイティブな方式があります。問題文のバージョンを確認します。 - コンテナの中で命令を実行して動作を確認するときも、コンテナを指定します。
# 特定のコンテナの中で命令を実行
k exec app-with-logging -c app -- cat /var/log/app/access.logまとめ #
今回の記事で押さえたこと:
- init container。メインの前に順番に実行され、成功してから次へ進む事前作業用のコンテナ。待機、マイグレーション、アセットのダウンロードに使う
- sidecar。メインと一緒に動き続ける補助コンテナ。ログ収集とプロキシが代表的な使いどころ。1.28+ ネイティブ sidecar は
restartPolicy: Alwaysの init container - ambassador。出ていく外部接続をローカルプロキシで抽象化する。アプリは
localhostだけを見ればよい - adapter。メインの出力を外部システムの標準フォーマットへ変換する
- 共有手段。
emptyDirvolume でファイルを共有し、shareProcessNamespaceでプロセスネームスペースを共有する - ログの確認。マルチコンテナ Pod は
k logs pod -c コンテナ名
次へ — コンテナイメージ #
Pod の中にコンテナをどう配置するかを身につけました。それなら、そのコンテナが収めているイメージはどう作るのかが次の問いです。
#4 コンテナイメージ: Dockerfile、マルチステージ、試験で直接ビルド では、Dockerfile の核心となる命令、イメージサイズを減らすマルチステージビルド、そして CKAD で直接イメージをビルドしてタグを付けレジストリへ上げる作業まで、手を動かしながら整理します。