- 1 1. なぜEKSの観測可能性で詰まるか — Vol1架橋と観測可能性の3つの壁
- 2 2. 観測可能性3本柱整理 — Logs / Metrics / Traces × EKS特有課題
- 3 3. ログ収集設計 — FluentBit + CloudWatch Logs / 構造化ログ / IRSA権限設計
- 4 4. メトリクス収集 (山場) — Container Insights + ADOT Collector + Prometheus互換 / kube-state-metrics
- 5 5. 分散トレーシング — ADOT × X-Ray / OpenTelemetry SDK / Service Map / W3C TraceContext
- 6 6. 詰まりポイント7選 図解
- 6.1 詰まりポイント1: FluentBit IRSA 信頼ポリシー設定ミス
- 6.2 詰まりポイント2: FluentBit ConfigMap 反映遅延
- 6.3 詰まりポイント3: sidecar 誤注入でホスト側ログが見えない
- 6.4 詰まりポイント4: ADOT metrics_exporter の namespace / dimension 設定不備
- 6.5 詰まりポイント5: W3C TraceContext 伝播の切断
- 6.6 詰まりポイント6: X-Ray Service Map に一部サービスが表示されない
- 6.7 詰まりポイント7: Helm Chart 更新後に ADOT Collector が再起動ループ
- 7 7. アンチパターン→正解パターン変換演習 (Terraform + Helm 両形式)
- 8 8. まとめ + EKS Vol3予告 + Vol1双方向クロスリンク
1. なぜEKSの観測可能性で詰まるか — Vol1架橋と観測可能性の3つの壁
1-1. この記事のゴール
Vol1でEKSクラスタを立ち上げ、KarpenterとIRSAを動かした。
Podがデプロイされ、ALB Ingressを通じてリクエストが届く — ここまで来たら次のステップがある。
「動いている」から「見える状態で動いている」への昇格だ。
本番運用でEKSが「動かせる」状態になると、次に直面するのは「何が起きているかわからない」という壁だ。
Podがクラッシュしてもログが残っていない。メトリクスを見ようとしたらCloudWatchに何も届いていない。
トレースを仕込んだはずがX-Rayのコンソールが空白のまま — こうした詰まりシーンはEKS観測可能性の設定に特有のパターンがある。
本記事を読み終えると、次の5つが達成できる。
- FluentBit DaemonSet を EKS Add-on として展開し、コンテナログを CloudWatch Logs へ構造化転送できる
- Container Insights を有効化し、Podレベルの CPU/メモリ/ネットワークメトリクスを CloudWatch Metrics で可視化できる
- ADOT Collector (AWS Distro for OpenTelemetry) を展開し、アプリケーショントレースを X-Ray へ送信できる
- IRSA権限設計 を理解し、FluentBit/ADOT に最小権限の IAM ロールを割り当てられる
- 詰まりポイント7選 を事前に把握し、本番運用でよく見るログ欠損・メトリクス途切れ・トレース未着信を未然に防げる
Vol1が「動かすところまで」だとすれば、Vol2は「見える状態で動かす」ための技術スタックを体系的に習得する章だ。
この記事の構成
| セクション | 内容 |
|---|---|
| §1 (本章) | 観測可能性の3つの壁 / 前提条件 |
| §2 | 観測可能性3本柱整理 — Logs / Metrics / Traces × EKS特有課題 |
| §3 | FluentBit構成 — DaemonSet / IRSA / CloudWatch Logs構造化転送 |
| §4 | Container Insights — メトリクス設計 / ダッシュボード |
| §5 | ADOT Collector — OpenTelemetry / X-Ray トレース統合 |
| §6 | 詰まりポイント7選 — ログ欠損 / メトリクス途切れ / トレース未着信 |
| §7 | 演習5問 |
| §8 | まとめ / Vol3予告 |
各セクションは独立して読めるように設計している。
FluentBitだけ使いたい場合は§3、X-Rayトレースを先に試したい場合は§5から読み始めても問題ない。
1-2. Vol1からの架橋
Vol1で学んだ技術が、Vol2でどう接続するかを整理しておく。
Vol1の内容を「知っている前提」として話が進むので、対応表で確認しておこう。
| Vol1で習得したこと | Vol2での活用先 |
|---|---|
| IRSA (IAM Roles for Service Accounts) | FluentBit が CloudWatch Logs へ書き込む IAM ロール設計 |
| IRSA | ADOT Collector が X-Ray / CloudWatch Metrics へ書き込む IAM ロール設計 |
| Karpenter ノード設計 | DaemonSet (FluentBit/ADOT) のノード配置と tolerations 設計 |
| ALB Ingress Controller | アプリケーションのアクセスログを FluentBit で取得する設定 |
| Terraform モジュール構成 | FluentBit/ADOT の Helm values を helm_release で管理する構成 |
特に IRSA はVol2の要だ。
FluentBitがCloudWatch Logsへ書き込むには logs:CreateLogGroup / logs:PutLogEvents 権限が必要で、これをPodのService AccountにIRSAで付与する。
ADOTもX-RayやCloudWatch Metricsへの書き込みにIRSAを使う。Vol1でIRSAの仕組みを理解していると、Vol2§3/§5の設定がスムーズに読める。
Vol2全体を通じて、インフラコードは Terraform + Helm 両形式 で提示する。
Terraformを使う場合は helm_release リソース、Helmのみの場合は values.yaml + helm install コマンドで解説する。
どちらを使っていても読み進められる構成になっている。
Vol1未読の方は先にVol1 (クラスタ設計 × IRSA × ALB Ingress)を確認することを推奨するが、IRSA/Helmに慣れた読者であれば本記事から開始しても問題ない。
1-3. EKSの観測可能性で詰まる3つの壁
EKS上の観測可能性で実際に詰まる理由は3パターンに集約される。
それぞれ「問題シーン → なぜ詰まるか → この記事での解決アプローチ」の順に整理する。
壁1: Pod短命性によるログ・メトリクス欠損
問題シーン:
Podがクラッシュループに入り、再起動を繰り返している。
CloudWatch Logsを確認しようとするが、直近のエラーログが残っていない。kubectl logs でも previous フラグをつけても取得できない状態だ。
なぜ詰まるか:
コンテナが終了すると、ノード上の /var/log/containers/ に書き込まれたログファイルも削除される。
FluentBitがログをバッファリングしている最中にPodが消えると、未転送分が欠損する。
また、短命なPodのメトリクスはContainer Insightsの集計ウィンドウをまたがず、時系列グラフに穴が空く。
「ログが届いていない」「メトリクスが途切れている」という問題の多くはここに起因する。
本記事での解決アプローチ:
FluentBitの Mem_Buf_Limit と storage.type filesystem を組み合わせ、ノードローカルのディスクバッファを活用する設定を§3で解説する。
Podが消えてもバッファが残り、FluentBitが次のポーリングサイクルで転送を再開できる。
Container Insightsの集計間隔についても§4で調整方法を取り上げる。
壁2: sidecar vs DaemonSet の選択ミス
問題シーン:
「FluentBitをサイドカーで動かすべきか、DaemonSetで動かすべきか」で判断が止まる。
公式ドキュメントと技術ブログの両方を見ると構成例が混在していて、どちらが正解かわからない。
とりあえずサイドカーで組んだら全Podのリソース消費が2倍になり、Karpenterのノード追加が想定外に増えた。
なぜ詰まるか:
サイドカー方式はPodごとにFluentBitコンテナを追加する。
Pod単位のきめ細かいフィルタリングが可能な反面、全Podに挿入するオーバーヘッドが大きく、CPU/メモリの消費が増える。
DaemonSet方式はノードごとに1つのFluentBitプロセスが全コンテナのログを収集する。
管理が集約されてコスト効率が高いが、全Pod共通の設定になるため、Pod別の細かい制御が難しい。
ユースケースを整理しないまま実装を始めると後から構成変更が必要になる。
本記事での解決アプローチ:
EKSにおける推奨パターンは DaemonSet方式 (EKS Add-on) だ。
AWS管理のFluentBit Add-onを使うとバージョン管理・IRSA連携・CloudWatch Logs設定が一体化し、運用負荷を大幅に下げられる。
Pod単位の細かいフィルタリングが必要なケースはサイドカーを部分的に追加する「ハイブリッド構成」で対応する。
§3でDaemonSet/サイドカー/ハイブリッドの選択基準を判断フローチャートとともに整理する。
壁3: マルチクラスタのログ・メトリクス集約
問題シーン:
staging/productionの2クラスタを運用している。
両クラスタのログをCloudWatch Logsで確認しようとすると、ロググループ名が混在して検索しづらい。
Prometheusでメトリクスを集約しようとしても、どこにADOT Collectorを置けばよいか設計が固まらない。
なぜ詰まるか:
デフォルトのFluentBit Add-on設定では、ロググループ名に /aws/containerinsights/{cluster-name}/ プレフィックスが付く。
クラスタ名ごとにロググループが分散し、CloudWatch Log Insightsのクロスクエリが煩雑になる。
Prometheusのスクレイプ設定はクラスタごとに独立しているため、複数クラスタのメトリクスをAmazon Managed Service for Prometheus (AMP)へ集約するには、各クラスタのADOT Collectorにremote_write先を統一する設計が必要になる。
本記事での解決アプローチ:
§3でFluentBitの log_group_name テンプレートをクラスタ間で統一する設定パターンを解説する。
§5でADOT CollectorのPrometheus remote_write設定と、AMPへの集約構成を扱う。
マルチクラスタ対応のIRSAロール設計も合わせて整理する。
1-4. 前提条件
推奨する事前知識
本記事はVol1読了者を対象としている。以下の知識レベルを前提とする。
| 知識領域 | 必要レベル | 参照先 |
|---|---|---|
| EKS クラスタ設計 / IRSA | 必須 | Vol1 (前作) |
| IAM ロール / AssumeRoleWithWebIdentity | 必須 | IAM入門 Vol4 |
| kubectl 基本操作 (get / describe / logs) | 必須 | — |
| Helm 基礎 (install / upgrade / values.yaml) | 必須 | — |
| Terraform 基礎 (helm_release リソース) | 推奨 | — |
| OpenTelemetry の基礎概念 (Traces / Spans) | 推奨 | — |
検証環境チェックリスト
本記事のハンズオンを実行するには以下が稼働済みであること。
# EKS クラスタ接続確認
kubectl get nodes
# → STATUS: Ready のノードが1台以上
# Helm バージョン確認 (3.x 以上)
helm version --short
# AWS CLI 設定確認
aws sts get-caller-identity
# OIDC プロバイダー確認 (IRSA の前提)
aws eks describe-cluster \
--name <YOUR_CLUSTER_NAME> \
--query "cluster.identity.oidc.issuer" \
--output text
# EKS Add-on 一覧 (FluentBit 未インストール確認用)
aws eks list-addons --cluster-name <YOUR_CLUSTER_NAME>
EKSクラスタはバージョン 1.29以上、Karpenter または Managed Node Group が稼働済みの状態を前提とする。
ALB Ingress Controllerが稼働済みであればVol1の前提条件を満たしている。
OIDCプロバイダーが設定済みであることも確認しておこう — IRSAの動作に必須だ。
EKS本番運用シリーズ ナビゲーション
- Vol1: クラスタ設計 × IRSA × ALB Ingress (前作)
- Vol2 (本記事): 観測可能性 — FluentBit × Container Insights × ADOT
- Vol3 (予告): セキュリティ・運用自動化 (近日公開予定)
関連シリーズ: IAM入門 Vol4 (STS × Cross-Account) — §3/§4 IRSA 設計の前提知識
2. 観測可能性3本柱整理 — Logs / Metrics / Traces × EKS特有課題
EKS の本番運用において「動かすこと」の次に直面するのが「見える状態を作ること」です。Pod が突然クラッシュしてもなぜ落ちたか分からない、レイテンシが上昇しているがどのサービスが原因か不明 — こうした状況を解消するのが観測可能性 (Observability) の3本柱です。本セクションでは EKS 特有の観測課題と、FluentBit / Container Insights / ADOT という3ツールの採用理由を整理します。
2-1. 観測可能性とは何か
Logs / Metrics / Traces の3本柱
観測可能性は「Logs / Metrics / Traces」の3種類のデータを組み合わせて「システムの内部状態を外部から推測できる」状態を作ることです。
| 柱 | 何を見るか | EKS での主な収集先 | 主なツール |
|---|---|---|---|
| Logs | 「何が起きたか」の時系列記録 (アプリケーションログ / Kubernetes イベント) | CloudWatch Logs | FluentBit DaemonSet |
| Metrics | 「どのくらいの負荷か」の数値集計 (CPU / Memory / リクエスト数 / エラー率) | Container Insights / Prometheus | ADOT Collector |
| Traces | 「どこで時間がかかったか」のリクエスト追跡 (マイクロサービス間の依存関係可視化) | X-Ray Service Map | ADOT Collector + X-Ray |
3本柱は相互補完的です。Metrics でレイテンシ上昇を検知し、Traces で原因マイクロサービスを特定し、Logs で詳細なエラー内容を確認する — このワークフローが本番での典型的なデバッグフローになります。3本柱のうちどれか1つでも欠けていると、障害の根本原因特定に時間がかかります。
VM ベースとの根本的な違い
従来の VM (EC2 単体) 運用では OS ログが /var/log に永続化され、プロセスが再起動してもログファイルが残りました。EKS では Pod が終了するとコンテナのファイルシステムが削除 されます。Karpenter によるスケールダウン時や Rolling Update 時に10秒以内に Pod が置換されることも珍しくなく、その間のログを失うリスクがあります。
| 環境 | ログの永続化 | メトリクス収集 | 観測設計の難易度 |
|---|---|---|---|
| EC2 (VM 直接運用) | OS に永続 (/var/log) | CloudWatch Agent 1設定 | 低 (ログが自然に残る) |
| ECS Fargate | awslogs ドライバーで自動転送 | Container Insights 自動収集 | 低〜中 |
| EKS (本記事) | Pod 終了でコンテナ FSが削除 | DaemonSet / Sidecar で別途設計 | 高 (設計が必要) |
「観測できない = 障害の再現性がない = 根本原因を特定できない」という問題に直結します。EKS では設計段階から観測可能性を組み込む必要があります。
2-2. EKS 特有の3つの課題
課題1: Pod 短命性問題
EKS では Pod が随時終了・再起動します。コンテナの標準出力 (stdout / stderr) はデフォルトで Node 上の /var/log/containers/ に書き込まれますが、Pod 削除後に対応するログファイルも失われます。
Karpenter を使用している場合は特に注意が必要です。Node のスケールダウン時に Pod が別 Node に移動すると、移動前 Node 上の /var/log/containers/ のログは Node が削除されると同時に消えます。
解決アプローチ: FluentBit DaemonSet による先行収集
FluentBit を DaemonSet として全 Node に配置し、/var/log/containers/ を継続的に監視して CloudWatch Logs へ転送します。Pod のライフサイクルに依存しないため、Pod が消えてもログが CloudWatch に永続保存されます。
# FluentBit DaemonSet の基本構成例
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: fluent-bit
namespace: amazon-cloudwatch
spec:
selector:
matchLabels:
name: fluent-bit
template:
spec:
serviceAccountName: fluent-bit# IRSA で CloudWatch Logs 権限を付与
tolerations:
- key: node-role.kubernetes.io/master
effect: NoSchedule
containers:
- name: fluent-bit
image: public.ecr.aws/aws-observability/aws-for-fluent-bit:stable
resources:
limits:
memory: 200Mi
requests:
cpu: 100m
memory: 100Mi
volumeMounts:
- name: varlog
mountPath: /var/log
readOnly: true
volumes:
- name: varlog
hostPath:
path: /var/log
serviceAccountName: fluent-bit には IRSA (Vol1 §4) で logs:PutLogEvents / logs:CreateLogGroup / logs:DescribeLogGroups 権限を付与します。
課題2: sidecar vs DaemonSet 問題
ログ収集コンテナの配置方式には sidecar と DaemonSet の2択があります。
| 比較軸 | sidecar (Pod 内コンテナ) | DaemonSet (Node 単位) |
|---|---|---|
| 管理コスト | 高 (全 Deployment に追記が必要) | 低 (1 DaemonSet で全 Pod をカバー) |
| リソース消費 | 高 (Pod ごとにコレクターが起動) | 低 (Node ごとに1プロセス) |
| ログ分離制御 | Pod 単位で個別設定可能 | ラベル / Namespace でフィルタリング |
| IRSA 付与対象 | 各 Pod の ServiceAccount (多数) | DaemonSet の ServiceAccount (1つ) |
| 推奨シーン | 特殊なログ形式処理が必要な場合 | 本番標準 (ほぼ全ケース) |
原則: DaemonSet を優先し、sidecar は最終手段 とします。全 Deployment の YAML を修正する必要がある sidecar はスケール時に管理コストが増大するためです。
課題3: マルチクラスタ集約問題
本番環境では dev / stg / prd の複数クラスタが存在するのが一般的です。各クラスタのログ・メトリクスを別々のダッシュボードで確認していると、障害時に複数コンソールを切り替える手間が発生します。
解決アプローチ: CloudWatch Logs グループ命名規則の統一
FluentBit の出力先 Log Group 名にクラスタ名を含める命名規則を採用します。
# FluentBit ConfigMap: OUTPUT セクション (Log Group 命名規則)
[OUTPUT]
Name cloudwatch_logs
Match application.*
regionap-northeast-1
log_group_name /aws/containerinsights/$(CLUSTER_NAME)/application
log_stream_prefix $(NODE_NAME)-
auto_create_group true
$(CLUSTER_NAME) を環境変数として注入することで、複数クラスタのログが /aws/containerinsights/dev-cluster/application / /aws/containerinsights/prd-cluster/application のように分離されます。CloudWatch Logs Insights のクロスロググループクエリで複数クラスタを一元検索できます。
2-3. 観測可能性 × EKS 全体アーキテクチャ

