AWS FargateでFireLensを使って同じログを3箇所に送ってみた

カスタムログルーティングにおいてはFireLens力よりもfluentbit力が大切だった。
2021.02.15

AWS Fargateのログ管理でFireLens(fluentbit)を利用して複数のログ保存先を設定してみる。

  • アプリケーションが標準出力に出力したログをCloudWatch Logsと、S3バケットに保存してみます。
  • Firehose経由せずS3バケットへ直接送るfluentbitのプラグインもあったのでついでに試しました。
  • fluentbitの設定ファイルはfluentbitのDockerイメージに同梱しました。

Icons made by Freepik from www.flaticon.com

検証環境

項目 バージョン
aws-for-fluent-bit 2.10.1
fluentbit 1.6.10
Fargate platform 1.4.0

fluentbitとfluentd

Firelensで起動するログルーティングのコンテナは同じタスク内にサイドカーとして起動します。 軽量なコンテナの方が嬉しいのでfluentbitを採用しました。

参考: Fluent Bit による集中コンテナロギング | Amazon Web Services ブログ

設定ファイル込みのイメージ作成

同じログ内容を合計3箇所の出力先へ送るシンプルなfluentbitの設定を作るところからはじめます。fluentbitの設定次第で特定のログであればCloudWatch Logsへ、それ以外はS3バケットへ送信も可能です。fluentbitのドキュメントまで読みきれなかったので細かい設定は断念。

fluentbitの設定ファイル作成

各プラグインの設定はfluentbitドキュメントを参考に出力先を設定しました。

S3へ直接する保存する場合はドキュメント内で注意事項として触れられています。Fargateのような永続ストレージがない実行環境でfluentbitを起動する場合、fluetbitのコンテナが停止するとログを消失する可能性があります。高頻度でログを送信するのを推奨されています。とあるため、従来どおりKinesis Data Firehoseを経由でS3へ送信する方が安全な構成と言えるでしょう。ただ、S3用のプラグインを試したいので設定します。

extra.conf

[OUTPUT]
    Name   cloudwatch
    Match  *
    region us-east-1
    log_group_name /ecs/logs/fluentbit-dev-ecs-group
    log_stream_name from-fluentbit
    auto_create_group true

[OUTPUT]
    Name   firehose
    Match  *
    region us-east-1
    delivery_stream fluentbit-dev-delivery-stream

[OUTPUT]
    Name s3
    Match *
    region us-east-1
    bucket fluentbit-dev-directs3
    total_file_size 1M
    upload_timeout 1m

参考:

DockerfileとECRへイメージアップロード

ECS EC2の場合、S3にfluentbitの設定ファイルを置いておけば設定ファイルを読み込んでくれる機能がサポートされています。しかし、Fargateはその機能がサポートされていません。そのため、fluentbitのコンテナ内に設定ファイルを入れておきます。

Icons made by Freepik from www.flaticon.com

AWS Fargate でホストされるタスクは、file 設定ファイルタイプのみをサポートします。

FireLens 設定を使用するタスク定義の作成 - Amazon ECS

とは言え、fluentbit設定のファイルをイメージ内にコピーするだけです。fluentbitコンテナの設定ファイル読み込み指定はECSのタスク定義の設定で行います。ベースのイメージはAmazonが配布しているAWS用のプラグインが同梱されたfluentbitのイメージを利用しています。

Dockerfile

FROM amazon/aws-for-fluent-bit:2.10.1
COPY ./extra.conf /fluent-bit/etc/extra.conf

設定ファイル込みのイメージをECRへプッシュします。FireLensで起動するサイドカーコンテナの準備完了です。

docker build -t fluentbit-dev-my-firelens .
docker tag fluentbit-dev-my-firelens:latest [AccountID].dkr.ecr.us-east-1.amazonaws.com/fluentbit-dev-my-firelens:latest
aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin [AccountID].dkr.ecr.us-east-1.amazonaws.com
docker push [AccountID].dkr.ecr.us-east-1.amazonaws.com/fluentbit-dev-my-firelens:latest

参考:

FireLens有効化とタスク定義の編集

Fargateの構築については省略します。

FireLens有効

FireLensの統合を有効にチェックを入れます。イメージはECRにアップした設定ファイル込みのfluentbitのイメージを指定します。

有効化するとlog_routerコンテナが追加されます。

タスク定義の編集

必要なログ設定箇所がドキュメントななめ読みだと頭に入ってこなかったのでまとめると私の理解ではこうなりました。

