コンテナ環境のログ収集設計は、「どのツールを入れるか」より「どの方式で収集するか」の判断が先です。stdoutへ書かせてkubeletに渡す構成、サイドカーコンテナで横取りする構成、DaemonSetエージェントでノード単位に集める構成——三者はオーバーヘッドの分布もマルチテナント対応の難易度も異なります。本記事では各方式の選定基準、ファイル書き込みからstdoutへの移行設計、再起動時のデータロス対策まで、設計フェーズで判断すべきポイントを整理します。
stdoutへ書かせてkubeletに渡すかサイドカーで受けるかの判断
kubeletのログ管理とstdout前提の設計意図
Kubernetesはコンテナのstdout/stderrをkubeletが自動的に受け取り、ノード上の/var/log/pods/配下にファイルとして書き出す仕組みを備えています。この設計はシンプルで、DaemonSetとして動くログエージェントがそのファイルをtailするだけで全Pod分のログを収集できます。アプリ側に収集ロジックを持ち込む必要がなく、収集設定の変更もエージェント側だけで完結します。新規にコンテナ化するアプリケーションであれば、「stdout前提・DaemonSet収集」を起点に設計を考えるのが合理的です。収集インフラの変更が必要になっても、アプリのコードを触らずにエージェント設定だけを更新できるため、アプリチームとインフラチームの責務が分離されます。
サイドカーパターンを選ぶべき条件
サイドカーパターンが有効なのは、アプリケーションのログをアプリと別のライフサイクル・別の転送先に切り分けたい場合です。一つのPodが複数のログストリーム(アクセスログ・エラーログ・監査ログ)を異なるフォーマットで出力し、それぞれ別のバックエンドへ送る必要があるなら、サイドカーコンテナ側でパースと振り分けを担当させる構成が適しています。また、ログに含まれる個人情報をマスキングしてから転送する要件がある場合、サイドカー内で処理を挟む方がDaemonSet側に処理を持たせるよりも責務が明確です。マスキングロジックの変更がノード全体に波及することなく、対象のPodだけに閉じて適用できる点が運用上の利点になります。
判断を誤りやすい落とし穴
よくあるつまずきとして、「とりあえずサイドカーを入れておけば柔軟に対応できる」という発想があります。サイドカーはPodごとにコンテナが増えるため、Pod数が増えるほどリソース消費も比例して増加します。想定例として、100Pod規模の環境で各サイドカーが50MiBのメモリを消費する場合、収集だけで5GiBが固定コストになります。DaemonSetならノード数に比例するため、同じ100Podがノード10台に分散していればエージェント10インスタンスで済み、500MiB程度に収まります。設計初期に「Pod数基準」ではなく「ノード数基準」でコストを計算する習慣を持つことで、方式選択の判断が変わります。
DaemonSet収集とサイドカーのオーバーヘッド比較
DaemonSetエージェントのリソース特性
DaemonSetは各ノードに1インスタンスだけ配置されるため、エージェント自体のリソース消費はノード数に線形比例します。Fluent BitをDaemonSetで動かす場合、1インスタンスあたりのメモリ消費は設定内容によって変わりますが、標準的な構成では20〜50MiBの範囲に収まることが多いです。ノード20台のクラスターであれば最大1GiB程度のメモリをログ収集に割り当てる計算になります。CPUについてはtailプラグインのポーリング間隔とログ量に依存しますが、1ノードあたり0.1コア未満に抑えることが一般的な設計目標です。Pod数が増えてもエージェント数は変わらないため、クラスターのスケールアウト局面でも収集コストが安定しています。
サイドカーの追加コストと比率
サイドカー方式ではPodごとにコンテナが追加されるため、オーバーヘッドの計算単位がPodになります。想定例として、マイクロサービス構成で200Podが稼働する環境にサイドカーを全展開する場合、各サイドカーが30MiBを消費するだけで合計6GiBの追加メモリが必要です。これに対してDaemonSet方式なら、同じ200Podがノード20台に分散している場合にエージェント20インスタンス分(最大1GiB)で済みます。Pod密度が高いほどDaemonSetのコスト優位性が際立ちます。サイドカーを採用する場合は、Pod数の上限とメモリバジェットを設計段階で合意しておき、オートスケーラーがPodを増やした際の最大消費量をシミュレーションしておくことが必要です。
規模別の選択指針
小規模(ノード数10台未満、Pod数50以下)では方式差のコスト影響が限定的なため、運用のシンプルさを優先してDaemonSet構成を選ぶのが無難です。中規模(ノード数10〜50台)ではログ転送先の多様性やテナント分離要件が出てくることが多く、DaemonSetをベースにしつつ一部のPodにだけサイドカーを組み合わせるハイブリッド構成を検討する段階になります。大規模(ノード数50台超)ではDaemonSetのリソース管理が重要になり、エージェントのメモリ上限やバッファ設定の調整に工数がかかります。規模の段階に応じて設計の複雑さを追加していく判断が、後戻りコストを抑える上で重要です。
アプリがファイルに書く構成からstdoutへ移行する設計
ファイル書き込みのままにするリスク
コンテナ内でファイルにログを書き続ける設計は、コンテナのエフェメラルな性質と相性が悪いです。コンテナが再起動されるとそのファイルシステムは初期化されるため、再起動前のログは失われます。これを防ごうとPersistentVolumeをマウントする構成を採用すると、ストレージのプロビジョニングとPVCの管理が全Podに必要になり、運用コストが膨らみます。また、ノードローカルのファイルシステムにログを書くと、ディスク使用量の監視・ローテーション・削除のロジックをアプリかサイドカーが担う必要があり、設計の複雑性を無用に増やします。長期的な保守コストの観点から、移行の優先度を高く設定することが合理的です。
移行の設計ステップ
ファイル書き込みからstdoutへの移行で重要なのは、いきなりアプリのロギング設定を変えるのではなく、段階的に移行できる構造を用意することです。まず現状のログファイルをtailするサイドカーを追加し、そのサイドカーがstdoutに中継する構成を挟みます。これにより収集パイプラインはstdout前提に変更できるため、アプリ側のログ設定変更を後回しにできます。次にアプリのロギングライブラリをstdout出力に切り替え、並走期間を設けて両方の出力が正常に収集されていることを確認します。最後にファイル出力の設定を削除してサイドカーも撤去することで、完全にstdout構成へ移行できます。各ステップを独立してロールバックできる設計にしておくことが重要です。
移行できないケースの対処
レガシーなミドルウェアやサードパーティ製コンポーネントの中には、ログ出力先をstdoutに変更できないものがあります。こうした場合は中継サイドカーを永続的に運用する設計になりますが、サイドカーの役割をできるだけ薄くすることが保守性を高めます。具体的には、サイドカーはファイルのtailとstdoutへの転送だけを担い、パースや加工はDaemonSet側のエージェントに任せる分担とするのが適切です。サイドカーとDaemonSetの両方に処理ロジックを置くと設定変更の影響範囲が把握しにくくなるため、「サイドカーは中継専用、変換はエージェントで行う」という責務の境界を設計段階で決めておくことが将来の混乱を防ぎます。
Fluentd・Fluent Bit・Vectorの選定基準
三者の位置づけと得意領域
Fluentdはプラグインエコシステムが豊富で、多様な入出力先への対応実績があります。ただしRubyベースのため起動時のメモリ消費が大きく、DaemonSetとして全ノードに展開すると固定コストが高くなります。Fluent BitはCベースで軽量に動作し、DaemonSetのエージェントとして広く普及しています。パースと転送に特化しており、複雑な変換ロジックが不要な環境に向いています。VectorはRust製でリッチな変換パイプラインをDSLで記述でき、ログ・メトリクス・トレースを統一的に扱える点が特徴です。モダンなオブザーバビリティ基盤を一から整備する場合に選定候補になります。
選定を左右する要素
選定で優先すべき要素は「転送先の種類」「変換ロジックの複雑さ」「運用チームの習熟度」の三点です。転送先がElasticsearchとS3だけであればFluent Bitで十分です。DatadogやSplunk・独自APIなど転送先が多様でFluentdのプラグインで既に対応できる場合は、Vectorへの移行よりFluentdを使い続ける方が移行コストを抑えられます。変換ロジックが複雑になりLuaスクリプトをFluent Bitに多数埋め込みはじめたら、Vectorのパイプライン構文に移行することで保守性が改善するケースがあります。各ツールの詳細な設定方法は本体リファレンスで確認しつつ、どのツールが自チームのスキルセットと合うかを軸に判断することが重要です。
組み合わせ構成の設計
大規模環境では、Fluent BitをDaemonSetとして全ノードに配置し、収集したログを集約ノードへ転送し、集約側でFluentdやVectorが変換・配信する二段構成を採用することがあります。この構成ではノード側のエージェントは軽量に保ち、複雑な処理を集約層に集中させるため、各層の設定変更が互いに影響しにくくなります。ただし集約ノードが単一障害点になるリスクへの対処として、集約層を複数インスタンスで稼働させるか、バッファのディスク永続化を有効にして再起動時のデータロスを防ぐ設計が必要です。転送量が増加した際のスケール方法も設計段階で決めておくことが、後から構成を変えるコストを抑えます。
マルチテナント構成でログの混入を防ぐ設計
テナント分離の基本戦略
マルチテナントのKubernetes環境でログが混入する最大の原因は、DaemonSetエージェントが全Namespaceのログを同一パイプラインで処理して同一の転送先に送ることです。テナントAのログとテナントBのログが同じインデックスやバケットに混在すると、テナントごとの検索・アクセス制御・保持期間の管理がすべて困難になります。分離の基本は「収集段階でNamespaceラベルを付与し、転送先をテナント単位で切り替える」設計です。DaemonSet一台で全テナントを処理しつつ出力先を分岐させる構成が、専用エージェントをテナントごとに立てるより運用コストを抑えられます。
ラベルとNamespaceによる分離
kubeletがstdoutを書き出す際、ログファイルのパスにはNamespaceやPod名が含まれます。Fluent BitやFluentdのtailプラグインはこのパス情報をメタデータとして取得できるため、ルーティングの判断材料に使えます。PodにラベルやAnnotationでテナントIDを付与しておき、エージェントがそのメタデータを読んで転送先を切り替える設計が基本です。一般的な失敗として、ラベルの命名規則をチームごとに決めず、後から複数の形式が混在してルーティング設定が複雑化するケースがあります。設計初期にラベル命名規則を標準化し、新規テナント追加時の手順をテンプレート化しておくことが将来のトラブルを防ぎます。
出力先を分ける設計の選択肢
テナントごとにログの転送先を変える方法として、一つのDaemonSetエージェント内にテナント数分のoutputセクションを記述する方法と、テナントごとにエージェントのNamespaceを分けてDaemonSetを複数立てる方法があります。前者は管理するエージェントが1セットで済む反面、設定ファイルが肥大化しやすく、テナント追加のたびに全ノードのエージェントを再起動する必要があります。後者はテナントの独立性が高くなる一方、ノードあたりのエージェント数が増えてリソース消費が比例します。テナント数が5以下であれば前者、それ以上で各テナントの独立性が求められるなら後者を選ぶ判断が現実的です。
コンテナ再起動でログが消えないための設計
コンテナログの揮発性と永続化の境界
kubeletがstdoutを書き出すログファイルはノードのローカルディスクに存在するため、コンテナが再起動してもPodが同じノードに残る限りログファイル自体は保持されます。ただし、Podが別ノードへ再スケジュールされた場合や、ノード自体が再起動した場合は状況が変わります。エージェントがログを読み取ってから転送を完了するまでの間にコンテナが再起動した場合、エージェントのオフセット(読み取り位置)が保存されていれば再起動後も続きから読めます。オフセットの永続化設定を忘れることがよくある落とし穴で、再起動後に重複取得や欠落が発生する原因になります。設定項目として「db(データベースファイル)パスの指定」がFluent Bitでは該当し、これをEmptyDir以外のボリュームに置く必要があります。
ログローテーション設計
kubeletの設定でコンテナごとのログファイルサイズ上限(containerLogMaxSize)とローテーション数(containerLogMaxFiles)を制御できます。デフォルトの10MiB・5ファイルは低スループットのアプリには十分ですが、高トラフィック環境ではエージェントが読み取る前にローテーションが進んでログが消えるリスクがあります。想定例として、1秒あたり1MBのログを出力するアプリでエージェントの転送が10秒間滞ると10MBのバックログが生じ、デフォルト設定ではローテーション境界に達してしまいます。ログ量のピーク値を測定した上でサイズ上限を設計し、エージェントのtail間隔も合わせて調整することが必要です。設定値の具体的な決め方は本体リファレンスで確認することを推奨します。
バッファ設計とデータロス対策
ログエージェント自体の再起動やクラッシュに備えて、エージェント側のバッファをメモリではなくディスクに永続化する設定を有効にしておくことが重要です。メモリバッファのみの場合、エージェントが再起動するとバッファ内のログは失われます。Fluent BitではStorageプラグインでファイルシステムバッファを有効にでき、VectorではDisk Bufferオプションが同等の機能を提供しています。バッファ用のディスク領域はエージェントのPodにVolumeMountで割り当てる設計が一般的で、EmptyDir(ノードローカル)を使うか専用のPVCを割り当てるかはノード障害時の要件によって判断します。ノード障害で収集済みログが失われることを許容できるか否かが、PVC採用の判断基準です。
収集方式比較表(stdout/サイドカー/DaemonSet)
| 観点 | stdout+DaemonSet | サイドカー | ハイブリッド(DaemonSet+サイドカー) |
|---|---|---|---|
| リソース消費の単位 | ノード数に比例 | Pod数に比例 | ノード数+対象Pod数 |
| スケール効率 | 高(Pod増加の影響なし) | 低(Pod増加でリソース増加) | 中(用途で使い分け) |
| マルチテナント対応 | エージェント設定で分岐 | Pod単位で独立制御可 | テナント要件に応じて選択 |
| 複数ログストリームの扱い | 混在しやすい(要設計) | Podごとに分離しやすい | 複数ストリームはサイドカー担当 |
| 再起動時のログ保全 | ノード側ファイルに残る | サイドカーのバッファ次第 | エージェントのバッファ設計による |
| アプリ側の変更コスト | stdout出力への変更が必要 | ファイル出力のままでよい | 条件に応じて混在可 |
| 設定変更の影響範囲 | エージェント再起動で全Pod影響 | Pod単位で局所化 | 層ごとに分割可 |
| 主な採用場面 | 新規コンテナ化・標準構成 | 複数ストリーム・マスキング要件 | レガシー移行期・混在環境 |
設計の起点は「アプリがstdoutに書けるか」の確認です。書けるならDaemonSet一本で始め、テナント分離やストリーム分割の要件が加わった段階でサイドカーを部分的に追加する判断が、構成の複雑さを最小に保ちます。バッファのディスク永続化とオフセット保存の設定漏れが再起動時のデータロスに直結するため、以下の項目を設計チェックリストとして活用してください。
- アプリのstdout出力可否を確認し、不可の場合は中継サイドカー採用を検討する
- Pod数・ノード数の比率からDaemonSet方式とサイドカー方式のリソースコストを比較する
- テナント分離が必要か確認し、ラベル命名規則を事前に標準化する
- kubeletのログローテーション設定(サイズ上限・ファイル数)をログ量ピーク値に基づいて設定する
- エージェントのオフセットDBをノード障害に耐えられるボリュームに配置する
- エージェントのバッファをディスク永続化し、PVC要否をノード障害要件から判断する
- 転送先の種類・変換ロジックの複雑さ・チームの習熟度からFluent Bit/Fluentd/Vectorを選定する
- 二段構成(ノード収集+集約層)を採用する場合は集約層の冗長化とバッファ設計を同時に決める
▶ 手を動かして本番まで到達する
脱属人化の第一歩は「読める・書けるスクリプト」から始まります
ここまでの設計判断は、読めば腹に落ちます。ですが「自分の手で本番まで通しきる自信」は、それとは別の話になります。
自動化の設計を、手を動かして身につける2日間です。手順書に閉じ込めた構築を、他人が読めて再現できる形へ変えていきます。
コンテナのログ収集設計をスクリプトで実践するシェルスクリプト実践コースへ →
シェルスクリプトマスターセミナー|自動化の設計を手で身につける2日間