EKS クラスタの観測可能性は以下の3つのデータフローで構成されます。
Logs フロー
EKS Pod (stdout/stderr)
→ /var/log/containers/ (Node FS)
→ FluentBit DaemonSet (読み取り・フィルタ・加工)
→ CloudWatch Logs (永続保存 / Logs Insights クエリ)
Metrics フロー
EKS Node / Pod / Container メトリクス
→ ADOT Collector DaemonSet (収集・集計)
→ Container Insights (CloudWatch) / Amazon Managed Prometheus
→ CloudWatch Dashboard / Grafana (可視化)
Traces フロー
アプリケーション (OpenTelemetry SDK 計装)
→ ADOT Collector (OTLP 受信: gRPC 4317 / HTTP 4318)
→ X-Ray (送信)
→ X-Ray Service Map (サービス間依存関係の可視化)
各フローの FluentBit / ADOT Collector には IRSA で CloudWatch / X-Ray への書き込み権限を付与 します。Vol1 §4 で解説した sts:AssumeRoleWithWebIdentity + OIDC Provider の仕組みがここで活用されます。
FluentBit の IRSA に必要な最小 IAM ポリシー権限:
| 権限 | 用途 |
|---|---|
logs:CreateLogGroup | Log Group の自動作成 |
logs:CreateLogStream | Log Stream の自動作成 |
logs:PutLogEvents | ログデータの書き込み |
logs:DescribeLogStreams | 既存 Log Stream の確認 |
2-4. FluentBit / Container Insights / ADOT の選定理由
FluentBit vs Fluentd 比較
| 比較軸 | FluentBit | Fluentd |
|---|---|---|
| リソース消費 | 低 (C言語実装 / 数十MB) | 高 (Ruby実装 / 数百MB) |
| EKS Add-on対応 | あり (aws-cloudwatch-observability) | なし |
| 設定簡素度 | 高 (INI 形式 / AWS公式 ConfigMap テンプレート) | 中 (Ruby DSL / プラグイン多数) |
| 起動速度 | 高速 (C言語) | 中程度 |
| 移行難度 | 低 (EKS Add-on で即導入) | 高 (既存環境からの移行コスト大) |
EKS での新規構築では FluentBit + EKS Add-on (aws-cloudwatch-observability) が現時点のベストプラクティスです。
Container Insights の採用理由
Container Insights は EKS の Node / Pod / Container レベルのメトリクスを CloudWatch に収集する AWS ネイティブのソリューションです。
| 比較軸 | Container Insights | 自己管理 Prometheus + Grafana |
|---|---|---|
| セットアップ | EKS Add-on で即時有効化 | Helm + AlertManager + Grafana の構築が必要 |
| CloudWatch 連携 | ネイティブ (追加設定不要) | CloudWatch Exporter が必要 |
| カスタムメトリクス | 限定的 | 柔軟 (カスタムメトリクス定義可能) |
| コスト | CloudWatch カスタムメトリクス料金 | EC2 + EBS (Prometheus 実行分) |
| 推奨シーン | 導入コストを最小化したい場合 | 高度なメトリクス制御・既存 Grafana 活用 |
本記事では Container Insights (EKS Add-on) を標準として採用します。詳細は §4 で解説します。
ADOT (AWS Distro for OpenTelemetry) の採用理由
ADOT は OpenTelemetry の AWS 公式ディストリビューションです。
# ADOT Collector ConfigMap: Traces + Metrics パイプライン骨格
apiVersion: v1
kind: ConfigMap
metadata:
name: adot-collector-config
namespace: adot-col
data:
config.yaml: |
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
exporters:
awsxray:
region: ap-northeast-1
awsemf:
namespace: EKS/ContainerInsights
region: ap-northeast-1
service:
pipelines:
traces:
receivers: [otlp]
exporters: [awsxray]
metrics:
receivers: [otlp]
exporters: [awsemf]
- OpenTelemetry 標準準拠: ベンダーロックインを避けた計装が可能 (AWS 以外のバックエンドへの移行も容易)
- X-Ray ネイティブ連携:
awsxrayExporter で X-Ray Service Map に直接送信 - Prometheus 互換:
prometheusremotewriteExporter で Amazon Managed Prometheus にも送信可能
3ツールの導入優先順位
EKS 観測可能性の構築では以下の順序で進めることを推奨します。
| 優先度 | ツール | 理由 |
|---|---|---|
| 1st | FluentBit (Logs) | Pod 短命性問題を最優先で解決。障害調査の起点はログ |
| 2nd | Container Insights (Metrics) | Node / Pod リソース消費の可視化。Logs 安定後に導入 |
| 3rd | ADOT + X-Ray (Traces) | アプリケーション計装が必要。上2つが安定してから着手 |
Logs から始める理由はシンプルです。障害発生時に最初に確認するのがログです。ログが CloudWatch に流れていない状態でメトリクスやトレースを整備しても調査効率は上がりません。各ツールの詳細実装は §3-§5 で解説します。
観測可能性3本柱 EKS特有3鉄則
- 鉄則1: Pod短命性対策 — DaemonSet で永続収集 (FluentBit / ADOT はコンテナ再起動に影響されない)
- 鉄則2: sidecar は最終手段 — マルチコンテナPodでのみ採用 (管理コスト増大を避ける)
- 鉄則3: マルチクラスタ集約は CloudWatch Logs Insights + Container Insights で一元化
3. ログ収集設計 — FluentBit + CloudWatch Logs / 構造化ログ / IRSA権限設計
3-1. FluentBit を選ぶ理由と EKS Add-on
FluentBit は Fluentd の軽量後継として C 言語で実装されたログコレクターです。EKS では公式 Add-on として提供されており、マネージドな更新フロー(eksctl update addon)で Kubernetes バージョンアップに追従できます。
FluentBit vs Fluentd 比較
| 評価軸 | FluentBit | Fluentd |
|---|---|---|
| メモリ消費 | 低(~5 MB) | 高(~40 MB) |
| 設定形式 | YAML / INI | Ruby DSL |
| EKS 公式 Add-on | ○ | ✗ |
| プラグイン数 | 100+ | 700+ |
| CloudWatch 直接送信 | ○(aws-for-fluent-bit プラグイン) | ○(fluentd-plugin-cloudwatch) |
| マルチライン処理 | ○(Multiline Filter) | ○(concat filter) |
| Kubernetes メタデータ付与 | ○(Kubernetes Filter) | ○(fluent-plugin-kubernetes_metadata_filter) |
EKS 環境では FluentBit を DaemonSet として全ノードにデプロイし、/var/log/containers/ に出力された Pod の stdout を収集します。EKS Add-on を使用することで、Helm チャートの管理や手動アップグレードが不要になります。
aws-for-fluent-bit プラグインが CloudWatch Logs への直接送信を担い、IRSA(IAM Roles for Service Accounts)によって AWS 認証を行います。この仕組みは次の §3-2 で詳しく解説します。
3-2. IRSA 権限設計 — Vol1 §4 IRSA を FluentBit に応用
Vol1 §4 で学んだ IRSA の仕組みを FluentBit に応用します。FluentBit DaemonSet が CloudWatch Logs にログを書き込むためには、cloudwatch-agent ServiceAccount に IRSA アノテーションを設定し、対応する IAM Role を付与する必要があります。
必要な IAM Permission
| Permission | 用途 |
|---|---|
logs:CreateLogGroup | ロググループ自動作成 |
logs:CreateLogStream | ログストリーム作成 |
logs:PutLogEvents | ログイベント送信 |
logs:DescribeLogGroups | 既存グループの確認 |
logs:DescribeLogStreams | 既存ストリームの確認 |
Terraform 実装
# FluentBit 用 IAM Role (IRSA)
data "aws_iam_policy_document" "fluentbit_trust" {
statement {
effect = "Allow"
actions = ["sts:AssumeRoleWithWebIdentity"]
principals {
type = "Federated"
identifiers = [aws_iam_openid_connect_provider.eks.arn]
}
condition {
test = "StringEquals"
variable = "${replace(aws_iam_openid_connect_provider.eks.url, "https://", "")}:sub"
values= ["system:serviceaccount:amazon-cloudwatch:cloudwatch-agent"]
}
condition {
test = "StringEquals"
variable = "${replace(aws_iam_openid_connect_provider.eks.url, "https://", "")}:aud"
values= ["sts.amazonaws.com"]
}
}
}
resource "aws_iam_role" "fluentbit" {
name= "${local.cluster_name}-fluentbit-irsa"
assume_role_policy = data.aws_iam_policy_document.fluentbit_trust.json
}
resource "aws_iam_role_policy_attachment" "fluentbit_cloudwatch" {
role = aws_iam_role.fluentbit.name
policy_arn = "arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy"
}
信頼ポリシーの Condition で sub を system:serviceaccount:amazon-cloudwatch:cloudwatch-agent に絞り込むことで、同クラスタの他 ServiceAccount による Role 引き受けを防ぎます。
Kubernetes ServiceAccount に IRSA アノテーション設定
resource "kubernetes_service_account" "fluentbit" {
metadata {
name= "cloudwatch-agent"
namespace = "amazon-cloudwatch"
annotations = {
"eks.amazonaws.com/role-arn" = aws_iam_role.fluentbit.arn
}
}
}
Kubernetes Mutating Webhook が Pod 起動時に AWS_ROLE_ARN / AWS_WEB_IDENTITY_TOKEN_FILE 環境変数を自動注入します。FluentBit は起動時にこれらの環境変数を参照して sts:AssumeRoleWithWebIdentity を呼び出します。
Terraform を使わず kubectl で適用する場合は以下のマニフェストを使用します。
apiVersion: v1
kind: ServiceAccount
metadata:
name: cloudwatch-agent
namespace: amazon-cloudwatch
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/my-eks-cluster-fluentbit-irsa
3-3. FluentBit インストール — EKS Add-on による管理