Icons made by Freepik from www.flaticon.com
  • アプリコンテナ(キャプチャの例だとfluentbit-dev-container)のログドライバーはawsfirelensを指定
  • FireLensのfluentbitコンテナ(log_router)のログドライバーはawslogsを指定
    • fluentbitのトラブル対応のためCloudWatch Logs(awslogs)にログを送ります
    • logConfigurationと似ているfirelensConfigurationの項目に設定を追加します

アプリコンテナ設定

awslogsからawsfirelensに変更します。

変更後の該当箇所

            "logConfiguration": {
                "logDriver": "awsfirelens",
                "secretOptions": null,
                "options": null
            },

FireLnesコンテナ設定

awslogsを指定して、CloudWatch Logsのロググループなど設定を追加します。

変更後の該当箇所

            "logConfiguration": {
                "logDriver": "awslogs",
                "secretOptions": null,
                "options": {
                    "awslogs-group": "/ecs/logs/fluentbit-dev-ecs-group",
                    "awslogs-region": "us-east-1",
                    "awslogs-stream-prefix": "from-log-router"
                }
            },

fluentbitの設定ファイルの指定はタスク定義のJSONファイルを直接編集します。

firelensConfigurationの項目を探します。

            "firelensConfiguration": {
                "type": "fluentbit"
            },

変更後の該当箇所

  • "type": "fluentbit"末尾の,の付け忘れに注意
  • optionsでイメージに格納した自前の設定ファイルのパスを指定
            "firelensConfiguration": {
                "type": "fluentbit",
                "options": {
                    "config-file-type": "file",
                    "config-file-value": "/fluent-bit/etc/extra.conf"
                }
            },

検証時のタスク定義全文

設定箇所の確認にお使いください。

{
  "ipcMode": null,
  "executionRoleArn": "arn:aws:iam::[AccountID]:role/fluentbit-dev-ECSTaskExecutionRolePolicy",
  "containerDefinitions": [
    {
      "dnsSearchDomains": [],
      "environmentFiles": null,
      "logConfiguration": {
        "logDriver": "awsfirelens",
        "secretOptions": null,
        "options": null
      },
      "entryPoint": [],
      "portMappings": [
        {
          "hostPort": 80,
          "protocol": "tcp",
          "containerPort": 80
        }
      ],
      "command": [],
      "linuxParameters": null,
      "cpu": 0,
      "environment": [],
      "resourceRequirements": null,
      "ulimits": null,
      "dnsServers": [],
      "mountPoints": [],
      "workingDirectory": null,
      "secrets": null,
      "dockerSecurityOptions": [],
      "memory": null,
      "memoryReservation": null,
      "volumesFrom": [],
      "stopTimeout": null,
      "image": "[AccountID].dkr.ecr.us-east-1.amazonaws.com/fluentbit-dev-sample-server:latest",
      "startTimeout": null,
      "firelensConfiguration": null,
      "dependsOn": null,
      "disableNetworking": null,
      "interactive": null,
      "healthCheck": null,
      "essential": true,
      "links": [],
      "hostname": null,
      "extraHosts": null,
      "pseudoTerminal": null,
      "user": null,
      "readonlyRootFilesystem": null,
      "dockerLabels": null,
      "systemControls": [],
      "privileged": null,
      "name": "fluentbit-dev-container"
    },
    {
      "dnsSearchDomains": null,
      "environmentFiles": null,
      "logConfiguration": {
        "logDriver": "awslogs",
        "secretOptions": null,
        "options": {
          "awslogs-group": "/ecs/logs/fluentbit-dev-ecs-group",
          "awslogs-region": "us-east-1",
          "awslogs-stream-prefix": "from-log-router"
        }
      },
      "entryPoint": null,
      "portMappings": [],
      "command": null,
      "linuxParameters": null,
      "cpu": 0,
      "environment": [],
      "resourceRequirements": null,
      "ulimits": null,
      "dnsServers": null,
      "mountPoints": [],
      "workingDirectory": null,
      "secrets": null,
      "dockerSecurityOptions": null,
      "memory": null,
      "memoryReservation": null,
      "volumesFrom": [],
      "stopTimeout": null,
      "image": "[AccountID].dkr.ecr.us-east-1.amazonaws.com/fluentbit-dev-my-firelens:latest",
      "startTimeout": null,
      "firelensConfiguration": {
        "type": "fluentbit",
        "options": {
          "config-file-type": "file",
          "config-file-value": "/fluent-bit/etc/extra.conf"
        }
      },
      "dependsOn": null,
      "disableNetworking": null,
      "interactive": null,
      "healthCheck": null,
      "essential": true,
      "links": null,
      "hostname": null,
      "extraHosts": null,
      "pseudoTerminal": null,
      "user": "0",
      "readonlyRootFilesystem": null,
      "dockerLabels": null,
      "systemControls": null,
      "privileged": null,
      "name": "log_router"
    }
  ],
  "placementConstraints": [],
  "memory": "512",
  "taskRoleArn": "arn:aws:iam::[AccountID]:role/fluentbit-dev-ECSTaskExecutionRolePolicy",
  "compatibilities": [
    "EC2",
    "FARGATE"
  ],
  "taskDefinitionArn": "arn:aws:ecs:us-east-1:[AccountID]:task-definition/fluentbit-dev-task:19",
  "family": "fluentbit-dev-task",
  "requiresAttributes": [
    {
      "targetId": null,
      "targetType": null,
      "value": null,
      "name": "com.amazonaws.ecs.capability.logging-driver.awslogs"
    },
    {
      "targetId": null,
      "targetType": null,
      "value": null,
      "name": "ecs.capability.execution-role-awslogs"
    },
    {
      "targetId": null,
      "targetType": null,
      "value": null,
      "name": "com.amazonaws.ecs.capability.ecr-auth"
    },
    {
      "targetId": null,
      "targetType": null,
      "value": null,
      "name": "com.amazonaws.ecs.capability.docker-remote-api.1.19"
    },
    {
      "targetId": null,
      "targetType": null,
      "value": null,
      "name": "ecs.capability.firelens.fluentbit"
    },
    {
      "targetId": null,
      "targetType": null,
      "value": null,
      "name": "ecs.capability.firelens.options.config.file"
    },
    {
      "targetId": null,
      "targetType": null,
      "value": null,
      "name": "com.amazonaws.ecs.capability.docker-remote-api.1.17"
    },
    {
      "targetId": null,
      "targetType": null,
      "value": null,
      "name": "com.amazonaws.ecs.capability.logging-driver.awsfirelens"
    },
    {
      "targetId": null,
      "targetType": null,
      "value": null,
      "name": "com.amazonaws.ecs.capability.task-iam-role"
    },
    {
      "targetId": null,
      "targetType": null,
      "value": null,
      "name": "ecs.capability.execution-role-ecr-pull"
    },
    {
      "targetId": null,
      "targetType": null,
      "value": null,
      "name": "com.amazonaws.ecs.capability.docker-remote-api.1.18"
    },
    {
      "targetId": null,
      "targetType": null,
      "value": null,
      "name": "ecs.capability.task-eni"
    }
  ],
  "pidMode": null,
  "requiresCompatibilities": [
    "FARGATE"
  ],
  "networkMode": "awsvpc",
  "cpu": "256",
  "revision": 19,
  "status": "ACTIVE",
  "inferenceAccelerators": null,
  "proxyConfiguration": null,
  "volumes": []
}

