EKS本番運用 Vol2 観測可能性 FluentBit Container Insights ADOT 統合入門

目次

1. なぜEKSの観測可能性で詰まるか — Vol1架橋と観測可能性の3つの壁

1-1. この記事のゴール

Vol1でEKSクラスタを立ち上げ、KarpenterとIRSAを動かした。
Podがデプロイされ、ALB Ingressを通じてリクエストが届く — ここまで来たら次のステップがある。
「動いている」から「見える状態で動いている」への昇格だ。

本番運用でEKSが「動かせる」状態になると、次に直面するのは「何が起きているかわからない」という壁だ。
Podがクラッシュしてもログが残っていない。メトリクスを見ようとしたらCloudWatchに何も届いていない。
トレースを仕込んだはずがX-Rayのコンソールが空白のまま — こうした詰まりシーンはEKS観測可能性の設定に特有のパターンがある。

本記事を読み終えると、次の5つが達成できる。

  1. FluentBit DaemonSet を EKS Add-on として展開し、コンテナログを CloudWatch Logs へ構造化転送できる
  2. Container Insights を有効化し、Podレベルの CPU/メモリ/ネットワークメトリクスを CloudWatch Metrics で可視化できる
  3. ADOT Collector (AWS Distro for OpenTelemetry) を展開し、アプリケーショントレースを X-Ray へ送信できる
  4. IRSA権限設計 を理解し、FluentBit/ADOT に最小権限の IAM ロールを割り当てられる
  5. 詰まりポイント7選 を事前に把握し、本番運用でよく見るログ欠損・メトリクス途切れ・トレース未着信を未然に防げる

Vol1が「動かすところまで」だとすれば、Vol2は「見える状態で動かす」ための技術スタックを体系的に習得する章だ。

この記事の構成

セクション内容
§1 (本章)観測可能性の3つの壁 / 前提条件
§2観測可能性3本柱整理 — Logs / Metrics / Traces × EKS特有課題
§3FluentBit構成 — DaemonSet / IRSA / CloudWatch Logs構造化転送
§4Container Insights — メトリクス設計 / ダッシュボード
§5ADOT 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 ロール設計
IRSAADOT 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_Limitstorage.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本番運用シリーズ ナビゲーション

関連シリーズ: 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 LogsFluentBit DaemonSet
Metrics「どのくらいの負荷か」の数値集計 (CPU / Memory / リクエスト数 / エラー率)Container Insights / PrometheusADOT Collector
Traces「どこで時間がかかったか」のリクエスト追跡 (マイクロサービス間の依存関係可視化)X-Ray Service MapADOT 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 Fargateawslogs ドライバーで自動転送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 問題

ログ収集コンテナの配置方式には sidecarDaemonSet の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 全体アーキテクチャ

fig01: 観測可能性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:CreateLogGroupLog Group の自動作成
logs:CreateLogStreamLog Stream の自動作成
logs:PutLogEventsログデータの書き込み
logs:DescribeLogStreams既存 Log Stream の確認

2-4. FluentBit / Container Insights / ADOT の選定理由

FluentBit vs Fluentd 比較

比較軸FluentBitFluentd
リソース消費低 (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 ネイティブ連携: awsxray Exporter で X-Ray Service Map に直接送信
  • Prometheus 互換: prometheusremotewrite Exporter で Amazon Managed Prometheus にも送信可能

3ツールの導入優先順位

EKS 観測可能性の構築では以下の順序で進めることを推奨します。

優先度ツール理由
1stFluentBit (Logs)Pod 短命性問題を最優先で解決。障害調査の起点はログ
2ndContainer Insights (Metrics)Node / Pod リソース消費の可視化。Logs 安定後に導入
3rdADOT + 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 比較

評価軸FluentBitFluentd
メモリ消費低(~5 MB)高(~40 MB)
設定形式YAML / INIRuby 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"
}

信頼ポリシーの Conditionsubsystem: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 による管理

fig02: FluentBit + IRSA + CloudWatch Logs データフロー

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 に構造化データとして保存されます。

