Podはなぜコンテナ一つではないのか
一言でいうと
Podは「コンテナを入れる箱」ではなく、同じノード上で、同じネットワークネームスペースとボリュームを共有し、一緒に起動して一緒に終了するプロセスの集まりの、最小のデプロイ単位です。この定義を受け入れると、initコンテナとサイドカーがなぜあのような形をしているのかが一度に説明できます。
なぜ必要なのか
コンテナ1つ=プロセス1つという原則を守っていると、すぐに壁にぶつかります。Webサーバーはアクセスログをファイルに書きますが、ログ収集ツールは別のプロセスです。アプリは設定ファイルがないと起動しませんが、そのファイルは起動の直前にダウンロードしなければなりません。これを1つのイメージにすべて詰め込むとイメージが肥大化し、ログ収集ツールを替えるたびにアプリのイメージをビルドし直すことになります。
かといって完全に別のPodに切り離すと、2つのことが壊れます。同じファイルシステムを見られなくなり、スケジュールが食い違います。ログ収集ツールが別のノードで起動すると、読むファイルがありません。
そこでKubernetesは中間の段階を作りました。スケジュールの単位とコンテナの単位を分けたのです。スケジュールされるのはPod、実行されるのはコンテナです。Podの中のコンテナ同士は同じノードに配置され、localhostで互いを呼び出し、emptyDirボリュームで同じディレクトリを見ます。
どう動くのか
Podのspecには、コンテナを入れる場所が2つあります。
| 場所 | 実行タイミング | 失敗すると |
|---|---|---|
spec.initContainers |
順番に1つずつ、終わるまで | 次のinitもメインも起動しません |
spec.containers |
initがすべて成功した後に同時に | restartPolicyに従って再起動されます |
initコンテナの目的は「完了」です。設定のダウンロード、DBマイグレーション、先行サービスの待機といった仕事をします。3つ並べると、1つ目が終わらないと2つ目が始まりません。1つでも終わらないと、PodはInit:1/3のような状態にいつまでも留まり、メインコンテナは起動すらしません。
ここには昔からの問題が1つありました。サイドカー(ログ収集ツール、プロキシ)は「動き続けていなければならない」ので、initの場所には入れられませんでした。しかしcontainersに入れるとメインと同時に起動するため、メインがすでにログを書き始めた後でやっと収集ツールが起動する、という競合状態が生まれました。JobのPodではさらに深刻で、メインが終わってもサイドカーが生き続けるため、Jobがいつまでも完了しませんでした。
ネイティブサイドカーがこの問題を整理しました。initContainersのリストに入れたうえで、そのコンテナにだけrestartPolicy: Alwaysを指定します。
spec:
initContainers:
- name: logger
image: busybox:1.36
restartPolicy: Always # ← 이 한 줄이 네이티브 사이드카로 만든다
command: ["sh", "-c", "tail -F /var/log/nginx/access.log"]
containers:
- name: web
image: nginx:1.27
こうすると、3つのことが同時に成立します。メインより先に起動し、動き続け、Jobではメインが終わると一緒に終了します。
パターンの名前も整理しておくとよいでしょう。サイドカーはメインの機能を補助(ログ・メトリクス)し、アンバサダーはメインが外へ出ていくときに代わりに出ていくプロキシ(localhost:6379につなぐと実際のクラスターへルーティングされる)で、アダプターはメインの出力を外が求める形式に変換します(アプリログ → Prometheusメトリクス)。
現場での姿
ホームラボの7ノードクラスターにNVIDIA GPU Operatorを導入したときの出来事です。インストール直後、GPU関連のPodがすべて次のような状態でした。
gpu-feature-discovery-fpw5l 0/1 Init:0/1
nvidia-dcgm-exporter-gmlvl 0/1 Init:0/1
nvidia-device-plugin-daemonset-k4ggb 0/1 Init:0/1
nvidia-operator-validator-krj69 0/1 Init:0/4
0/1、0/4は、initコンテナを1つも通過できていないという意味です。メインコンテナには触れてすらいません。イベントを見ると、原因はコンテナの中ではなく、その外側にありました。
Warning FailedCreatePodSandBox desc = failed to get sandbox runtime:
no runtime for "nvidia" is configured
Podを入れるサンドボックスを作る段階で止まっていました。コンテナが失敗したのではなく、コンテナ同士が共有するネットワーク・IPCネームスペースを作る段階が失敗したのです。Podが「集まり」であるという定義が、ここでそのまま表れています。集まりを作る場所がなければ、その中のどのコンテナも起動しません。
もう1つあります。同じクラスターにKubeVirtを導入したとき、コンポーネントの状態はすべてAllComponentsReadyだったのにVMは起動しませんでした。virt-launcherのPod定義を詳しく調べると、initコンテナが実行するバイナリを入れたボリュームマウントが抜けていました。initコンテナが終われないため、メインがいつまでも待機していたのです。「状態がReady」であることと「実際に動作する」ことは別の命題だと、このクラスターだけで3度目に確認した出来事でした。
次のラボですること
ckad-designネームスペースでPod・ラベル・アノテーション・コマンドの上書き・環境変数・restartPolicyを作成し、Jobのcompletions/parallelism/backoffLimitとCronJobのschedule/concurrencyPolicy/startingDeadlineSecondsを自分で埋めます。続くラボでは、ckad-multiネームスペースにinitコンテナの順序、ネイティブサイドカー、アンバサダー・アダプターのパターン、共有のemptyDir、terminationGracePeriodSecondsを手で作ります。