タスクロールの追加

タスク実行ロールではなくタスクロールにログ送信先リソースへアクセスできるポリシーを追加します。

手抜きでフルアクセスのポリシーを追加しました。

FireLens サイドカー起動

サービス更新してタスク起動し直します。 log_routerコンテナも起動してきました。

ログ確認

FireLensからCloudWatch Logsへ

fluentbitコンテナ自身はawslogs指定でCloudWatch Logsへ保存設定しました。S3へアップロードしましたという大量のログを確認できました。高頻度で送信するようにしたためほぼこのログで埋まっています。fluentbitコンテナのトラブル調査はCloudwatch Logsからできます。

[2021/02/14 11:12:52] [ info] [output:s3:s3.2] Successfully uploaded object /fluent-bit-logs/fluentbit-dev-container-firelens-af27785192944632a76e3d1696c42686/2021/02/14/11/11/47-objectNj3AEbw5
[2021/02/14 11:14:22] [ info] [output:s3:s3.2] Successfully uploaded object /fluent-bit-logs/fluentbit-dev-container-firelens-af27785192944632a76e3d1696c42686/2021/02/14/11/13/17-objectyjr40CIG

アプリから各種保存先へ

アプリコンテナはELBの後ろにある文字列を返すだけのWEBサーバです。

$ curl http://fluentbit-dev-elb-586101502.us-east-1.elb.amazonaws.com/
"Hello, Abashiri"

まず、CloudWatch LogsにはELBのヘルスチェックのリクエストログを確認できました。fluentbitからCloudWatch Logsへのルーティングは成功していました。

{
    "container_id": "af27785192944632a76e3d1696c42686-3097909514",
    "container_name": "fluentbit-dev-container",
    "ecs_cluster": "fluentbit-dev-cluster",
    "ecs_task_arn": "arn:aws:ecs:us-east-1:[AccountID]:task/fluentbit-dev-cluster/af27785192944632a76e3d1696c42686",
    "ecs_task_definition": "fluentbit-dev-task:19",
    "log": "{\"time\":\"2021-02-14T11:18:44.214087349Z\",\"id\":\"\",\"remote_ip\":\"10.1.2.37\",\"host\":\"10.1.17.31\",\"method\":\"GET\",\"uri\":\"/\",\"user_agent\":\"ELB-HealthChecker/2.0\",\"status\":200,\"error\":\"\",\"latency\":40220,\"latency_human\":\"40.22µs\",\"bytes_in\":0,\"bytes_out\":18}",
    "source": "stdout"
}

