EKS on Fargate で Pod のコンテナログを CloudWatch Logs に出力してみた(Built-in Fluent Bit 編)

EKS on Fargate で Pod のコンテナログを CloudWatch Logs に出力してみた(Built-in Fluent Bit 編)

2026.04.08

はじめに

クラウド事業本部、あきやまです。
直近、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 クラスターの作成

cluster-config.yaml
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> を置き換えてください。

eks-fargate-logging-policy.json
{
  "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 ラベルを付与する必要があります。

namespace-aws-observability.yaml
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} 形式)は使用不可(値はすべてハードコード)
configmap-aws-logging.yaml
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 を作成します。

namespace-demo.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: demo
deployment-web-app.yaml
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...

READY2/2 であること(2コンテナとも起動済み)、NODEfargate- で始まること(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-observability Namespace に aws-observability: enabled ラベルがあるか
  • aws-logging ConfigMap が aws-observability Namespace に存在するか
  • Pod Execution Role に CloudWatch Logs のポリシーがアタッチされているか

Step 7: CloudWatch Logs でログを確認

ストリーム名にコンテナ名(nginx, log-generator)が含まれているため、コンテナ単位でログが分離されていることがわかります。
スクリーンショット 2026-04-08 11.23.10

注意点・制約

検証を通じて発見した制約やハマりポイントをまとめます。

# カテゴリ 内容
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-observability Namespace + aws-logging ConfigMap の 2リソースだけで設定が完了

Fargate vs EC2 の使い分け:

  • アプリケーションの stdout/stderr を CloudWatch Logs / OpenSearch / Kinesis に送るだけなら → Fargate で十分
  • ホストログの収集、Datadog/Splunk 等への直接転送、カスタムプラグインが必要なら → EC2 の DaemonSet 方式

Fargate の組み込み Fluent Bit はカスタマイズ性では EC2 に劣りますが、運用負荷の低さとセットアップの簡潔さが大きなメリットです。CloudWatch Logs への出力が主要件であれば、十分に実用的な選択肢だと感じました。参考になれば幸いです。

参考

この記事をシェアする

関連記事