0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【検証】FireLensでログが消える現象を実際に再現してみた

0
Last updated at Posted at 2026-06-21

検証の狙い

具体的には以下を実証します。

  • 既定で ecs_* メタデータがレコードに付与されること(Fargateでは ec2_instance_id は付かないこと)。
  • メモリバッファ既定で高負荷をかけると paused (mem buf overlimit) が出てログが欠落すること。(実測で OOMKill は発生せずバックプレッシャーによる欠落と判明)
  • forward input を storage.type filesystem に切り替えると、検証2と同一負荷でも欠落が解消し到達行数が投入数に一致すること。
  • FireLens標準のCloudWatchメトリクスが存在せず、観測はログルーター自身のログと宛先メトリクスに依存すること。

思想・設計判断(例「要件が単純なら awslogs でよい」「FireLensの実体はサイドカー+設定自動生成」)は概念であり観測対象外のため、本検証からは除外しています。

検証マップ(記事の背骨)

詳細版の主張と検証シナリオを1対1で対応させたものです。各行の詳細は後述の「シナリオ別検証」に対応します。

# 検証する主張(詳細版由来) 再現方法 証拠(観測すべきもの)
1 既定で ecs_* メタデータ付与(Fargateは ec2_instance_id なし) 通常時に1行出し、CloudWatchイベントを確認 ecs_* キーあり/ec2_instance_id 無し
2 memory既定でメモリバッファ上限超過→input pause→欠落 大量ログを短時間に流す paused (mem buf overlimit)/流量>到達
3 storage.type filesystem 化で欠落が解消する 検証2と同一負荷を filesystem 構成で流す overlimit 出ず/到達 ≈ 投入数
4 FireLens標準のCloudWatchメトリクスは無い メトリクスのネームスペースを確認 FireLens専用NS不在/代替観測のみ

検証環境

Terraform + Docker 構成を使います。完全な一式は以下のGitHubリポジトリより terraform/ および docker/ を参照してください。以下は記事用の HCL 抜粋です。

GitHub リポジトリリンク:
aws-ecs-firelens-verify

構成要素

architecture.png

  • ECSクラスタ(Fargate)
  • タスク定義(2コンテナ、log_router を app より先に定義する)
    • log_router(ECR カスタムイメージ。ベース: public.ecr.aws/aws-observability/aws-for-fluent-bit:3、S3 OUTPUT を追加した extra.conf を同梱。firelensConfiguration.type: fluentbit、essential: true)
    • app(httpd:2.4、logConfiguration.logDriver: awsfirelens。大量ログを吐けるもの)
    • log_router 自身にも awslogs を設定し、Fluent Bit のログを CloudWatch に出す(観測の前提)
  • ECR リポジトリ(カスタム log_router イメージ置き場。memory 構成は :latest、filesystem 構成は :fs タグ)
  • 宛先: CloudWatch Logs ロググループ × 2(app / log-router)+ 検証用 S3 バケット(複数宛先分岐の確認用)
  • IAM: タスクロール=宛先書込権限(CloudWatch Logs / S3)/タスク実行ロール=ECR プル・log_router の awslogs 用権限
  • セキュリティグループ: ポート 24224 の ingress を開けない

検証モードの切り替え: 検証1・2・4(memory バッファ)と検証3(filesystem バッファ)では log_router の構成が異なります。terraform/terraform.tfvars の enable_filesystem_buffer トグル1行で、memory 構成(:latest + extra.conf を @INCLUDE)と filesystem 構成(:fs + フル設定を CMD で読み込み)を切り替えます。コード本体は編集しません。

Terraform 概要

Terraform 概要(HCL 抜粋・クリックで展開)
# --- ECS クラスタ(Fargate) ---
resource "aws_ecs_cluster" "verify" {
  name = var.project_name
}

# --- ロググループ(宛先 + log_router 自身のログ) ---
resource "aws_cloudwatch_log_group" "app" {
  name              = "/aws/ecs/${var.project_name}/app"
  retention_in_days = 1 # 検証用に短く(コスト抑制)
}

resource "aws_cloudwatch_log_group" "log_router" {
  name              = "/aws/ecs/${var.project_name}/log-router"
  retention_in_days = 1
}