次にKinesis Data Firehose経由のS3バケット内の様子です。ダウンロードして確認すると同様にELBのヘルスチェックログを確認できました。

{"container_id":"af27785192944632a76e3d1696c42686-3097909514","container_name":"fluentbit-dev-container","ecs_cluster":"fluentbit-dev-cluster","ecs_task_arn":"arn:aws:ecs:us-east-1:[AccountID]:task/fluentbit-dev-cluster/af27785192944632a76e3d1696c42686","ecs_task_definition":"fluentbit-dev-task:19","log":"{\"time\":\"2021-02-14T11:30:14.682585288Z\",\"id\":\"\",\"remote_ip\":\"10.1.2.37\",\"host\":\"10.1.17.31\",\"method\":\"GET\",\"uri\":\"/\",\"user_agent\":\"ELB-HealthChecker/2.0\",\"status\":200,\"error\":\"\",\"latency\":51727,\"latency_human\":\"51.727µs\",\"bytes_in\":0,\"bytes_out\":18}","source":"stdout"}
{"container_id":"af27785192944632a76e3d1696c42686-3097909514","container_name":"fluentbit-dev-container","ecs_cluster":"fluentbit-dev-cluster","ecs_task_arn":"arn:aws:ecs:us-east-1:[AccountID]:task/fluentbit-dev-cluster/af27785192944632a76e3d1696c42686","ecs_task_definition":"fluentbit-dev-task:19","log":"{\"time\":\"2021-02-14T11:30:44.408751519Z\",\"id\":\"\",\"remote_ip\":\"10.1.1.178\",\"host\":\"10.1.17.31\",\"method\":\"GET\",\"uri\":\"/\",\"user_agent\":\"ELB-HealthChecker/2.0\",\"status\":200,\"error\":\"\",\"latency\":37581,\"latency_human\":\"37.581µs\",\"bytes_in\":0,\"bytes_out\":18}","source":"stdout"}

最後は直接S3バケットにログを送った様子です。オブジェクトまでのパスが深いのと、ログフォーマットが今まで多少異なっていました。

{"date":"2021-02-14T11:15:43.721428Z","container_name":"fluentbit-dev-container","source":"stdout","log":"{\"time\":\"2021-02-14T11:15:43.72122498Z\",\"id\":\"\",\"remote_ip\":\"10.1.1.178\",\"host\":\"10.1.17.31\",\"method\":\"GET\",\"uri\":\"/\",\"user_agent\":\"ELB-HealthChecker/2.0\",\"status\":200,\"error\":\"\",\"latency\":39110,\"latency_human\":\"39.11\u00b5s\",\"bytes_in\":0,\"bytes_out\":18}","container_id":"af27785192944632a76e3d1696c42686-3097909514","ecs_cluster":"fluentbit-dev-cluster","ecs_task_arn":"arn:aws:ecs:us-east-1:[AccountID]:task/fluentbit-dev-cluster/af27785192944632a76e3d1696c42686","ecs_task_definition":"fluentbit-dev-task:19"}
{"date":"2021-02-14T11:15:44.109974Z","source":"stdout","log":"{\"time\":\"2021-02-14T11:15:44.109820687Z\",\"id\":\"\",\"remote_ip\":\"10.1.2.37\",\"host\":\"10.1.17.31\",\"method\":\"GET\",\"uri\":\"/\",\"user_agent\":\"ELB-HealthChecker/2.0\",\"status\":200,\"error\":\"\",\"latency\":39215,\"latency_human\":\"39.215\u00b5s\",\"bytes_in\":0,\"bytes_out\":18}","container_id":"af27785192944632a76e3d1696c42686-3097909514","container_name":"fluentbit-dev-container","ecs_cluster":"fluentbit-dev-cluster","ecs_task_arn":"arn:aws:ecs:us-east-1:[AccountID]:task/fluentbit-dev-cluster/af27785192944632a76e3d1696c42686","ecs_task_definition":"fluentbit-dev-task:19"}

おわりに

FireLensはfluentbitまたはfluentdを起動するのに便利な機能であって、ログのルーティングはfluentbit、fluentdの設定次第ということがわかりました。設定ファイルはS3に置いておけるようになると自前イメージの管理の手間減って助かりますね。

参考