TT Lab
はじめる
学ぶ 学習パス コース

サイドカーとコンテナ構成パターン

何を隣に置くのか

TT Labで続きを見る

一言でいうと

Pod内のコンテナはネットワークネームスペースとボリュームを共有します。その2つを共有したいときだけコンテナを追加します。それ以外では追加しません。

なぜ必要なのか

「機能をもう1つ追加しよう」という理由でサイドカーを増やすと、Podが重くなり、デプロイの単位が大きくなり、1つが落ちると全体が再起動します。サイドカーはタダではありません。

追加する理由は、結局次の3つのどれかです。

パターン 何をするか 例
サイドカー 本体に手を加えずに機能を足す ログ収集、メトリクス変換、証明書の更新
アンバサダー 本体の代わりに外部と通信する サービスメッシュのプロキシ、DBコネクションプール
アダプター 本体の出力を標準形式に変換する アプリ固有のメトリクス → Prometheus形式

3つを分ける基準はトラフィックの向きです。サイドカーは横で手伝い、アンバサダーは外へ出ていく経路に立ち、アダプターは入ってくるリクエストに応答する形式を変換します。

どう動くのか

共有されるものと共有されないものを正確に知っておかなければ、設計はできません。

共有される 共有されない
ネットワーク(同じlocalhost、同じIP) ファイルシステムのルート(それぞれ自分のイメージ)
IPC、ボリューム(マウントしたものだけ) プロセス一覧(デフォルトでは共有されません。shareProcessNamespace: trueで有効にできます)

そのため、サイドカーが本体のログを読むには同じボリュームを両方にマウントする必要があります。emptyDirを1つ作り、本体は/var/log/appに、コレクターは/logsにマウントする、といった形です。

1.29で解消された古い問題

以前、サイドカーは単に「コンテナをもう1つ」追加したものにすぎませんでした。そのため、2つの点で不都合がありました。

  1. 起動順序を保証できませんでした。本体がプロキシより先に起動すると、最初のリクエストが失敗します。
  2. Jobが終了しませんでした。本体が終わってもサイドカーが動き続け、JobがずっとRunningのままでした。

1.29から、initContainersにrestartPolicy: Alwaysを指定すると正式なサイドカーになります。初期化コンテナのように本体より先に起動し、本体が終了すると一緒に終了します。

spec:
  initContainers:
    - name: proxy
      image: envoyproxy/envoy:v1.31-latest
      restartPolicy: Always      # ← 이 한 줄이 사이드카로 만든다
  containers:
    - name: app
      image: myapp:1.0

よくある勘違い

リソースのリクエストが加算されるという点: Podのリクエスト量はコンテナの合計です。サイドカーにrequests.cpu: 100mを指定すると、Pod 100個で10コアが予約されます。スケジューラーはその合計を見て配置先を探すため、サイドカー1つがクラスターの密度を大きく下げることがあります。

ログ用サイドカーが常に正しいという考え: ノードごとに1つ動くDaemonSetのコレクターのほうが、たいてい安上がりです。Podごとにコレクターを付けると、その数だけプロセスが増えます。サイドカーのコレクターは、ほかのPodと分離する必要があるとき(マルチテナンシー)やファイルパスが特殊なときにだけ使います。

付けないほうを先に考える

サイドカーは便利に見えるので増えやすいものです。しかし付ける前に同じことを別の場所でできないかを先に確認すると、多くの場合は付けないほうがよいという結論になります。

やりたいこと サイドカー以外の方法 それでもサイドカーが必要な場合
ログ収集 ノードごとに1つ動くコレクター ファイルパスが特殊な場合、またはテナントを分離する必要がある場合
メトリクスの公開 アプリケーションが直接公開する コードを修正できない商用プログラムの場合
証明書の更新 コントローラーがSecretを更新し、アプリが再読み込みする アプリがファイルしか読めず、再読み込みできない場合
設定の再読み込み 設定のハッシュをPodテンプレートに入れてローリング更新する 再起動なしで反映する必要がある場合

表の右の列に当てはまらなければ付けません。サイドカー1つはPodごとに掛け算されるため、Podが200個なら200個のプロセスと200個分のリクエスト量が増えます。さらに、そのコンテナのイメージ更新、脆弱性対応、設定変更がすべて本体のデプロイに上乗せされます。

付けると決めたなら、いくつかを同時に決めておきます。本体より先に起動して後から終了するか(前に見たrestartPolicy: Always)、上限をどう設定するか(バッファーをためるコレクターは特に余裕を持たせます)、そしてサイドカーが失敗したときに本体をどうするかです。最後の点は忘れられがちですが、プロキシが落ちたときに本体がリクエストを受け続けてすべて失敗するより、readinessによってトラフィックから外れるほうがよい場合がほとんどです。そのためには、本体のreadinessがサイドカーの状態も合わせて見る必要があります。

実務で本当に大切なこと

サイドカーは本体とライフサイクルを共有します。サイドカーがOOMで落ちると、そのコンテナが再起動する間は本体が頼っていた機能が止まり、再起動が繰り返されるとPodがCrashLoopBackOffに入ってまるごと使えなくなります。そのためサイドカーのメモリ上限には余裕を持たせ、本体より先に落ちないように設定する必要があります。ログコレクターがバッファーをメモリにためる設定の場合は特にそうです。