フィールド説明
timestampISO 8601 形式"2026-05-08T12:00:00Z"
levelログレベル"INFO" / "ERROR"
messageログ本文"Request processed"
traceIdX-Ray トレース ID"1-5f84...c6ae"
requestIdAPI リクエスト 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-agent ServiceAccount の IRSA アノテーション設定必須(eks.amazonaws.com/role-arn)。アノテーションがないと IRSA の Mutating Webhook が動作せず、AWS 認証が失敗する
  • 鉄則2: IAM Role 信頼ポリシーの Condition audsts.amazonaws.com か確認(OIDC プロバイダー URL も https:// を除いた形式で指定)。不一致で AssumeRoleWithWebIdentity が拒否される
  • 鉄則3: FluentBit DaemonSet は kube-system または amazon-cloudwatch Namespace にデプロイ(アプリ 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.resourcedetectioneks で 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 に nodeSelectortolerations を設定し、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 の NodePoolworkload-type: observability ラベルを持つ Node を起動するよう設定すれば、ADOT Collector は Observability 専用ノードに配置される。


fig03: Container Insights × ADOT × kube-state-metrics 3点セット (山場)

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本の「トレース」として可視化する技術だ。

主要な概念

概念説明
Trace1つのリクエストが複数サービスをまたいで処理される全体の流れ。一意の TraceId で識別される
SpanTrace の中の個々の処理単位。サービス内の関数呼び出しやサービス間の HTTP/gRPC 呼び出しなど
TraceContextTraceId + SpanId をサービス間で引き継ぐためのメタデータ。HTTP ヘッダーに付与して伝播する
W3C TraceContextIETF 標準の 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 の処理フロー:

  1. OTLP Receiver: Pod から gRPC (ポート 4317) または HTTP (ポート 4318) でスパンを受信する
  2. resourcedetection Processor: EKS / EC2 のメタデータ (クラスター名 / ノード名 / Pod 名) をスパンに付与する
  3. batch Processor: スパンをバッチにまとめてネットワーク効率を上げる
  4. 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 はサービス間の依存関係・レイテンシ・エラー率を視覚的に表示し、問題の局所化を助ける。

確認手順

  1. AWS コンソール → CloudWatch → X-Ray トレース → Service Map を開く
  2. 時間範囲を設定し、ALB Ingress (Vol1 §5 で構築) から始まるリクエストフローを確認する
  3. 各ノード (サービス) をクリックすると Latency 分布 / エラー率 / スループットが表示される
  4. 赤くなっているノードがエラーの多いサービス — クリックしてトレースを掘り下げ、原因 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-criticalpayment-service/api/payment/*100%
health-check-exclude*/health0%

ヘルスチェックエンドポイント (/health) を 0% にすることで、無駄なトレースを排除してコストと可読性を改善できる。

X-Ray + ADOT 詳細設定ガイド (深掘り記事)

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選 図解

fig04: 詰まりポイント7パターン体系図

FluentBit・ADOT・X-Ray を本番導入すると、設定の細部で詰まるポイントが集中する。以下の7パターンは現場で頻出するトラブルとその根本原因・対処法をまとめたものだ。


詰まりポイント1: FluentBit IRSA 信頼ポリシー設定ミス

症状: FluentBit Pod が CloudWatch Logs にログを送信できない。Pod ログに NoCredentialProvidersAccessDenied が出力される。

原因: 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 と完全一致させる
  • aud Condition は必ず sts.amazonaws.com を指定する
  • subsystem: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/containershostPath でマウントする
  • sidecar は特定 Pod のみのログを別の宛先に送る場合に限定して使用する

詰まりポイント4: ADOT metrics_exporter の namespace / dimension 設定不備

症状: CloudWatch にカスタムメトリクスが届かない。または届いているが namespace が default になっており、dimension が想定と異なる。

原因: ADOT Collector の awsemf exporter で namespacedimension_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 対処法

  • awsemf exporter の 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 configurationfailed 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

❌ 問題: regionus-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選を以下にまとめる。

  1. OIDC Provider URL の誤り — リージョン・クラスター ID が違うと IRSA が機能しない
  2. ConfigMap 更新後の rollout restart 漏れ — DaemonSet は ConfigMap 変更で自動再起動しない
  3. sidecar で hostPath ログが見えない — クラスター全体収集には DaemonSet が正解
  4. awsemf exporter の namespace 未設定 — CloudWatch メトリクスが default namespace に混在する
  5. W3C TraceContext ヘッダーの伝播漏れ — Service Map のグラフが途切れる原因の大半
  6. Sampling Rules 100% 設定 — 高トラフィック環境でのコスト爆発、ヘルスチェックの除外漏れ
  7. Helm upgrade 後の CrashLoopBackOff — YAML 構文エラーと接続先設定ミスが主因
  8. Pod Disruption Budget 未設定 — Node 入れ替え時に FluentBit・ADOT が全滅するリスク
  9. CloudWatch Logs の Log Group 保持期間 Never に放置 — ログコストが際限なく増加し続ける
  10. 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本番運用シリーズ 関連記事

EKS 深掘りシリーズ: