EKS on Fargate で Pod のコンテナログを CloudWatch Logs に出力してみた(Built-in Fluent Bit 編)
はじめに
クラウド事業本部、あきやまです。
直近、EKS on Fargate で複数コンテナを持つ Pod のログを CloudWatch Logs に集約したいケースがあり、Fargate の組み込み Fluent Bit(Built-in log router)を使って検証してみました。
本記事では、2つの Pod にそれぞれ 2つのコンテナを配置し、各コンテナのログを CloudWatch Logs に出力するまでの手順を紹介します。
実際、検証するとEC2 と Fargate の違いによって設定にも影響することがあるのでそちらについてもまとめていけたらと思います。
環境
| 項目 | 値 |
|---|---|
| OS | macOS Tahoe 26.3 |
| AWS CLI | v2.32.32 |
| kubectl | v1.35.2 |
| eksctl | v0.222.0 |
| EKS クラスターバージョン | 1.31 |
| リージョン | ap-northeast-1 |
結論
EKS on Fargate ではノードの概念が抽象化されているため、DaemonSet が使えません。代わりに、AWS が提供する組み込み Fluent Bit(Built-in log router)を利用して Pod のコンテナログを CloudWatch Logs へ出力できます。
| 項目 | 結果 |
|---|---|
| 複数コンテナのログ収集 | Pod 内の全コンテナのログが自動的に収集される |
| コンテナ別のログ分離 | log_stream_prefix を使うと、ログストリーム名にコンテナ名が含まれ自然に分離される |
| 設定の簡潔さ | aws-observability Namespace + aws-logging ConfigMap の 2リソースのみ |
| 主な制約 | ConfigMap は 5,300文字上限、使える Output プラグインは6種類のみ |
※ Fargate では [SERVICE] / [INPUT] セクションを制御できません。AWS が自動管理します。
EKS on Fargate と on EC2 のログ設定差異
本題に入る前に、Fargate と EC2 でのログ収集アーキテクチャの違いを整理します。
アーキテクチャの違い
EKS on EC2(DaemonSet 方式)
EKS on Fargate(Built-in log router)
比較表
| 比較軸 | EKS on EC2 (DaemonSet) | EKS on Fargate (Built-in) |
|---|---|---|
| デプロイ方式 | ノード1台に1 Pod(DaemonSet) | AWS が Pod ごとに自動注入 |
| 設定方法 | Helm chart / DaemonSet YAML | aws-observability ns の ConfigMap のみ |
[INPUT] 設定 |
完全に自由(tail, systemd, tcp 等) | 設定不可(AWS 管理) |
[SERVICE] 設定 |
自由(Flush, Buffer サイズ等) | 設定不可 |
| Output プラグイン | すべての Fluent Bit プラグイン | 6種類のみ(CW, OpenSearch, Kinesis 系) |
| Filter プラグイン | すべて使用可能 | 8種類のみ |
| ホスト/カーネルログ | 収集可能 | 不可 |
| Datadog/Splunk 直接転送 | ネイティブプラグインで可能 | 不可(Kinesis 経由が必要) |
| Lua スクリプト | 使用可能 | 不可 |
| バッファ永続化 | SQLite DB で可能 | 不可(インメモリのみ) |
| IAM 設定 | IRSA または Pod Identity | Pod Execution Role に付与 |
| Container Insights | 完全対応(Enhanced 含む) | 基本機能のみ ADOT 経由で対応(Enhanced Observability は非対応) |
| 設定の動的反映 | ConfigMap 変更後、Pod の再起動または Hot Reload が必要 | 新規 Pod のみ(既存 Pod は再起動が必要) |
| Fluent Bit バージョン管理 | Helm chart で固定可能 | AWS 管理(選択不可) |
| 運用負荷 | DaemonSet のアップグレード管理が必要 | AWS が管理 |
Fargate でできないこと
- ホストログ(
/var/log/messages,journald等)の収集 - Container Insights の Enhanced Observability(DaemonSet ベースの CloudWatch Agent が必要。基本機能は ADOT 経由で利用可能)
- Datadog / Splunk / Grafana Loki への直接転送(Kinesis Firehose 経由なら可能)
- Fluent Bit のカスタムプラグインや Lua スクリプトの利用
- クラッシュ後のログ再読み込み(バッファ永続化なし)
環境準備
検証用の EKS クラスターを eksctl で構築します。Fargate 専用クラスターとして作成し、demo Namespace 用の Fargate Profile も同時に定義します。
EKS クラスターの作成
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
name: fargate-logging-test
region: ap-northeast-1
version: "1.31"
# Fargate 専用のためマネージドノードグループは作成しない
fargateProfiles:
# kube-system と CoreDNS 用(Fargate 専用クラスターで必須)
- name: fp-system
selectors:
- namespace: kube-system
- namespace: default
# 検証用ワークロード
- name: fp-demo
selectors:
- namespace: demo
# aws-observability(ConfigMap 用)
- name: fp-observability
selectors:
- namespace: aws-observability
eksctl create cluster -f cluster-config.yaml
※ クラスター作成には 15〜20分ほどかかります。
CoreDNS の Fargate 対応
eksctl で Fargate 専用クラスターを作成した場合、CoreDNS が EC2 ノードを待ち続けて Pending のままになることがあります。以下で Fargate 上で動作するように修正します。
# CoreDNS から EC2 用のアノテーションを削除
kubectl patch deployment coredns \
-n kube-system \
--type json \
-p='[{"op": "remove", "path": "/spec/template/metadata/annotations/eks.amazonaws.com~1compute-type"}]'
# Pod を再起動して Fargate で起動させる
kubectl rollout restart deployment coredns -n kube-system
# CoreDNS が Running になることを確認
kubectl get pods -n kube-system -l k8s-app=kube-dns
Pod Execution Role 名の確認
eksctl が自動作成した Pod Execution Role 名を確認します。後続の IAM ポリシーアタッチで使用します。
# Fargate Profile から Pod Execution Role ARN を取得
aws eks describe-fargate-profile \
--cluster-name fargate-logging-test \
--fargate-profile-name fp-demo \
--region ap-northeast-1 \
--query 'fargateProfile.podExecutionRoleArn' \
--output text
出力例:
arn:aws:iam::123456789012:role/eksctl-fargate-logging-test-FargatePodExecutionRole-XXXXX
末尾のロール名(eksctl-fargate-logging-test-FargatePodExecutionRole-XXXXX)をメモしておきます。
やってみた
環境が整ったので、ここからは実際の検証手順です。以下の構成を構築します。
Step 1: IAM ポリシーの準備
Fargate の組み込み Fluent Bit は Pod Execution Role の権限でログを送信します。EC2 の場合は IRSA でしたが、Fargate では Pod Execution Role に直接ポリシーをアタッチします。
まず自分のアカウント ID を確認します。
aws sts get-caller-identity --query Account --output text
確認したアカウント ID で <ACCOUNT_ID> を置き換えてください。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:CreateLogStream",
"logs:CreateLogGroup",
"logs:DescribeLogStreams",
"logs:PutLogEvents",
"logs:PutRetentionPolicy"
],
"Resource": "arn:aws:logs:ap-northeast-1:<ACCOUNT_ID>:log-group:/eks/*"
}
]
}
# ポリシーを作成
aws iam create-policy \
--policy-name eks-fargate-logging-policy \
--policy-document file://eks-fargate-logging-policy.json
# 環境準備で確認した Pod Execution Role 名を指定してアタッチ
aws iam attach-role-policy \
--policy-arn arn:aws:iam::<ACCOUNT_ID>:policy/eks-fargate-logging-policy \
--role-name <環境準備で確認した Pod Execution Role 名>
※ eksctl で作成した場合、ロール名は eksctl-fargate-logging-test-FargatePodExecutionRole-XXXXX のような形式です。「環境準備」でメモした値を使用してください。
※ EC2 の場合は ServiceAccount + IRSA(または Pod Identity)で Fluent Bit DaemonSet に権限を付与しますが、Fargate では Pod Execution Role にアタッチするだけです。
Step 2: aws-observability Namespace の作成
Fargate の組み込み Fluent Bit を有効化するには、aws-observability という名前の Namespace を作成し、aws-observability: enabled ラベルを付与する必要があります。
kind: Namespace
apiVersion: v1
metadata:
name: aws-observability
labels:
aws-observability: enabled # このラベルが必須
kubectl apply -f namespace-aws-observability.yaml
Step 3: aws-logging ConfigMap の作成
ここが Fargate ログ設定の核心部分です。aws-observability Namespace に aws-logging という名前の ConfigMap を作成します。
重要な制約:
- ConfigMap 名は
aws-logging固定 - Namespace は
aws-observability固定 - 合計 5,300文字以内
[SERVICE]/[INPUT]セクションは記載不可(AWS が管理)[FILTER]/[OUTPUT]/[PARSER]セクションのみ記載可能- 環境変数(
${VAR}形式)は使用不可(値はすべてハードコード)
apiVersion: v1
kind: ConfigMap
metadata:
name: aws-logging
namespace: aws-observability
data:
# Fluent Bit 自身のプロセスログを CloudWatch に送る場合は "true"
flb_log_cw: "false"
parsers.conf: |
[PARSER]
Name crio
Format Regex
Regex ^(?<time>[^ ]+) (?<stream>stdout|stderr) (?<logtag>P|F) (?<log>.*)$
Time_Key time
Time_Format %Y-%m-%dT%H:%M:%S.%L%z
filters.conf: |
[FILTER]
Name parser
Match *
Key_name log
Parser crio
Reserve_Data On
[FILTER]
Name kubernetes
Match kube.*
Merge_Log On
Keep_Log Off
Buffer_Size 0
Kube_Meta_Cache_TTL 300s
output.conf: |
[OUTPUT]
Name cloudwatch_logs
Match kube.*
region ap-northeast-1
log_group_name /eks/my-cluster/demo
log_stream_prefix from-fluent-bit-
log_retention_days 7
auto_create_group true
kubectl apply -f configmap-aws-logging.yaml
各セクションの役割を補足します。
parsers.conf: Fargate は containerd/CRI-O 形式でコンテナログを出力します。crio パーサーでログ行をパースし、タイムスタンプ・ストリーム種別(stdout/stderr)・ログ本文を分離します。
filters.conf:
parserフィルター: 生ログにcrioパーサーを適用kubernetesフィルター: Pod 名・Namespace・コンテナ名などの Kubernetes メタデータをログレコードに付与
output.conf: CloudWatch Logs への出力設定です。log_stream_prefix を指定すると、ストリーム名は from-fluent-bit- + コンテナのログファイルパス(Pod名・コンテナ名を含む)になります。
log_stream_template について
log_stream_template を使えばコンテナ名でストリームを分離できるように見えますが、Fargate 環境では正常に動作しないケースが報告されています(containers-roadmap#1854)。kubernetes フィルターのメタデータ取得が失敗するとログが一切 CloudWatch に届かなくなるため、本記事では安定動作する log_stream_prefix を採用しています。
Step 4: demo Namespace とアプリケーションのデプロイ
2つの Pod にそれぞれ 2つのコンテナ(nginx + log-generator)を配置する Deployment を作成します。
apiVersion: v1
kind: Namespace
metadata:
name: demo
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
namespace: demo
spec:
replicas: 2
selector:
matchLabels:
app: web-app
template:
metadata:
labels:
app: web-app
spec:
containers:
# コンテナ 1: nginx(メインアプリケーション)
- name: nginx
image: nginx:1.25
ports:
- containerPort: 80
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "200m"
memory: "256Mi"
# コンテナ 2: log-generator(サイドカー / ログ生成用)
- name: log-generator
image: busybox:1.36
command:
- /bin/sh
- -c
- |
i=0
while true; do
i=$((i+1))
echo "{\"level\":\"info\",\"container\":\"log-generator\",\"msg\":\"heartbeat #${i}\",\"ts\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"}"
sleep 10
done
resources:
requests:
cpu: "50m"
memory: "64Mi"
limits:
cpu: "100m"
memory: "128Mi"
kubectl apply -f namespace-demo.yaml
kubectl apply -f deployment-web-app.yaml
Step 5: Pod の起動確認
kubectl get pods -n demo -o wide
NAME READY STATUS RESTARTS AGE IP NODE
web-app-7d4b9c6f8-xk2p9 2/2 Running 0 2m 10.0.1.123 fargate-ip-10-0-1-123...
web-app-7d4b9c6f8-r7m3t 2/2 Running 0 2m 10.0.2.456 fargate-ip-10-0-2-456...
READY が 2/2 であること(2コンテナとも起動済み)、NODE が fargate- で始まること(Fargate で起動)を確認します。
各コンテナのログが出力されているか確認します。
# nginx コンテナのログ
kubectl logs -n demo -l app=web-app -c nginx --tail=5
# log-generator コンテナのログ
kubectl logs -n demo -l app=web-app -c log-generator --tail=5
{"level":"info","container":"log-generator","msg":"heartbeat #1","ts":"2026-04-08T05:00:00Z"}
{"level":"info","container":"log-generator","msg":"heartbeat #2","ts":"2026-04-08T05:00:10Z"}
...
Step 6: Fluent Bit の有効化確認
Pod の Annotations に Logging: LoggingEnabled が表示されていれば、組み込み Fluent Bit が有効化されています。
kubectl describe pod -n demo -l app=web-app | grep -A 2 "Annotations"
Annotations: CapacityProvisioned: 0.25vCPU 0.5GB
Logging: LoggingEnabled
Logging: LoggingEnabled が表示されない場合は、以下を確認してください。
aws-observabilityNamespace にaws-observability: enabledラベルがあるかaws-loggingConfigMap がaws-observabilityNamespace に存在するか- Pod Execution Role に CloudWatch Logs のポリシーがアタッチされているか
Step 7: CloudWatch Logs でログを確認
ストリーム名にコンテナ名(nginx, log-generator)が含まれているため、コンテナ単位でログが分離されていることがわかります。

注意点・制約
検証を通じて発見した制約やハマりポイントをまとめます。
| # | カテゴリ | 内容 |
|---|---|---|
| 1 | ConfigMap の反映 | ConfigMap の変更は新規 Pod にのみ適用されます。既存 Pod には反映されないため、変更後は kubectl rollout restart deployment が必要です |
| 2 | 文字数上限 | ConfigMap の合計サイズは 5,300文字以内です。複雑な設定を書くと上限に達する場合があります |
| 3 | 環境変数が使えない | ${AWS_REGION} のような環境変数展開は ConfigMap 内で許可されていませんリンク。値はすべてハードコードする必要があります |
| 4 | auto_create_group の値 |
小文字の true を使用してください。大文字の True は拒否されます |
| 5 | log_stream_template の問題 |
Kubernetes メタデータの取得に失敗するとログが届かなくなる既知の問題があります。log_stream_prefix の使用を推奨します |
| 6 | Output プラグインの制限 | 使用可能なプラグインは cloudwatch_logs, cloudwatch, es, kinesis_firehose, firehose, kinesis の6種類のみです |
| 7 | メモリオーバーヘッド | 組み込み Fluent Bit は Pod あたり 50〜100MB のメモリを消費します。Fargate の課金に影響します |
| 8 | ネットワーク要件 | VPC から CloudWatch Logs エンドポイントへのアウトバウンド接続が必要です。NAT Gateway または VPC Interface Endpoint(com.amazonaws.ap-northeast-1.logs)を設定してください |
まとめ
本記事では、EKS on Fargate の組み込み Fluent Bit を使って、複数コンテナの Pod ログを CloudWatch Logs に出力する方法を検証しました。
実現できたこと:
- 2つの Pod × 2コンテナの構成で、すべてのコンテナログが CloudWatch Logs に出力されることを確認
log_stream_prefixを使うことで、ストリーム名にコンテナ名が含まれ、コンテナ単位でのログ分離が可能aws-observabilityNamespace +aws-loggingConfigMap の 2リソースだけで設定が完了
Fargate vs EC2 の使い分け:
- アプリケーションの stdout/stderr を CloudWatch Logs / OpenSearch / Kinesis に送るだけなら → Fargate で十分
- ホストログの収集、Datadog/Splunk 等への直接転送、カスタムプラグインが必要なら → EC2 の DaemonSet 方式
Fargate の組み込み Fluent Bit はカスタマイズ性では EC2 に劣りますが、運用負荷の低さとセットアップの簡潔さが大きなメリットです。CloudWatch Logs への出力が主要件であれば、十分に実用的な選択肢だと感じました。参考になれば幸いです。