# --- 検証用 S3(複数宛先分岐の確認) ---
resource "aws_s3_bucket" "verify_logs" {
  bucket        = "${var.project_name}-logs-${var.s3_bucket_suffix}"
  force_destroy = true # teardown で中身ごと消す
}

# --- ECR リポジトリ(カスタム log_router イメージ置き場) ---
resource "aws_ecr_repository" "log_router" {
  name         = "${var.project_name}-log-router"
  force_delete = true
}

# --- タスク定義(log_router + app の2コンテナ) ---
# log_router を app より先に定義することで FireLens サイドカーを先行起動させる。
resource "aws_ecs_task_definition" "verify" {
  family                   = var.project_name
  requires_compatibilities = ["FARGATE"]
  network_mode             = "awsvpc"
  cpu                      = var.task_cpu
  memory                   = var.task_memory
  task_role_arn            = aws_iam_role.task.arn
  execution_role_arn       = aws_iam_role.execution.arn

  container_definitions = jsonencode([
    {
      name      = "log_router"
      # S3 OUTPUT を追加した extra.conf を同梱するためカスタムイメージを使用。
      # ベース: public.ecr.aws/aws-observability/aws-for-fluent-bit:3
      image     = "${aws_ecr_repository.log_router.repository_url}:latest"
      memory    = 200 # log_router コンテナのハードリミット (MiB)
      essential = true
      firelensConfiguration = {
        type = "fluentbit"
        options = {
          # Fargate は config-file-type = "s3" 非サポートのため file 方式でイメージに同梱する。
          "config-file-type"  = "file"
          "config-file-value" = "/fluent-bit/configs/extra.conf"
        }
      }
      logConfiguration = {
        logDriver = "awslogs"
        options = {
          "awslogs-group"         = aws_cloudwatch_log_group.log_router.name
          "awslogs-region"        = var.aws_region
          "awslogs-stream-prefix" = "fb"
        }
      }
      environment = [
        { name = "AWS_REGION",     value = var.aws_region },
        { name = "S3_BUCKET_NAME", value = aws_s3_bucket.verify_logs.id },
      ]
    },
    {
      name      = "app"
      image     = "httpd:2.4"
      essential = true
      logConfiguration = {
        logDriver = "awsfirelens"
        options = {
          Name              = "cloudwatch_logs"
          region            = var.aws_region
          log_group_name    = aws_cloudwatch_log_group.app.name
          auto_create_group = "false"
          log_stream_prefix = "app-"
        }
      }
    }
  ])
}

# --- IAM(タスクロール:宛先書込 / 実行ロール:プル・awslogs) ---
# task role: logs:PutLogEvents, logs:CreateLogStream on app log group / s3:PutObject on verify bucket
# execution role: AmazonECSTaskExecutionRolePolicy + logs:PutLogEvents on log_router log group

