Amazon ECS Managed Instances が json-file ログドライバーに対応したので、S3、Logsへの転送を試してみた

Amazon ECS Managed Instances が json-file ログドライバーに対応したので、S3、Logsへの転送を試してみた

Amazon ECS Managed Instances の json-file ログドライバーと、Managed Daemons のログドライバーボリュームで、nginx のログを AWS 提供の Fluent Bit から S3 と CloudWatch Logs に送りました。プラグインの追加もカスタムイメージも要りませんでした。ログはコンテナ別のディレクトリに置かれ、runtimeId で区別できます。
2026.11.14

はじめに

2026年11月4日に、Amazon ECS Managed Instances が json-file ログドライバーに対応しました。json-file を指定したコンテナは、ログを JSON のローカルファイルに書きます。Managed Daemons に加わったログドライバーボリュームを daemon にマウントすると、daemon がそのファイルを読めます。

https://aws.amazon.com/jp/about-aws/whats-new/2026/11/amazon-ecs-managed-instances-json-file-log-driver/

この記事では、nginx のログを daemon の Fluent Bit で読んで S3 と CloudWatch Logs に転送します。

検証環境

項目 内容
リージョン 東京(ap-northeast-1)
インスタンス c6a.large(x86_64)
nginx 1.31.8
AWS for Fluent Bit 2.34.3.20261102(Fluent Bit 1.9.10)

設定

ログの経路は、アプリ側と daemon 側の2つのタスク定義で決まります。

アプリのタスク定義

アプリ側は logConfiguration に json-file を指定するだけです。タスク定義は次のとおりです。

{
  "family": "jsonlog-demo-nginx",
  "requiresCompatibilities": ["MANAGED_INSTANCES"],
  "networkMode": "awsvpc",
  "cpu": "256",
  "memory": "512",
  "runtimePlatform": {"cpuArchitecture": "X86_64", "operatingSystemFamily": "LINUX"},
  "containerDefinitions": [
    {
      "name": "nginx",
      "image": "public.ecr.aws/nginx/nginx:latest",
      "essential": true,
      "portMappings": [{"containerPort": 80, "protocol": "tcp"}],
      "logConfiguration": {"logDriver": "json-file"}
    }
  ]
}

nginx の公式イメージは、access.log を stdout に、error.log を stderr にリンクしているので、アクセスログとエラーログが json-file に入ります。

daemon のタスク定義

daemon 側では、volumes にログドライバーボリュームを定義し、mountPoints でコンテナにマウントします。ボリュームに入るのは、json-file を指定したコンテナのログだけです。

https://docs.aws.amazon.com/AmazonECS/latest/developerguide/managed-daemons-task-definitions.html

  "volumes": [
    {
      "name": "app-logs",
      "logDriverVolumeConfiguration": {
        "driver": "json-file"
      }
    }
  ],
      "mountPoints": [
        {
          "sourceVolume": "app-logs",
          "containerPath": "/var/log/tasks"
        }
      ],

この抜粋を含む定義を daemon-taskdef.json に保存し、次のコマンドで登録しました。

aws ecs register-daemon-task-definition --cli-input-json file://daemon-taskdef.json

転送

Fluent Bit の設定

daemon の Fluent Bit が tail 入力でボリュームを読み、s3 と cloudwatch_logs の2つの出力に送ります。マウント先の下には、コンテナごとのディレクトリができ、その中に <runtimeId>-json.log が置かれます。ドキュメントはディレクトリ名を「コンテナID」と書いていますが、実測では describe-tasks が返す runtimeId と一致しました。

daemon のコンテナには、AWS 提供の public.ecr.aws/aws-observability/aws-for-fluent-bit:stable をそのまま使いました。コンテナの user は指定せず、イメージの既定のまま動かしています。イメージは既定の設定ファイルを固定のパスから読むため、起動コマンドを上書きして自分の設定を渡しました。環境変数 FB_CONF に埋め込んだ設定を /tmp/fluent-bit.conf へ書き出し、fluent-bit -c に渡しています。コンテナ定義の該当部分は次のとおりです。

"environment": [{"name": "FB_CONF", "value": "<下の設定ファイルの内容>"}],
"command": ["sh", "-c", "printf '%s' \"$FB_CONF\" > /tmp/fluent-bit.conf && exec /fluent-bit/bin/fluent-bit -c /tmp/fluent-bit.conf"]
[SERVICE]
    Flush         5
    Log_Level     info
    Parsers_File  /fluent-bit/parsers/parsers.conf

