FireLens fluent bit でログを振り分けたい場合、 fluent bit の設定ファイル内で Parsers_File
などで指定した別のファイルを用いて、カスタム docker イメージを作成するサンプルが多いかと思いますが、カスタムイメージを作成することなく( Parsers_File
無しで)ささやかながら実現した例を紹介したいと思います。
前置き
コンテナのログを簡単に取り扱うためのツールをとして AWS は FireLens を発表しました (AWS Blog) 。それまではログドライバに awslogs
を指定することで、コンテナの標準出力に書き出せば CloudWatch Logs にログが出力されるようになっていましたが、一方コンテナから出力されるログは、アクセスログやアプリケーションログ、エラーログといった複数の種類があり、種類に応じて出力先を振り分けたいといったニーズもあったのではないかと思います(そのために CloudWatch Logs にサブスクリプションフィルタを設定して Kinesis Firehose に送ったり、、、とかしていたかと思います)。
FireLens の登場により、共に動作する Fluent Bit や Fluentd を利用することでログの振り分けが簡単に実装できるようになり、実際、 AWS Blog などでも紹介されています。しかし前述の通り、多くの実装例では fluent bit の設定ファイルに Parsers_File
や Streams_File
で指定したさらに別のファイルを用意し、さらにそれらの設定ファイルを同梱した fluent bit のカスタムイメージを作成する必要があります。fluent bit の設定ファイルは s3 から読み込むことができますが、残念ながら Parsers_File
や Streams_File
はローカルから読み込む必要があり、そのためカスタムイメージを作成する必要が出てきます。
カスタムイメージをメンテしていくめんどくささも考慮すると、極力デフォルトのイメージのままでコンテナログを取り扱いたいものです。そこで本記事では rewrite_tag
を使ったログの振り分け方法を紹介したいと思います。あまり難しいことをしない、シンプルなログ振り分けであればこちらの方が簡単ではないかと思います。
アーキテクチャ
今回は以下のような構成で試してみました(バージニア北部リージョンを使用しています)。
Amazon ECS 上に Nginx コンテナと Fluent Bit コンテナでタスクを構成し、Nginx のログの内容に応じて、CloudWatch Logs、Kinesis Firehose への振り分けと、ちょうどつい先日、Fluent Bit から Amazon Elasticsearch Service へのログルーティングがサポート しましたので、Elasticsearch への振り分けも設定してみたいと思います。
訳あって EC2 ベースですが、Fargate でもだいたい同じようにできるのではないかと思います。
ログ振り分けのルール
(意味があるのかどうかはさておき)以下のように振り分けてみたいと思います。
- Nginx のログに error や 4xx、5xx 系の出力があれば CloudWatch Logs へ
- ALB からのヘルスチェックによるアクセスログは CloudWatch Logs にも Elasticsearch にも出力されないように
- エラーでもヘルスチェックでもないログ(正常なリクエスト)であれば Elasticsearch に
- 全てのログは Firehose(から S3)に
- (※ Fluent Bit コンテナ自身のログは CloudWatch Logs に出力されます)
設定方法など
タスク定義やタスクロール、タスク実行ロール、そして Fluent Bit の設定について以下に記載していきます。Firehose や Elasticsearch などの設定、デプロイメントについては触れていない点ご了承ください。
タスク定義
まずタスク定義です。
|
|
ハイライトしたところを補足しますと、
- 32行目 : nginx コンテナのログドライバを
awsfirelens
に設定しています。 - 34-39行目 : 訳あってネットワークモードが
bridge
であるために、コンテナの起動順序の依存関係を設定しています(参考)。また、依存関係設定のためコンテナのヘルスチェック設定が必要になりますが、今回は簡単にecho 'Hello'
を設定しています。本番での利用の際には適切なヘルスチェックを設定しましょう。 - 53行目 : fluent bit のコンテナである log_router には ログドライバとして
awslogs
に設定しています。 - 64-65行目 : fluent bit の設定ファイルを s3 上から読み取るようにしています。
このタスク定義を以下のようなコマンドで登録します。
aws ecs register-task-definition --cli-input-json file://./taskdef.json
IAM ロール
続いて必要な IAM Role について。タスクロールとタスク実行ロールを紹介します。
タスクロールのポリシー
タスク定義の 3 行目( arn:aws:iam::xxxxxxxxxxxx:role/taskRole_with_FireLens
)で指定しているロールのポリシーです。
( Resource
の対象ロググループ名、ストリーム名は環境に合わせて変更してください。)
|
|
タスク実行ロールのポリシー
タスク定義の 4 行目( arn:aws:iam::xxxxxxxxxxxx:role/ecsTaskExecutionRole_with_FireLens
)で指定しているロールのポリシーです。
簡単のため、タスク実行ロールにはあらかじめ以下の管理ポリシーを付与しておきます。
- CloudWatchAgentServerPolicy
- AmazonECSTaskExecutionRolePolicy
上記の管理ポリシーに加えて以下のポリシーを付与します。
|
|
- 13 行目 : Fluent Bit の設定ファイルを読み取るための権限をここで付与しています。タスク定義の 63行目と同じファイルが相当します。
Fluent Bit の設定
本題となる Fluent Bit の設定ファイルです。
|
|
CloudWatch Logs への Output
について、52行目で log_key
を指定しています。log_key
を指定すると JSON の中の該当するキー項目のみを出力します。
その JSON の一例ですが下記のようになっています。
{
"container_id": "xxxxxxxxxxxxxxx",
"container_name": "/ecs-test-firelens-1-nginx-xxxxxxxxxxxxxx",
"ec2_instance_id": "i-xxxxxxxxxx",
"ecs_cluster": "cluster-name",
"ecs_task_arn": "arn:aws:ecs:us-east-1:xxxxxxxxxxxx:task/cluster-name/xxxxxxxxxx",
"ecs_task_definition": "test-firelens:1",
"log": "10.254.1.125 - - [19/Jul/2020:08:19:41 +0000] \"GET /firelens/abc HTTP/1.1\" 404 555 \"-\" \"Mozilla/5.0 .....\"",
"source": "stdout",
"ver": "1.5"
}
今回 log_key
に log
を指定していますので、以下が出力されます。 log_key
を指定しない場合は上記の JSON が出力されます。
10.254.1.125 - - [19/Jul/2020:08:19:41 +0000] "GET /firelens/abc HTTP/1.1" 404 555 "-" "Mozilla/5.0 ....."
出力結果
CloudWatch Logs
下図のようにログストリームが作成されました。
(log-router/log-router/...
は Fluent Bit コンテナのログにになります。)
nginx-error.....
の中を見てみると、下図のように error
や 4xx
にマッチしたログのみ出力されています。
Elastisearch (Kibana)
ヘルスチェックにも、エラー系にもマッチしなかった通常のアクセスのみが記録されています。
Firehose (S3)
Firehose の配信先である S3 には以下の通り、全てのログが出力されています(見やすいように prettify しています)。
{
"ENV": "dev",
"container_id": "9fc09b.....",
"container_name": "/ecs-test-firelens-1-nginx-b0cdb.....",
"ec2_instance_id": "i-.....",
"ecs_cluster": "<cluster_name>",
"ecs_task_arn": "arn:aws:ecs:us-east-1:xxxxxxxxxxxx:task/<cluster_name>/38a4f.....",
"ecs_task_definition": "test-firelens:1",
"log": "10.254.1.125 - - [19/Jul/2020:08:19:22 +0000] \"GET /firelens/ HTTP/1.1\" 200 131 \"-\" \"ELB-HealthChecker/2.0\"",
"source": "stdout"
}
{
"ENV": "dev",
"container_id": "9fc09b.....",
"container_name": "/ecs-test-firelens-1-nginx-b0cdb.....",
"ec2_instance_id": "i-.....",
"ecs_cluster": "<cluster_name>",
"ecs_task_arn": "arn:aws:ecs:us-east-1:xxxxxxxxxxxx:task/<cluster_name>/38a4f.....",
"ecs_task_definition": "test-firelens:1",
"log": "10.254.0.151 - - [19/Jul/2020:08:19:22 +0000] \"GET /firelens/ HTTP/1.1\" 200 131 \"-\" \"ELB-HealthChecker/2.0\"",
"source": "stdout"
}
{
"ENV": "dev",
"container_id": "9fc09b.....",
"container_name": "/ecs-test-firelens-1-nginx-b0cdb.....",
"ec2_instance_id": "i-.....",
"ecs_cluster": "<cluster_name>",
"ecs_task_arn": "arn:aws:ecs:us-east-1:xxxxxxxxxxxx:task/<cluster_name>/38a4f.....",
"ecs_task_definition": "test-firelens:1",
"log": "10.254.1.125 - - [19/Jul/2020:08:19:39 +0000] \"GET /firelens/ HTTP/1.1\" 200 131 \"-\" \"Mozilla/5.0 .....\"",
"source": "stdout"
}
{
"ENV": "dev",
"container_id": "9fc09b.....",
"container_name": "/ecs-test-firelens-1-nginx-b0cdb.....",
"ec2_instance_id": "i-.....",
"ecs_cluster": "<cluster_name>",
"ecs_task_arn": "arn:aws:ecs:us-east-1:xxxxxxxxxxxx:task/<cluster_name>/38a4f.....",
"ecs_task_definition": "test-firelens:1",
"log": "10.254.1.125 - - [19/Jul/2020:08:19:41 +0000] \"GET /firelens/abc HTTP/1.1\" 404 555 \"-\" \"Mozilla/5.0 .....\"",
"source": "stdout"
}
{
"ENV": "dev",
"container_id": "9fc09b.....",
"container_name": "/ecs-test-firelens-1-nginx-b0cdb.....",
"ec2_instance_id": "i-.....",
"ecs_cluster": "<cluster_name>",
"ecs_task_arn": "arn:aws:ecs:us-east-1:xxxxxxxxxxxx:task/<cluster_name>/38a4f.....",
"ecs_task_definition": "test-firelens:1",
"log": "2020/07/19 08:19:41 [error] 7#7: *29 open() \"/usr/share/nginx/html/abc\" failed (2: No such file or directory), client: 10.254.1.125, server: , request: \"GET /firelens/abc HTTP/1.1\", host: \"xxx.xxx.xxx\"",
"source": "stderr"
}
まとめ
FireLens Fluent Bit で rewrite_tag によるログの振り分けを試してみました。シンプルなユースケースであれば使えるのではないかと思います。Fluent Bit のファイルを S3 から読み取るようにすることで、カスタムイメージを作ることなく利用できますので運用面にも寄与できるのではないかと思います。
参考リンクなど
- AWS ブログ - Firelens の発表 – コンテナログの新たな管理方法
- Splitting an application’s logs into multiple streams: a Fluent tutorial
- Under the hood: FireLens for Amazon ECS Tasks
- Amazon ECS FireLens Examples
- Fluent Bit: Official Manual
最後に・・・
この投稿は個人的なものであり、所属組織を代表するものではありません。ご了承ください。