# --- セキュリティグループ:24224 の ingress は定義しない(開けない) ---
resource "aws_security_group" "task" {
  name        = "${var.project_name}-task"
  description = "No port 24224 ingress by design."
  vpc_id      = var.vpc_id
  # ingress for 24224 は意図的に作らない(詳細版のセキュリティ注意を踏襲)
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

カスタムイメージのビルド・push 手順は docker/Dockerfile および docker/extra.conf を参照してください。

コスト注意

  • Fargate タスク(vCPU/メモリ)・CloudWatch Logs 取り込み・S3 PUT/保管に課金されます。検証は短時間で完了させ、放置しないこと。
  • ロググループの retention_in_days を短く(例: 1日)して保管コストを抑えています。
  • 高負荷再現で大量ログを流すため、CloudWatch Logs 取り込み量が増えます。流す量は再現に必要な最小限にしてください。

Teardown 手順(検証後に確実に消す)

# 1. 実行中タスクがないことを確認
aws ecs list-tasks --cluster <cluster_name> --region <region>

# 2. Terraform で一式破棄(S3 は force_destroy で中身ごと削除)
aws-vault exec <profile> -- terraform destroy

# 3. 残存していないか確認(ロググループ・S3 バケット・タスク定義)
aws logs describe-log-groups --log-group-name-prefix /aws/ecs/<project_name>
aws s3 ls | grep <project_name>

セキュリティ注意(詳細版を踏襲)

  • ポート 24224 の ingress は開けない。 forward input はタスク内のサイドカー間通信であり、外部公開は不要かつ危険です。
  • 検証用に作った S3 バケットはパブリックアクセスをブロックしたまま使うこと。

シナリオ別検証

各検証は 仮説 → 手順 → 証拠 → 実測結果 の順で統一しています。


検証1 | 既定で ecs_* メタデータが付与される

Fargate では ec2_instance_id が付かないことも併せて確認する。

仮説(詳細版) — 既定で各レコードに ecs_cluster / ecs_task_arn / ecs_task_definition が付与される。ec2_instance_id は EC2 起動タイプのみで、Fargate では付与されない。enable-ecs-log-metadata: false で無効化できる。

再現手順

  1. 上記 Fargate 構成でタスクを起動する。

  2. app コンテナの stdout に 1 行ログを出す。

    aws ecs run-task \
      --cluster <cluster_name> \
      --task-definition <task_definition_arn> \
      --launch-type FARGATE \
      --region <region> \
      --network-configuration "awsvpcConfiguration={subnets=[<SUBNET_ID>],securityGroups=[<SG_ID>],assignPublicIp=ENABLED}" \
      --overrides '{"containerOverrides":[{"name":"app","command":["sh","-c","echo hello-firelens-verify && sleep 30"]}]}'
    
  3. 宛先の CloudWatch Logs で該当ログイベントの JSON を確認する。

    aws logs tail <app_log_group_name> --follow --region <region>
    

観測すべき証拠

  • イベント JSON に ecs_cluster / ecs_task_arn / ecs_task_definition が含まれる
  • ec2_instance_id が含まれない(Fargate のため)

実測結果 —

期待値通りのログ出力結果であることを確認。

verify-1.png


検証2 | mem buf overlimit でログが欠落する

仮説 — input は既定 storage.type memory。メモリバッファが上限に達すると input が pause され新規レコードは失われる。Fluent Bit ログに [input] forward.1 paused (mem buf overlimit) が出る。

欠落点は直列2段: ログは app の stdout →Docker ログドライバ(awsfirelens)のバッファ→**Fluent Bit forward input(既定メモリバッファ)**→ CloudWatch と流れます。
① FB のメモリバッファ上限で input が pause され、
② pause 中に Docker ドライバのバッファ(log-driver-buffer-limit、既定 1,048,576 行)が溢れると古いメッセージが破棄されます。

いずれもノンブロッキングな破棄で OOMKill は起こしません。再現には terraform.tfvars の app_log_driver_buffer_limit(実測では 8192)を設定し、FB のメモリバッファ容量より大きく・かつ投入量が両バッファ合計を十分超えるようにします。

再現手順

  1. terraform.tfvars で enable_filesystem_buffer = false(既定)・app_log_driver_buffer_limit = 8192 を設定して apply 済みとする。

  2. memory バッファ構成のまま、app から大量ログを短時間に流し続ける。

    N=100000
    
    aws-vault exec <profile> -- aws ecs run-task \
      --cluster "$CLUSTER" \
      --task-definition "$TASKDEF" \
      --launch-type FARGATE \
      --region "$REGION" \
      --network-configuration "awsvpcConfiguration={subnets=[$SUBNET],securityGroups=[$SG],assignPublicIp=ENABLED}" \
      --overrides '{"containerOverrides":[{"name":"app","command":["sh","-c","P=$(printf %10000s | tr \" \" X); for i in $(seq 1 '"$N"'); do echo \"$P\"; done"]}]}' \
      --query "tasks[0].taskArn" --output text
    
  3. log_router のログ(CloudWatch)で paused (mem buf overlimit) を観察する。

    aws logs tail <log_router_log_group_name> --follow --region <region> | grep -i "paused\|overlimit"
    
  4. タスク完了後、CloudWatch に到達した行数を数え、流した行数との差分(欠落)を確認する。

観測すべき証拠

  • log_router ログに [input] forward.1 paused (mem buf overlimit) が出る
  • 流した行数 > 宛先到達行数(欠落発生)

実測結果 —

流した100,000件に対して、到達件数が90,550であることから、仮説通り欠損があることを確認。

verify-2-1.png
verify-2-2.png


検証3 | storage.type filesystem 化でログ欠落が解消する

仮説 — forward input を storage.type filesystem に切り替えると、検証2と同じ負荷でも mem buf overlimit による pause/ドロップが起きず、到達行数が投入数 N に近づく。

前提: filesystem バッファは input 定義に storage.type filesystem を書く必要がありますが、FireLens が自動生成する forward input には後付けできません(公式: カスタム設定で forward input を定義してはいけない)。そのため検証3だけは extra.conf を @INCLUDE する方式ではなく、CMD を上書きして自前のフル設定を読ませ、その中で forward input を filesystem 化して再定義します(:fs イメージ)。

再現手順

  1. :fs イメージ(fluent-bit-fs.conf を CMD で読む構成)をビルドして ECR に push する。

  2. terraform.tfvars の enable_filesystem_buffer = true にして apply し、TASKDEF を再度取り直す。

  3. 検証2と同一の N・同一の負荷を流す(before = 検証2 / after = 検証3)。

    N=100000  # 検証2と必ず一致させる
    
    aws-vault exec <profile> -- aws ecs run-task \
      --cluster "$CLUSTER" \
      --task-definition "$TASKDEF" \
      --launch-type FARGATE \
      --region "$REGION" \
      --network-configuration "awsvpcConfiguration={subnets=[$SUBNET],securityGroups=[$SG],assignPublicIp=ENABLED}" \
      --overrides '{"containerOverrides":[{"name":"app","command":["sh","-c","P=$(printf %10000s | tr \" \" X); for i in $(seq 1 '"$N"'); do echo \"$P\"; done"]}]}' \
      --query "tasks[0].taskArn" --output text
    
  4. log_router に overlimit が出ないことを確認し、タスク完了(flush 完了)後に IncomingLogEvents の Sum で到達総数を数え、検証2と比較する。

観測すべき証拠

  • log_router ログに mem buf overlimit / paused が出ない
  • 到達行数(IncomingLogEvents の Sum)≈ N(検証2より明確に増える)

実測結果 —

同一負荷 N=100,000 行に対し、検証2(memory)は到達 90,550 行・約 9,450 行(≒9.5%)欠落だったのに対し、検証3(filesystem)では overlimit が一切発生せず到達行数が N と完全一致(100,000 行)した。

確認項目 検証2(memory・before) 検証3(filesystem・after)
mem buf overlimit 発生(pause/resume) 発生無し
到達行数 90,550 100,000

verify-3-1.png
verify-3-2.png


検証4 | FireLens 標準の CloudWatch メトリクスは無い

仮説(詳細版) — FireLens / Fluent Bit 自体の CloudWatch メトリクスは AWS 標準では提供されない。観測はログルーター自身のログと宛先サービス側メトリクスで行う。

再現手順

  1. CloudWatch メトリクスのネームスペース一覧を確認し、FireLens 専用のものが無いことを確認する。

    aws cloudwatch list-metrics --namespace "FireLens" --region <region>
    aws cloudwatch list-metrics --namespace "ECS/FireLens" --region <region>
    
  2. 観測手段として、log_router 自身のログ(awslogs)と宛先メトリクス(CloudWatch Logs の IncomingLogEvents 等)が使えることを確認する。

    aws cloudwatch list-metrics \
      --namespace "AWS/Logs" \
      --metric-name "IncomingLogEvents" \
      --dimensions "Name=LogGroupName,Value=<app_log_group_name>" \
      --region <region>
    

観測すべき証拠

  • FireLens / ECS/FireLens ネームスペースが空(メトリクスなし)
  • 観測が log_router 自身のログであることの確認

実測結果 —

verify-4-1.png
verify-4-2.png
verify-4-3.png

上記は FireLens / ECS/FireLens 両ネームスペースともに空でした。

確認項目 実測
FireLens ネームスペース {"Metrics": []}(メトリクスなし)
ECS/FireLens ネームスペース {"Metrics": []}(メトリクスなし)
AWS/Logs IncomingLogEvents 取得可能(宛先側メトリクスとして利用)

まとめ

検証 主張 裏付け
1 ecs_* メタデータ付与 / Fargate で ec2_instance_id なし ✅ 実測で確認
2 mem buf overlimit でログ欠落(OOMKill は発生せずバックプレッシャーで抑制) ✅ 実測で確認(仮説修正済み)
3 storage.type filesystem 化で欠落解消 ✅ 実測で確認
4 FireLens 標準メトリクス無し(観測は log_router 自身のログ) ✅ 実測で確認
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?