[INPUT]
    Name              tail
    Path              /var/log/tasks/*/*-json.log
    Tag_Regex         /var/log/tasks/[^/]+/(?<runtime_id>[^/]+)-json\.log$
    Tag               <runtime_id>
    Parser            docker
    Read_from_Head    On
    Refresh_Interval  5

[OUTPUT]
    Name              s3
    Match             *
    bucket            amzn-s3-demo-bucket
    region            ap-northeast-1
    s3_key_format     /logs/$TAG/%Y%m%d%H%M%S-$UUID.json
    use_put_object    On
    total_file_size   1M
    upload_timeout    20s

[OUTPUT]
    Name               cloudwatch_logs
    Match              *
    region             ap-northeast-1
    log_group_name     /jsonlog-demo/app
    log_stream_prefix  app-
    auto_create_group  On

Tag_Regex でファイル名から runtimeId を取り出して Tag にしています。この Tag が、S3 のキーでは $TAG の部分に、CloudWatch Logs のストリーム名では log_stream_prefix の後ろに入ります。Parser には、イメージに同梱の parsers.conf にある docker パーサーを指定し、json-file の1行を log、stream、time に分けています。auto_create_group を On にしたので、ロググループはプラグインが作成しました。

今回の設定では、tail の DB を指定していません。DB を指定すると再起動後に読み取り位置を引き継げるので、daemon を再起動する運用では DB の指定が必要になることがあります。

IAM の権限

daemon のタスクロールに次のポリシーを付けました。検証用なので、最小権限には絞っていません。

{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":["s3:PutObject"],"Resource":"arn:aws:s3:::amzn-s3-demo-bucket/*"},{"Effect":"Allow","Action":["logs:CreateLogGroup","logs:CreateLogStream","logs:PutLogEvents"],"Resource":"*"}]}

この権限で、ログは S3 と CloudWatch Logs の両方に届きました。

届いたログ

S3 のキーは logs/<runtimeId>/<YYYYmmddHHMMSS>-<UUID>.json です。nginx のタスクでは、起動時のログと停止時のログが、それぞれ別のオブジェクトになりました。

一覧の日時は JST、キーの日時は UTC です。

2026-11-13 02:58:09       3058 logs/2624f628xxxxxxxxxxxxxxxxxxxxxxxx-2531612879/20261112175744-Sq2rToP8.json
2026-11-13 03:01:03       1973 logs/2624f628xxxxxxxxxxxxxxxxxxxxxxxx-2531612879/20261112180039-Ah3pdjzR.json

次は1つ目のオブジェクトの先頭2行です。中身は1行1レコードの JSON で、log に nginx の1行がそのまま入っています。date は S3 の出力プラグインが追加するフィールドで、CloudWatch Logs のイベントにはありません。

{"date":"2026-11-12T17:57:35.880309Z","log":"/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration\n","stream":"stdout","time":"2026-11-12T17:57:35.880309457Z"}
{"date":"2026-11-12T17:57:35.880475Z","log":"/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/\n","stream":"stdout","time":"2026-11-12T17:57:35.880475542Z"}

CloudWatch Logs のログストリームは app-<runtimeId> です。各イベントの本文は、log、stream、time を持つ JSON です。イベントのタイムスタンプは、本文の time とミリ秒の桁まで一致しました。次は、別の nginx タスクのストリームから、先頭2イベントのタイムスタンプ(UNIX 時間のミリ秒)と本文を抜き出したものです。

1794506627468	{"log":"/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration\n","stream":"stdout","time":"2026-11-12T18:03:47.468205463Z"}
1794506627468	{"log":"/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/\n","stream":"stdout","time":"2026-11-12T18:03:47.468320715Z"}

S3 のキーも CloudWatch Logs のストリーム名も runtimeId ごとに分かれるので、ログはコンテナ単位で分類できます。

まとめ

ECS Managed Instances で json-file ログドライバーが提供されたことで、アプリケーションコンテナは、標準的な Docker の json-file 形式でログをローカルファイルとして書き出すだけで、外部へ転送できるようになりました。

書き出されたログは、daemon がボリュームマウントを通じて直接読み取れるので、bind mount パスの調整や root 作業なしに Fluent Bit などで扱えます。

FireLens でログを転送する場合は、タスクごとにログルーターのコンテナを置き、アプリ側のコンテナに awsfirelens の設定を書く必要があります。json-file と daemon の組み合わせでは、サイドカーを置かずに、json-file を指定した全コンテナのログをインスタンスごとの daemon 1つで集められます。ログ転送のためのミドルウェア設定やサイドカーの管理が課題だった場合は、今回のアップデートをお試しください。

この記事をシェアする

AWSのお困り事はクラスメソッドへ

関連記事