EKS Add-on インストール
# eksctl で EKS Add-on インストール
eksctl create addon--name aws-for-fluent-bit--cluster my-eks-cluster--region ap-northeast-1--service-account-role-arn arn:aws:iam::123456789012:role/my-eks-cluster-fluentbit-irsa--force
# インストール確認
eksctl get addon --cluster my-eks-cluster --name aws-for-fluent-bit
FluentBit ConfigMap (fluent-bit.conf)
EKS Add-on はデフォルトの ConfigMap を作成しますが、マルチライン処理やフィールド追加が必要な場合はカスタム ConfigMap を適用します。
apiVersion: v1
kind: ConfigMap
metadata:
name: fluent-bit-config
namespace: amazon-cloudwatch
labels:
k8s-app: fluent-bit
data:
fluent-bit.conf: |
[SERVICE]
Flush5
Log_Level info
Daemon off
Parsers_File parsers.conf
[INPUT]
Name tail
Tagkube.*
Path /var/log/containers/*.log
Parserdocker
DB /var/fluent-bit/state/flb_kube.db
Mem_Buf_Limit 50MB
Skip_Long_LinesOn
Refresh_Interval 10
[FILTER]
Name kubernetes
Matchkube.*
Kube_URLhttps://kubernetes.default.svc:443
Kube_CA_File /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
Kube_Token_File /var/run/secrets/kubernetes.io/serviceaccount/token
Merge_Log On
Keep_LogOff
K8S-Logging.Parser On
K8S-Logging.Exclude On
[FILTER]
Name multiline
Matchkube.*
multiline.key log
multiline.parser java,go,python
[OUTPUT]
Name cloudwatch_logs
Matchkube.*
region ap-northeast-1
log_group_name/aws/eks/my-eks-cluster/workloads
log_stream_prefix${kubernetes['pod_name']}/
auto_create_grouptrue
retry_limit2
Merge_Log On で JSON 形式のアプリログを CloudWatch にネストされた構造で送信します。multiline.parser java を有効にすることで Java スタックトレースを一件のログイベントに統合します。
# ConfigMap 適用後に DaemonSet を再起動
kubectl rollout restart daemonset/fluent-bit -n amazon-cloudwatch
# Pod 稼働確認
kubectl get pods -n amazon-cloudwatch -l k8s-app=fluent-bit
# ログ出力確認
kubectl logs -n amazon-cloudwatch -l k8s-app=fluent-bit --tail=50
3-4. 構造化ログ設計
JSON ログ出力のベストプラクティス
アプリケーションが JSON 形式で stdout に出力すると、FluentBit の Merge_Log On 設定によって CloudWatch Logs に構造化データとして保存されます。
| フィールド | 説明 | 例 |
|---|---|---|
timestamp | ISO 8601 形式 | "2026-05-08T12:00:00Z" |
level | ログレベル | "INFO" / "ERROR" |
message | ログ本文 | "Request processed" |
traceId | X-Ray トレース ID | "1-5f84...c6ae" |
requestId | API リクエスト ID | "abc-123" |
durationMs | 処理時間 (ms) | 42 |
CloudWatch Logs Insights クエリ例
# エラーログを集計 (過去1時間)
fields @timestamp, @message
| filter level = "ERROR"
| stats count() as error_count by bin(5m)
| sort @timestamp desc
# 特定リクエスト ID のトレース
fields @timestamp, @message, requestId
| filter requestId = "abc-123"
| sort @timestamp asc
# レスポンスタイム 95 パーセンタイル集計
fields @timestamp, durationMs
| stats pct(durationMs, 95) as p95, avg(durationMs) as avg by bin(1m)
CloudWatch Logs → S3 エクスポート(コスト削減)
7 日以上保持するログは CloudWatch Logs よりも S3 ストレージの方がコストを大幅に削減できます。
# S3 エクスポート設定 (Kinesis Firehose 経由)
resource "aws_cloudwatch_log_subscription_filter" "s3_export" {
name= "eks-logs-to-s3"
log_group_name = "/aws/eks/my-eks-cluster/workloads"
filter_pattern = ""
destination_arn = aws_kinesis_firehose_delivery_stream.eks_logs.arn
distribution = "ByLogStream"
}
resource "aws_cloudwatch_log_group" "eks_workloads" {
name = "/aws/eks/my-eks-cluster/workloads"
retention_in_days = 7
}
CloudWatch Logs の保持期間は 7 日に設定し、長期保管は S3 + Athena クエリで対応することで、ストレージコストを最小化します。
3-5. Mermaid01: FluentBit ログパイプライン シーケンス
sequenceDiagram
participant Pod as Pod (stdout)
participant FB as FluentBit (DaemonSet)
participant STS as AWS STS
participant CWL as CloudWatch Logs
Pod->>FB: ログ出力 (stdout → /var/log/containers/)
FB->>FB: INPUT: tail プラグイン (ファイル読み取り)
FB->>FB: FILTER: Kubernetes メタデータ付与
FB->>FB: FILTER: multiline (スタックトレース統合)
FB->>STS: sts:AssumeRoleWithWebIdentity (IRSA)
STS-->>FB: 一時認証情報
FB->>CWL: OUTPUT: cloudwatch_logs プラグイン (PutLogEvents)
CWL-->>FB: 200 OK
Note over FB,CWL: ロググループ: /aws/eks/{cluster}/workloads
Pod が stdout に出力したログは FluentBit が /var/log/containers/ から読み取り、Kubernetes Filter でメタデータ(Pod 名 / Namespace / ラベル)を付与した後、IRSA で取得した一時認証情報を使って CloudWatch Logs に送信します。
3-6. FluentBit + IRSA 3鉄則
FluentBit + IRSA 3鉄則(設定ミスで CloudWatch Logs に届かない)
- 鉄則1:
cloudwatch-agentServiceAccount の IRSA アノテーション設定必須(eks.amazonaws.com/role-arn)。アノテーションがないと IRSA の Mutating Webhook が動作せず、AWS 認証が失敗する - 鉄則2: IAM Role 信頼ポリシーの Condition
audがsts.amazonaws.comか確認(OIDC プロバイダー URL もhttps://を除いた形式で指定)。不一致でAssumeRoleWithWebIdentityが拒否される - 鉄則3: FluentBit DaemonSet は
kube-systemまたはamazon-cloudwatchNamespace にデプロイ(アプリ Namespace に入れると IAM Role の ServiceAccount 指定が広がり最小権限原則が崩れる)
FluentBit による ログ収集(§3)と次の §4 で扱う メトリクス収集(Container Insights)を組み合わせることで、EKS クラスタの可観測性を 3 シグナル(ログ・メトリクス・トレース)のうち 2 つカバーできます。IRSA アーキテクチャは §4 の ADOT Collector でも同様のパターンで適用します。
4. メトリクス収集 (山場) — Container Insights + ADOT Collector + Prometheus互換 / kube-state-metrics
EKS のメトリクス収集は Container Insights + ADOT Collector + kube-state-metrics の3点セットで構築するのが本番運用の定石だ。前章 (§3) で Karpenter NodePool を設計したが、そのノードが実際にどのくらいのリソースを使っているかを把握するには、本章のメトリクス収集基盤が不可欠になる。
4-1. なぜEKSのメトリクス収集は難しいか
EC2 単体なら CloudWatch Agent を入れるだけでメトリクスが取れる。しかし EKS では以下の理由でそれだけでは不十分だ。
| 課題 | 詳細 |
|---|---|
| Pod 単位のメトリクス | 同じノード上の複数 Pod を識別してメトリクスを分けたい |
| Deployment / ReplicaSet の状態 | replicas / readyReplicas などの Kubernetes オブジェクト状態は CloudWatch では取れない |
| Prometheus 互換クエリ | 既存の Prometheus/Grafana スタックと連携したい場合に障壁がある |
| ノードの自動増減 | Karpenter でノードが動的に増減するため固定 IP での監視が困難 |
これを解決するのが 3点セット:
- Container Insights: AWS マネージドの EKS メトリクス収集 (CPU / Memory / Network / Pod / Node)
- ADOT Collector (AWS Distro for OpenTelemetry): Prometheus Receiver でメトリクスをスクレイプし、CloudWatch Metrics / X-Ray に転送
- kube-state-metrics: Kubernetes オブジェクト (Deployment / Pod / PVC 等) の状態を Prometheus 形式で公開
4-2. Container Insights の有効化
Container Insights は EKS Add-on として提供されており、CloudWatch Agent と FluentBit がバンドルされている。
Terraform での Container Insights 有効化:
# EKS Add-on: Amazon CloudWatch Observability (Container Insights)
resource "aws_eks_addon" "cloudwatch_observability" {
cluster_name = aws_eks_cluster.main.name
addon_name= "amazon-cloudwatch-observability"
addon_version= "v1.7.0-eksbuild.1"
service_account_role_arn = aws_iam_role.cloudwatch_agent_irsa.arn
resolve_conflicts_on_create = "OVERWRITE"
resolve_conflicts_on_update = "OVERWRITE"
tags = {
Component = "observability"
}
}
# IRSA: CloudWatch Agent が CloudWatch / X-Ray に書き込む権限
resource "aws_iam_role_policy_attachment" "cloudwatch_agent" {
role = aws_iam_role.cloudwatch_agent_irsa.name
policy_arn = "arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy"
}
Container Insights で確認できるメトリクス:
# Container Insights のメトリクス一覧 (CloudWatch Metrics)
aws cloudwatch list-metrics--namespace ContainerInsights--dimensions Name=ClusterName,Value=my-eks-cluster--query 'Metrics[].MetricName'--output table
# 特定 Pod の CPU 使用率 (直近60分・1分間隔)
aws cloudwatch get-metric-statistics--namespace ContainerInsights--metric-name pod_cpu_utilization--dimensions Name=ClusterName,Value=my-eks-clusterName=Namespace,Value=productionName=PodName,Value=my-app-pod--start-time "$(date -u -d '1 hour ago' +%Y-%m-%dT%H:%M:%SZ)"--end-time "$(date -u +%Y-%m-%dT%H:%M:%SZ)"--period 60--statistics Average--output table
Container Insights が動作していれば、CloudWatch コンソールの「Container Insights」ダッシュボードで Cluster / Node / Pod 単位のメトリクスをグラフで確認できる。
4-3. ADOT Collector のインストールと設定
ADOT Collector は OpenTelemetry の AWS 最適化ディストリビューションだ。Prometheus Receiver でメトリクスをスクレイプし、CloudWatch Metrics (EMF) と X-Ray にデータを転送できる。
Helm による ADOT Collector のインストール:
# AWS OTEL Collector の Helm Chart リポジトリを追加
helm repo add aws-otel https://aws-observability.github.io/aws-otel-helm-charts
helm repo update
# インストール (values.yaml で設定を上書き)
helm install adot-collector aws-otel/adot-collector--namespace observability--create-namespace-f adot-values.yaml
Terraform Helm Release での管理:
resource "helm_release" "adot_collector" {
name = "adot-collector"
repository = "https://aws-observability.github.io/aws-otel-helm-charts"
chart= "adot-collector"
namespace = "observability"
version = "0.3.0"
create_namespace = true
values = [file("${path.module}/helm-values/adot-values.yaml")]
depends_on = [
aws_eks_addon.cloudwatch_observability,
kubernetes_service_account.adot_collector_sa
]
}
ADOT Collector ConfigMap (Pipeline 設計):
ADOT Collector の核心は receivers → processors → exporters のパイプライン設計だ。
# adot-collector-config.yaml (ConfigMap として適用)
apiVersion: v1
kind: ConfigMap
metadata:
name: adot-collector-config
namespace: observability
data:
config.yaml: |
receivers:
prometheus:
config:
scrape_configs:
- job_name: kube-state-metrics
static_configs:
- targets: ['kube-state-metrics.kube-system.svc.cluster.local:8080']
- job_name: node-exporter
kubernetes_sd_configs:
- role: node
relabel_configs:
- source_labels: [__address__]
regex: (.+):(.+)
target_label: __address__
replacement: ${1}:9100
processors:
batch:
timeout: 60s
send_batch_size: 1000
resourcedetection:
detectors: [eks, ec2]
timeout: 15s
exporters:
awsemf:
namespace: EKS/Metrics
region: ap-northeast-1
log_group_name: /aws/eks/my-cluster/metrics
awsxray:
region: ap-northeast-1
service:
pipelines:
metrics:
receivers: [prometheus]
processors: [batch, resourcedetection]
exporters: [awsemf]
traces:
receivers: [otlp]
processors: [batch]
exporters: [awsxray]
receivers.prometheus セクションで kube-state-metrics と node-exporter のエンドポイントを指定する。processors.resourcedetection の eks で EKS クラスター名・ノード情報などのメタデータを自動付与できる。
4-4. kube-state-metrics の連携
kube-state-metrics は Kubernetes API Server を監視し、Deployment / ReplicaSet / Pod / PVC などのオブジェクト状態を Prometheus 形式で公開する。
Helm インストール:
# kube-state-metrics のインストール
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
helm install kube-state-metrics prometheus-community/kube-state-metrics--namespace kube-system--set image.tag=v2.10.1
ADOT Collector でのスクレイプ対象確認:
# kube-state-metrics のエンドポイントを確認
kubectl get svc -n kube-system kube-state-metrics
# メトリクス一覧の確認 (kubectl port-forward でローカルアクセス)
kubectl port-forward svc/kube-state-metrics 8080:8080 -n kube-system &
curl -s localhost:8080/metrics | grep kube_deployment_status_replicas
# 出力例:
# kube_deployment_status_replicas{namespace="production",deployment="my-app"} 3
# kube_deployment_status_replicas_available{namespace="production",deployment="my-app"} 3
ADOT Collector ConfigMap にスクレイプ設定を追加:
# kube-state-metrics スクレイプ設定 (adot-collector-config.yaml の receivers.prometheus に追記)
scrape_configs:
- job_name: kube-state-metrics
static_configs:
- targets:
- 'kube-state-metrics.kube-system.svc.cluster.local:8080'
metric_relabel_configs:
- source_labels: [__name__]
regex: 'kube_(deployment|pod|replicaset|pvc)_.+'
action: keep# Deployment / Pod / ReplicaSet / PVC 関連のみ保持
4-5. Prometheus互換 PromQL クエリ例
Container Insights と ADOT Collector でメトリクスが CloudWatch に到達したら、CloudWatch Metrics Insights で PromQL 互換クエリを使って可視化できる。
Pod CPU 使用率のクエリ:
# CloudWatch Metrics Insights: Pod CPU 使用率 (直近30分の平均)
aws cloudwatch get-metric-data--metric-data-queries '[
{
"Id": "cpu",
"Expression": "SELECT AVG(pod_cpu_utilization) FROM ContainerInsights WHERE ClusterName = '"'"'my-eks-cluster'"'"' GROUP BY PodName",
"Period": 60
}
]'--start-time "$(date -u -d '30 minutes ago' +%Y-%m-%dT%H:%M:%SZ)"--end-time "$(date -u +%Y-%m-%dT%H:%M:%SZ)"--output json
Deployment レプリカ数の監視:
# kube-state-metrics 由来: Deployment のレプリカ数を CloudWatch で確認
aws cloudwatch get-metric-statistics--namespace EKS/Metrics--metric-name kube_deployment_status_replicas_available--dimensions Name=deployment,Value=my-appName=namespace,Value=production--start-time "$(date -u -d '1 hour ago' +%Y-%m-%dT%H:%M:%SZ)"--end-time "$(date -u +%Y-%m-%dT%H:%M:%SZ)"--period 60--statistics Minimum--output table
CloudWatch ダッシュボードへの追加:
# Terraform で CloudWatch ダッシュボードを作成
aws cloudwatch put-dashboard--dashboard-name "EKS-Observability"--dashboard-body file://dashboard-body.json
4-7. ADOT Collector の IRSA 権限設計 (Vol1 §4 との架橋)
ADOT Collector は CloudWatch に書き込み、X-Ray にトレースを送信するため、適切な IAM 権限が必要だ。前章 (§4 IRSA) で学んだパターンをそのまま使う。
ADOT Collector IRSA の Terraform 実装:
# ADOT Collector 専用 IRSA ロール
data "aws_iam_policy_document" "adot_irsa_assume" {
statement {
effect = "Allow"
actions = ["sts:AssumeRoleWithWebIdentity"]
principals {
type = "Federated"
identifiers = [aws_iam_openid_connect_provider.eks.arn]
}
condition {
test = "StringEquals"
variable = "${local.oidc_url}:sub"
values= ["system:serviceaccount:observability:adot-collector-sa"]
}
condition {
test = "StringEquals"
variable = "${local.oidc_url}:aud"
values= ["sts.amazonaws.com"]
}
}
}
resource "aws_iam_role" "adot_collector" {
name= "adot-collector-irsa"
assume_role_policy = data.aws_iam_policy_document.adot_irsa_assume.json
}
# CloudWatch + X-Ray への書き込み権限
resource "aws_iam_role_policy" "adot_policy" {
name = "adot-collector-policy"
role = aws_iam_role.adot_collector.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"cloudwatch:PutMetricData",
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents",
"xray:PutTraceSegments",
"xray:PutTelemetryRecords"
]
Resource = "*"
}
]
})
}
# ADOT Collector ServiceAccount (IRSA アノテーション付き)
resource "kubernetes_service_account" "adot_collector_sa" {
metadata {
name= "adot-collector-sa"
namespace = "observability"
annotations = {
"eks.amazonaws.com/role-arn" = aws_iam_role.adot_collector.arn
}
}
}
Karpenter NodePool (§3) との接続: ADOT Collector の Pod に nodeSelector や tolerations を設定し、Observability 専用の Karpenter NodePool に配置することで、アプリケーション Pod のリソース競合を避けられる。
Helm values.yaml での NodeSelector 設定 (Karpenter NodePool 親和性):
# adot-values.yaml: Observability 専用 NodePool への配置
collector:
serviceAccount:
name: adot-collector-sa
annotations:
eks.amazonaws.com/role-arn: "arn:aws:iam::123456789012:role/adot-collector-irsa"
nodeSelector:
workload-type: observability# Karpenter NodePool の NodeSelectorLabel と一致させる
tolerations:
- key: "dedicated"
operator: "Equal"
value: "observability"
effect: "NoSchedule"
resources:
requests:
cpu: "200m"
memory: "512Mi"
limits:
cpu: "500m"
memory: "1Gi"
Karpenter の NodePool で workload-type: observability ラベルを持つ Node を起動するよう設定すれば、ADOT Collector は Observability 専用ノードに配置される。

ADOT Collector 設計3鉄則 (設定ミスで Container Insights / X-Ray にデータが届かない)
- 鉄則1: Helm Chart 管理必須 —
kubectl applyで直接デプロイすると更新管理が困難になる。values.yamlで ConfigMap・IRSA・NodeSelector を一元管理し、helm upgradeで安全に更新すること。 - 鉄則2: NodePool / NodeSelector を指定する — Karpenter の Observability 専用 NodePool に ADOT Collector を配置し (Vol1 §3 接続)、アプリケーション Pod のリソース競合を避ける。未指定だと ADOT Collector が突然 Evict される。
- 鉄則3: kube-state-metrics のスクレイプ確認 — ADOT Collector の Prometheus Receiver スクレイプ対象に
kube-state-metricsが含まれているかkubectl execで確認すること。含まれていないと Deployment / Pod / PVC のメトリクスが CloudWatch に届かない。
Container Insights EKS Add-on セットアップ → 公式ドキュメント
5. 分散トレーシング — ADOT × X-Ray / OpenTelemetry SDK / Service Map / W3C TraceContext
5-1. 分散トレーシングとは
マイクロサービス化された EKS 環境では、1つのユーザーリクエストが複数の Pod (サービス) を経由して処理される。ログだけでは「どのサービスで遅延が発生したか」「どのサービス間の通信がエラーになったか」を特定するのが困難だ。分散トレーシングはこの問題を解決するために、リクエストの全経路を1本の「トレース」として可視化する技術だ。
主要な概念
| 概念 | 説明 |
|---|---|
| Trace | 1つのリクエストが複数サービスをまたいで処理される全体の流れ。一意の TraceId で識別される |
| Span | Trace の中の個々の処理単位。サービス内の関数呼び出しやサービス間の HTTP/gRPC 呼び出しなど |
| TraceContext | TraceId + SpanId をサービス間で引き継ぐためのメタデータ。HTTP ヘッダーに付与して伝播する |
| W3C TraceContext | IETF 標準の TraceContext 仕様。traceparent / tracestate ヘッダーで伝播する |
| Sampling | 全トレースを記録するとコストが高くなるため、一定割合 (例: 5%) だけ記録する仕組み |
EKS での課題: TraceContext 伝播
EKS 環境では Pod が短命で動的に変化する。Pod 間の HTTP / gRPC 通信に traceparent ヘッダーを追加し、全サービスが TraceContext を引き継ぐことで、ALB Ingress から始まったリクエストの全経路が1つのトレースとして記録される。
ALB Ingress Controller (Vol1 §5) は HTTP ヘッダーをそのまま転送するため、クライアントが付与した traceparent ヘッダーが Pod まで到達する。Service Map では ALB ノードが最上位に表示され、下流サービスへの依存関係が可視化される。
5-2. ADOT Collector + X-Ray 連携設計
ADOT Collector の Trace Pipeline
ADOT (AWS Distro for OpenTelemetry) Collector は、OpenTelemetry プロトコル (OTLP) でスパンを受信し X-Ray フォーマットに変換して送信するコンポーネントだ。§3 の FluentBit と同様に DaemonSet としてデプロイすることで、同じノード上の全 Pod のスパンを一元収集する。
Trace Pipeline の処理フロー:
- OTLP Receiver: Pod から gRPC (ポート 4317) または HTTP (ポート 4318) でスパンを受信する
- resourcedetection Processor: EKS / EC2 のメタデータ (クラスター名 / ノード名 / Pod 名) をスパンに付与する
- batch Processor: スパンをバッチにまとめてネットワーク効率を上げる
- X-Ray Exporter: SigV4 認証で AWS X-Ray API にスパンを送信する
ADOT ConfigMap — Trace Pipeline 設定
apiVersion: v1
kind: ConfigMap
metadata:
name: adot-collector-config
namespace: amazon-cloudwatch
data:
config.yaml: |
extensions:
health_check: {}
sigv4auth:
region: ap-northeast-1
service: xray
receivers:
otlp:
protocols:
grpc:
endpoint: "0.0.0.0:4317"
http:
endpoint: "0.0.0.0:4318"
processors:
batch:
send_batch_size: 50
timeout: 5s
resourcedetection:
detectors: [eks, ec2]
timeout: 5s
override: false
exporters:
awsxray:
region: ap-northeast-1
no_verify_ssl: false
auth:
authenticator: sigv4auth
service:
extensions: [health_check, sigv4auth]
pipelines:
traces:
receivers: [otlp]
processors: [resourcedetection, batch]
exporters: [awsxray]
W3C TraceContext の伝播設定
W3C TraceContext を有効にするには、OpenTelemetry SDK 側で tracecontext Propagator を設定する。X-Amzn-Trace-Id (X-Ray 独自フォーマット) との混在はスパンの断絶を引き起こすため、全サービスで W3C TraceContext に統一すること。
apiVersion: v1
kind: ConfigMap
metadata:
name: otel-sdk-config
namespace: production
data:
OTEL_PROPAGATORS: "tracecontext,baggage"
OTEL_EXPORTER_OTLP_ENDPOINT: "http://adot-collector.amazon-cloudwatch.svc.cluster.local:4318"
OTEL_EXPORTER_OTLP_PROTOCOL: "http/protobuf"
OTEL_TRACES_SAMPLER: "parentbased_traceidratio"
OTEL_TRACES_SAMPLER_ARG: "0.05"
OTEL_TRACES_SAMPLER_ARG: "0.05" でデフォルト 5% サンプリング。本番コスト削減に重要だ。課金フローや重要 API は X-Ray Sampling Rules でサービス / URL パスごとに 100% に設定する。
ADOT Collector DaemonSet デプロイ
ADOT Collector を DaemonSet として全ノードにデプロイする。IRSA を付与した ServiceAccount を使い、コントローラーが X-Ray API に SigV4 認証でアクセスする。
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: adot-collector
namespace: amazon-cloudwatch
spec:
selector:
matchLabels:
app: adot-collector
template:
metadata:
labels:
app: adot-collector
spec:
serviceAccountName: adot-collector # IRSA 設定済み SA
containers:
- name: adot-collector
image: public.ecr.aws/aws-observability/aws-otel-collector:latest
args: ["--config=/conf/config.yaml"]
ports:
- containerPort: 4317 # gRPC
hostPort: 4317
- containerPort: 4318 # HTTP
hostPort: 4318
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "512Mi"
volumeMounts:
- name: adot-config
mountPath: /conf
volumes:
- name: adot-config
configMap:
name: adot-collector-config
5-3. OpenTelemetry SDK 計装
各 Pod のアプリに OpenTelemetry SDK を組み込み、スパンを ADOT Collector に送信する。SDK 計装は「自動計装」と「手動計装」の2種類があり、まずは自動計装から導入して徐々に手動スパンを追加する流れが推奨だ。
Python (FastAPI) — 自動計装
# 自動計装ライブラリのインストール
pip installopentelemetry-distroopentelemetry-exporter-otlp-proto-httpopentelemetry-instrumentation-fastapiopentelemetry-instrumentation-httpxopentelemetry-instrumentation-sqlalchemy
# Dockerfile CMD を自動計装コマンドに変更
# CMD ["uvicorn", "app.main:app"] → 以下に変更
CMD ["opentelemetry-instrument", "uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8080"]
opentelemetry-instrument コマンドを使うと、ソースコード変更なしで FastAPI / httpx / SQLAlchemy の全リクエストを自動計装できる。環境変数 (OTEL_*) は上記 ConfigMap を Pod の envFrom で参照する。
Java — -javaagent による自動計装
Java アプリには aws-opentelemetry-agent.jar を -javaagent オプションで付与する。initContainer で agent JAR を emptyDir にコピーし、アプリコンテナが参照する構成が一般的だ。
apiVersion: apps/v1
kind: Deployment
metadata:
name: java-service
namespace: production
spec:
replicas: 2
selector:
matchLabels:
app: java-service
template:
metadata:
labels:
app: java-service
spec:
initContainers:
- name: otel-agent-init
image: public.ecr.aws/aws-observability/adot-autoinstrumentation-java:latest
command: ["cp", "/javaagent.jar", "/otel-agent/javaagent.jar"]
volumeMounts:
- name: otel-agent-volume
mountPath: /otel-agent
containers:
- name: java-service
image: your-registry/java-service:latest
env:
- name: JAVA_TOOL_OPTIONS
value: "-javaagent:/otel-agent/javaagent.jar"
- name: OTEL_SERVICE_NAME
value: "java-service"
envFrom:
- configMapRef:
name: otel-sdk-config
ports:
- containerPort: 8080
volumeMounts:
- name: otel-agent-volume
mountPath: /otel-agent
volumes:
- name: otel-agent-volume
emptyDir: {}
5-4. X-Ray Service Map 確認手順
X-Ray Service Map は AWS コンソールの CloudWatch から確認できる。Service Map はサービス間の依存関係・レイテンシ・エラー率を視覚的に表示し、問題の局所化を助ける。
確認手順
- AWS コンソール → CloudWatch → X-Ray トレース → Service Map を開く
- 時間範囲を設定し、ALB Ingress (Vol1 §5 で構築) から始まるリクエストフローを確認する
- 各ノード (サービス) をクリックすると Latency 分布 / エラー率 / スループットが表示される
- 赤くなっているノードがエラーの多いサービス — クリックしてトレースを掘り下げ、原因 Span を特定する
Vol1 §5 ALB Ingress との接続
ALB Ingress Controller 経由のリクエストは、Client → ALB → Service A → Service B の経路で Service Map に表示される。traceparent ヘッダーが全サービスで伝播されている場合、単一トレースとして可視化される。Service Map に断絶が現れる場合は、どこかのサービスが TraceContext を引き継いでいないことを示す。
Filter Expression を使った絞り込み
X-Ray コンソールの Filter Expression で特定の条件に絞り込める。
responsetime > 1— レスポンス 1 秒以上のトレースだけ表示error = true— エラーが発生したトレースだけ表示service("java-service") { fault = true }— 特定サービスで fault が発生したトレースだけ表示
X-Ray Sampling Rules の設定
AWS コンソールの X-Ray → Sampling ルール画面から、サービスやパスごとにサンプリング率を設定できる。
| ルール名 | サービス名 | URL パス | サンプリング率 |
|---|---|---|---|
| default | * | * | 5% |
| payment-critical | payment-service | /api/payment/* | 100% |
| health-check-exclude | * | /health | 0% |
ヘルスチェックエンドポイント (/health) を 0% にすることで、無駄なトレースを排除してコストと可読性を改善できる。
5-5. Mermaid02: ADOT × X-Ray 分散トレーシング シーケンス
sequenceDiagram
participant Client as Client
participant ALB as ALB Ingress
participant SA as Service A (Pod)
participant SB as Service B (Pod)
participant AC as ADOT Collector
participant XR as X-Ray
Client->>ALB: HTTP リクエスト (traceparent ヘッダー付与)
ALB->>SA: W3C TraceContext 伝播
SA->>SB: 内部 gRPC 呼出し (TraceContext 引き継ぎ)
SA->>AC: OTLP Span エクスポート (gRPC 4317)
SB->>AC: OTLP Span エクスポート (gRPC 4317)
AC->>XR: X-Ray Trace 送信 (SigV4 認証)
Note over AC,XR: Service Map 生成 (ALB→A→B の依存関係)
X-Ray Service Map 設計鉄則
- 鉄則1: W3C TraceContext (traceparent / tracestate) を全サービスで統一 — X-Amzn-Trace-Id との混在はスパン断絶を引き起こす
- 鉄則2: ADOT Collector は Sidecar ではなく DaemonSet で運用 — Pod 短命性によるスパンロストを防ぐためバッファとして機能させる
- 鉄則3: X-Ray Sampling ルールで本番コストを削減 — デフォルト 5% + 課金フローや SLA 監視対象パスは 100% のハイブリッド設定
6. 詰まりポイント7選 図解

FluentBit・ADOT・X-Ray を本番導入すると、設定の細部で詰まるポイントが集中する。以下の7パターンは現場で頻出するトラブルとその根本原因・対処法をまとめたものだ。
詰まりポイント1: FluentBit IRSA 信頼ポリシー設定ミス
症状: FluentBit Pod が CloudWatch Logs にログを送信できない。Pod ログに NoCredentialProviders や AccessDenied が出力される。
原因: IRSA の信頼ポリシーに2種類の誤りが混入しやすい。① StringEquals の OIDC Provider URL が別クラスターまたは別リージョンのものになっている。② aud Condition に sts.amazonaws.com ではなく誤った値が設定されている。
まず正しい OIDC Issuer URL を確認する:
aws eks describe-cluster --name my-cluster \
--query "cluster.identity.oidc.issuer" --output text
aws iam list-open-id-connect-providers
正しい信頼ポリシー:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789012:oidc-provider/oidc.eks.ap-northeast-1.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"oidc.eks.ap-northeast-1.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE:aud": "sts.amazonaws.com",
"oidc.eks.ap-northeast-1.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE:sub": "system:serviceaccount:amazon-cloudwatch:fluent-bit"
}
}
}
]
}
詰まりポイント1 対処法
aws eks describe-clusterで OIDC Issuer URL を取得し、信頼ポリシーの URL と完全一致させるaudCondition は必ずsts.amazonaws.comを指定するsubはsystem:serviceaccount:NAMESPACE:SA_NAMEの形式で namespace を正確に指定する
詰まりポイント2: FluentBit ConfigMap 反映遅延
症状: ConfigMap を更新したのに FluentBit の動作が変わらない。古いフィルター設定が残り続ける。
原因: Kubernetes の ConfigMap は Volume マウントで DaemonSet Pod に注入されるが、ConfigMap を更新しても DaemonSet の Pod は自動再起動しない。kubelet のキャッシュ更新 (最大 1-2 分) を待った後も、FluentBit プロセス自体は ConfigMap をリロードしない。
正しい更新手順:
kubectl rollout restart daemonset/fluent-bit -n amazon-cloudwatch
kubectl rollout status daemonset/fluent-bit -n amazon-cloudwatch
kubectl exec -n amazon-cloudwatch \
$(kubectl get pod -n amazon-cloudwatch -l name=fluent-bit -o jsonpath='{.items[0].metadata.name}') \
-- cat /fluent-bit/etc/fluent-bit.conf
詰まりポイント2 対処法
- ConfigMap 更新後は必ず
kubectl rollout restart daemonset/fluent-bitを実行する - Helm 管理の場合は ConfigMap 変更が DaemonSet の annotation ハッシュに反映され自動再起動される構成にしておく
- 更新確認は
kubectl execで設定ファイルの中身を直接確認する
詰まりポイント3: sidecar 誤注入でホスト側ログが見えない
症状: FluentBit を sidecar として Pod に注入したら他 Pod のログが収集できなくなった。/var/log/containers/ 配下のファイルが見えない。
原因: FluentBit の sidecar モードは同じ Pod 内の Volume にしかアクセスできない。クラスター全体のコンテナログを収集するには DaemonSet 構成が正解だ。
DaemonSet 正解構成:
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: fluent-bit
namespace: amazon-cloudwatch
spec:
selector:
matchLabels:
name: fluent-bit
template:
spec:
serviceAccountName: fluent-bit
volumes:
- name: varlog
hostPath:
path: /var/log
- name: varlibdockercontainers
hostPath:
path: /var/lib/docker/containers
containers:
- name: fluent-bit
image: public.ecr.aws/aws-observability/aws-for-fluent-bit:latest
volumeMounts:
- name: varlog
mountPath: /var/log
readOnly: true
- name: varlibdockercontainers
mountPath: /var/lib/docker/containers
readOnly: true
詰まりポイント3 対処法
- クラスター全体のログ収集には sidecar ではなく DaemonSet モードを使う
/var/logと/var/lib/docker/containersをhostPathでマウントする- sidecar は特定 Pod のみのログを別の宛先に送る場合に限定して使用する
詰まりポイント4: ADOT metrics_exporter の namespace / dimension 設定不備
症状: CloudWatch にカスタムメトリクスが届かない。または届いているが namespace が default になっており、dimension が想定と異なる。
原因: ADOT Collector の awsemf exporter で namespace と dimension_rollup_option が未設定のままになっている。namespace が空の場合は CloudWatch が default namespace として扱い、アラーム設定やダッシュボードが機能しなくなる。
正しい awsemf exporter 設定:
exporters:
awsemf:
region: ap-northeast-1
namespace: MyApp/EKS
log_group_name: /aws/eks/my-cluster/metrics
log_stream_name: /metrics
dimension_rollup_option: NoDimensionRollup
metric_declarations:
- dimensions:
- [ClusterName, Namespace, ServiceName]
metric_name_selectors:
- ".*"
Terraform で ADOT ConfigMap を管理する例:
resource "kubernetes_config_map" "adot_collector" {
metadata {
name= "adot-collector-config"
namespace = "amazon-cloudwatch"
}
data = {
"config.yaml" = templatefile("${path.module}/templates/adot-config.yaml.tpl", {
cluster_name= var.cluster_name
region= var.aws_region
metrics_namespace = var.metrics_namespace
})
}
}
詰まりポイント4 対処法
awsemfexporter のnamespaceに CloudWatch 上で管理したい名前を必ず指定するdimension_rollup_option: NoDimensionRollupでディメンションの自動集約を無効化し意図通りのメトリクス構造を維持する- Terraform テンプレートで ConfigMap を管理することでパラメーターの一元管理を実現する
詰まりポイント5: W3C TraceContext 伝播の切断
症状: X-Ray Service Map でサービス間のトレースが繋がらない。各サービスが独立したトレースとして表示され、依存関係グラフが構成できない。
原因: サービス間の HTTP 呼び出しで traceparent ヘッダー (W3C TraceContext) がコピーされていない。ADOT SDK の auto-instrumentation が設定されていない、または独自の HTTP クライアントがヘッダーを引き継がない実装になっているケースで発生する。
Java Spring Boot サービスへの ADOT auto-instrumentation 設定例:
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-service
namespace: my-app
spec:
template:
metadata:
annotations:
instrumentation.opentelemetry.io/inject-java: "true"
spec:
containers:
- name: my-service
image: my-service:latest
env:
- name: OTEL_PROPAGATORS
value: "xray,tracecontext,baggage"
- name: OTEL_EXPORTER_OTLP_ENDPOINT
value: "http://adot-collector.amazon-cloudwatch.svc.cluster.local:4317"
- name: OTEL_SERVICE_NAME
value: "my-service"
詰まりポイント5 対処法
OTEL_PROPAGATORS環境変数にxray,tracecontextを設定してヘッダー伝播を有効化する- 全サービスに auto-instrumentation annotation を付与するか、SDK を明示的に組み込む
- 独自 HTTP クライアントを使う場合は Interceptor / Middleware でヘッダーをコピーする
詰まりポイント6: X-Ray Service Map に一部サービスが表示されない
症状: X-Ray コンソールの Service Map に一部のサービスが欠けている。トレースデータは届いているのに依存関係グラフに現れない。
原因: ① SDK 計装が漏れているサービスはトレースを生成しないため Service Map に登場しない。② Sampling Rules でそのサービスのリクエストが 0% サンプリングになっている。
現状の確認コマンド:
aws xray get-sampling-rules \
--query "SamplingRuleRecords[*].SamplingRule.{Name:RuleName,Rate:FixedRate,Reservoir:ReservoirSize}" \
--output table
aws xray get-trace-summaries \
--start-time $(date -v-1H +%s) \
--end-time $(date +%s) \
--filter-expression 'service("my-missing-service")' \
--query "TraceSummaries[*].Id" --output text
詰まりポイント6 対処法
- Service Map に表示されないサービスが Sampling Rules で除外されていないか
aws xray get-sampling-rulesで確認する - Sampling Rules の
fixed_rate: 0.0になっているルールを確認し、意図的かどうかを判断する - SDK 計装の漏れは
get-trace-summariesでサービス名を指定して検索し、0件なら計装の問題と判断する
詰まりポイント7: Helm Chart 更新後に ADOT Collector が再起動ループ
症状: helm upgrade 後に ADOT Collector Pod が CrashLoopBackOff になる。ログに invalid configuration や failed to connect が出力される。
原因: ① ConfigMap の YAML 構文エラー (インデント誤り / Helm テンプレートの | と |- の混用) でコレクターが起動失敗。② exporters セクションのエンドポイント設定が誤っており接続失敗ループに入る。
診断コマンド:
kubectl logs -l app=adot-collector -n amazon-cloudwatch --previous
kubectl get configmap adot-collector-config -n amazon-cloudwatch \
-o jsonpath='{.data.config\.yaml}' \
| python3 -c "import sys, yaml; yaml.safe_load(sys.stdin); print('YAML OK')"
helm status adot-collector -n amazon-cloudwatch
helm get values adot-collector -n amazon-cloudwatch
詰まりポイント7 対処法
- Helm upgrade 前に
helm templateでレンダリング結果を確認し、YAML 構文を事前検証する - ConfigMap の YAML は Python の
yaml.safe_loadで構文チェックを自動化する - CrashLoopBackOff 時は
kubectl logs --previousで直前の起動ログを確認しエラーメッセージを特定する
7. アンチパターン→正解パターン変換演習 (Terraform + Helm 両形式)
EKS 観測可能性の設定を正しく理解するため、よくある壊れた設定を自分で直す演習を5問用意した。各問題の「壊れた設定」を見て、何が問題かを考えてから「正解パターン」と照らし合わせてほしい。
演習1: FluentBit OUTPUT プラグイン設定ミス
壊れた設定:
[OUTPUT]
Name cloudwatch_logs
Match *
regionus-east-1
log_group_name /eks/my-cluster
log_stream_name$(kubernetes['pod_name'])
auto_create_group false
❌ 問題: region が us-east-1 になっている (クラスターは ap-northeast-1)。auto_create_group false のため Log Group が未作成だと書き込みに失敗する。
正解パターン:
[OUTPUT]
Name cloudwatch_logs
Match *
regionap-northeast-1
log_group_name /eks/my-cluster
log_stream_name$(kubernetes['pod_name'])
auto_create_group true
log_key log
log_format json/emf
retry_limit False
✅ 解説: region はクラスターのリージョンに合わせる。auto_create_group true で Log Group を自動作成。retry_limit False でログロストを防ぐ。
演習2: ADOT Collector Prometheus Receiver スクレイプ設定ミス
壊れた設定:
receivers:
prometheus:
config:
scrape_configs:
- job_name: 'kubernetes-pods'
scrape_interval: 5s
kubernetes_sd_configs:
- role: pod
relabel_configs:
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
action: keep
regex: true
❌ 問題: scrape_interval: 5s が短すぎて負荷が高い。名前空間フィルターがなく全 Pod がターゲット。regex: true は文字列 "true" にマッチしないため annotation フィルターが機能しない。
正解パターン:
receivers:
prometheus:
config:
scrape_configs:
- job_name: 'kubernetes-pods'
scrape_interval: 30s
kubernetes_sd_configs:
- role: pod
namespaces:
names:
- my-app
- monitoring
relabel_configs:
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
action: keep
regex: "true"
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_port]
action: replace
target_label: __address__
regex: (.+)
replacement: $1
✅ 解説: scrape_interval は 30s 以上が推奨。namespaces.names で対象を絞る。regex: "true" をクォートして文字列マッチを保証する。
演習3: X-Ray Sampling ルール 100% 収集によるコスト爆発
壊れた設定 (Terraform):
resource "aws_xray_sampling_rule" "all_traces" {
rule_name= "catch-all"
priority = 9999
reservoir_size = 100
fixed_rate = 1.0
url_path = "*"
host = "*"
http_method = "*"
service_type= "*"
service_name= "*"
resource_arn= "*"
version = 1
}
❌ 問題: fixed_rate = 1.0 (100% サンプリング) で全リクエストのトレースを収集。高トラフィック環境では X-Ray 料金が急増する。
正解パターン (Terraform):
resource "aws_xray_sampling_rule" "health_check_exclude" {
rule_name= "health-check-exclude"
priority = 1
reservoir_size = 0
fixed_rate = 0.0
url_path = "/health"
host = "*"
http_method = "GET"
service_type= "*"
service_name= "*"
resource_arn= "*"
version = 1
}
resource "aws_xray_sampling_rule" "default_sampling" {
rule_name= "default"
priority = 9999
reservoir_size = 5
fixed_rate = 0.05
url_path = "*"
host = "*"
http_method = "*"
service_type= "*"
service_name= "*"
resource_arn= "*"
version = 1
}
✅ 解説: ヘルスチェックを priority=1 のルールで 0% 除外。デフォルトルールは 5% + reservoir_size=5 に抑えてコストを大幅削減する。
演習4: IRSA ServiceAccount と信頼ポリシーの OIDC URL 不一致
壊れた設定:
apiVersion: v1
kind: ServiceAccount
metadata:
name: adot-collector
namespace: amazon-cloudwatch
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/ADOTCollectorRole
信頼ポリシー (誤り):
{
"Condition": {
"StringEquals": {
"oidc.eks.us-east-1.amazonaws.com/id/WRONGCLUSTERID:sub": "system:serviceaccount:amazon-cloudwatch:adot-collector"
}
}
}
❌ 問題: 信頼ポリシーの OIDC URL が別リージョン (us-east-1) かつ別クラスター ID になっている。aud Condition も省略されている。
正解パターン:
{
"Condition": {
"StringEquals": {
"oidc.eks.ap-northeast-1.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE:aud": "sts.amazonaws.com",
"oidc.eks.ap-northeast-1.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE:sub": "system:serviceaccount:amazon-cloudwatch:adot-collector"
}
}
}
✅ 解説: OIDC URL はクラスターのリージョン + クラスター固有 ID を使用。aud: sts.amazonaws.com Condition を必ず追加する。
演習5: ADOT Collector Helm values.yaml で resources.limits 未設定による OOM
壊れた設定 (Helm values.yaml):
adotCollector:
image:
repository: public.ecr.aws/aws-observability/aws-otel-collector
tag: latest
mode: deployment
replicas: 1
config: |
receivers:
prometheus: {}
exporters:
awsemf: {}
service:
pipelines:
metrics:
receivers: [prometheus]
exporters: [awsemf]
❌ 問題: resources.limits が未設定のため高負荷時に OOMKilled される。tag: latest を本番で使うと予期しないバージョンアップが起きる。
正解パターン (Helm values.yaml):
adotCollector:
image:
repository: public.ecr.aws/aws-observability/aws-otel-collector
tag: v0.40.0
mode: deployment
replicas: 2
resources:
requests:
cpu: 200m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
config: |
receivers:
prometheus: {}
exporters:
awsemf: {}
service:
pipelines:
metrics:
receivers: [prometheus]
exporters: [awsemf]
Terraform で Helm リリースを管理する例:
resource "helm_release" "adot_collector" {
name = "adot-collector"
repository = "https://aws-otel.github.io/helm-charts"
chart= "aws-otel-collector"
version = "0.3.0"
namespace = "amazon-cloudwatch"
set {
name = "adotCollector.image.tag"
value = "v0.40.0"
}
set {
name = "adotCollector.resources.limits.memory"
value = "512Mi"
}
set {
name = "adotCollector.resources.limits.cpu"
value = "500m"
}
set {
name = "adotCollector.replicas"
value = "2"
}
}
✅ 解説: resources.limits は必ず設定する。tag: latest は本番禁止 — 固定バージョンを指定。Terraform で Helm リリースを管理することでバージョンドリフトを防ぐ。
8. まとめ + EKS Vol3予告 + Vol1双方向クロスリンク
8-1. 本記事のまとめ
本記事では EKS クラスターの観測可能性を FluentBit・Container Insights・ADOT の3本柱で実現する方法を解説した。各セクションで達成した5つのゴールを振り返る。
- §1 観測可能性の設計思想: ログ・メトリクス・トレースを分離して収集し、CloudWatch と X-Ray で一元管理する全体像を把握した
- §2 FluentBit DaemonSet 構築: hostPath マウント + IRSA で Node 全体のコンテナログを CloudWatch Logs へ転送する構成を実装した
- §3 Container Insights 有効化: CloudWatch Agent のメトリクス収集と Container Insights ダッシュボードで CPU・メモリ・ネットワーク使用率を可視化した
- §4 ADOT Collector 導入: OpenTelemetry Collector をクラスター内に配置し、アプリケーションのカスタムメトリクスと分散トレースを収集する基盤を整えた
- §5 X-Ray 分散トレース設定: ADOT auto-instrumentation と X-Ray Sampling Rules で本番コストを抑えながらサービス間のトレースを可視化した
「コンテナを動かす」から「コンテナが見える状態で動かす」への移行が完了した。
8-2. EKS 観測可能性 落とし穴10選
§6 の詰まりポイント7選に加えて、実務でよく見る3つの追加パターンを含めた落とし穴10選を以下にまとめる。
- OIDC Provider URL の誤り — リージョン・クラスター ID が違うと IRSA が機能しない
- ConfigMap 更新後の rollout restart 漏れ — DaemonSet は ConfigMap 変更で自動再起動しない
- sidecar で hostPath ログが見えない — クラスター全体収集には DaemonSet が正解
- awsemf exporter の namespace 未設定 — CloudWatch メトリクスが
defaultnamespace に混在する - W3C TraceContext ヘッダーの伝播漏れ — Service Map のグラフが途切れる原因の大半
- Sampling Rules 100% 設定 — 高トラフィック環境でのコスト爆発、ヘルスチェックの除外漏れ
- Helm upgrade 後の CrashLoopBackOff — YAML 構文エラーと接続先設定ミスが主因
- Pod Disruption Budget 未設定 — Node 入れ替え時に FluentBit・ADOT が全滅するリスク
- CloudWatch Logs の Log Group 保持期間 Never に放置 — ログコストが際限なく増加し続ける
- X-Ray サービス名の重複 — 複数クラスターで同じ
OTEL_SERVICE_NAMEを使うと Service Map が混在する
8-3. EKS Vol3 予告
Vol2 で「EKS クラスターが見える状態で動く」を実現した。次回 Vol3 では「見えるクラスターをコスト効率よく削る」をテーマに取り上げる予定だ。
具体的には Karpenter の Consolidation と Disruption Budget の設計、Spot インスタンスと On-Demand の組み合わせ戦略、および Pod Identity への IRSA 移行パスを実践的に解説する。
次回: EKS本番運用 Vol3 予告
Vol3 では Karpenter Consolidation × Spot 最適化 × Pod Identity 移行を取り上げる予定です。
Karpenter の Disruption Budget 設計、On-Demand と Spot の混在戦略、IRSA から Pod Identity への段階移行パスを実践的に解説します。
8-4. EKS本番運用シリーズ + 関連記事
EKS本番運用シリーズ 関連記事
- Vol1: クラスタ設計 × IRSA × ALB Ingress — EKS本番運用の基礎固め
- Vol2 (本記事): 観測可能性 — FluentBit × Container Insights × ADOT
- Vol3 (近日公開): コスト最適化 × Karpenter深掘り (予告)
EKS 深掘りシリーズ: