Help us understand the problem. What is going on with this article?

AWS FireLensでfluentbitコンテナをカスタマイズする具体的な方法

ECSのログ事情について

AWSのECSによるコンテナ運用では、ログの出力がデフォルトでCloudWatchに限定されています。
これはECSのログドライバがawslogsになっているということになりますが、実用面では力不足であることが否めません。
S3やElasticsearchにログを流すには?Datadogなどのサードパーティツールにログを流すには?
解決方法として、AWSの公式でFireLensという方法が紹介されています。

FireLensはfluentbitのコンテナをサイドカーとして起動し、ログの収集をfluentbitの機能で実行するというものです。
fluentdも選択できます)

例)今回目指すWebアプリケーションの構成
firelens.png

実際に、AWSコンソールのECSのタスク定義を設定する画面には以下のような設定項目があります。

スクリーンショット 2020-03-14 15.01.08.png

FireLensの統合を有効にするを選択すると、自動で906394416424.dkr.ecr.ap-northeast-1.amazonaws.com/aws-for-fluent-bit:latestのコンテナがサイドカーとして追加されるようになります。
このコンテナはAWSの公式コンテナです。

当記事では、このコンテナを自身でカスタマイズしたものを適用する一例を紹介します。

コンテナ定義

それではコンテナ定義がどのようになるかTerraformで見てみましょう。
サンプルコードを以下にまとめていますので合わせてご覧ください。

task_definitisons.tf
data "template_file" "default" {
  template = <<EOF
[
  {
    "image": "${var.aws-account-id}.dkr.ecr.ap-northeast-1.amazonaws.com/firelens-sample/go:latest",
    "name": "go",
    "essential": false,
    "logConfiguration": {
      "logDriver": "awsfirelens"
    },
    "portMappings": [],
    "cpu": 64,
    "memoryReservation": 128
  },
  {
    "image": "${var.aws-account-id}.dkr.ecr.ap-northeast-1.amazonaws.com/firelens-sample/nginx:latest",
    "name": "nginx",
    "essential": true,
    "logConfiguration": {
      "logDriver": "awsfirelens"
    },
    "portMappings": [
      {
        "hostPort": 80,
        "protocol": "tcp",
        "containerPort": 80
      }
    ],
    "cpu": 64,
    "memoryReservation": 128
  },
  {
    "image": "${var.aws-account-id}.dkr.ecr.ap-northeast-1.amazonaws.com/firelens-sample/fluentbit:latest",
    "name": "log_router",
    "essential": true,
    "logConfiguration": {
      "logDriver": "awslogs",
      "options": {
        "awslogs-group": "/ecs/firelens-sample",
        "awslogs-region": "ap-northeast-1",
        "awslogs-stream-prefix": "ecs"
      }
    },
    "firelensConfiguration": {
      "type": "fluentbit",
      "options": {
        "config-file-type": "file",
        "config-file-value": "/fluent-bit/etc/fluent-bit_custom.conf"
      }
    },
    "portMappings": [],
    "cpu": 128,
    "memoryReservation": 256
  }
]
EOF
}

Goのコンテナ、Nginxのコンテナ、fluentbitのコンテナを定義しました。
以下を確認してください。

"logConfiguration": {
    "logDriver": "awsfirelens"
}

logDriverをawsfirelensとしています。
なお、fluentbitコンテナ自体のログドライバはawslogsとしてください。

そして注目すべき定義はこちらです。

"firelensConfiguration": {
    "type": "fluentbit",
    "options": {
        "config-file-type": "file",
        "config-file-value": "/fluent-bit/etc/fluent-bit_custom.conf"
    }
}

config-file-valueを指定しています。
この/fluent-bit/etc/fluent-bit_custom.confに独自カスタマイズしたfluentbitの設定を記載しましょう。

fluentbitの設定

よくある設定

サンプルコードをもとに進めます。
先ほどの/fluent-bit/etc/fluent-bit_custom.confを記載していきましょう。

/docker/fluentbit/fluent-bit_custom.conf
[SERVICE]
    Flush 1
    Grace 30
    Log_Level info

[OUTPUT]
    Name cloudwatch
    Match *
    log_key log
    region ap-northeast-1
    log_group_name /ecs/firelens-sample
    log_stream_name container

[OUTPUT]
    Name firehose
    Match *
    delivery_stream my-firehose
    region ap-northeast-1

OUTPUTにcloudwatchfirehoseを指定していることに注目してください。
後述しますが、amazon/aws-for-fluent-bitのコンテナをベースに進めますのでデフォルトで上記のプラグインがインストールされた状態になっています。

さて、この設定でcloudwatchfirehoseにログが出力されるようになりますが、一点、Match *に注目しましょう。
これはFireLensが収集したログを全て出力されるようになっており、
例えばDataLakeのようなログの流し方であれば問題ありませんが、もう少し整形してから流したいというニーズがあるかもしれません。

そこでParserを使って、ログをコンテナごとのCloudWatchストリームに分けるということをしてみましょう。

Parser

先ほどのconfを以下のようにしました。

/docker/fluentbit/fluent-bit_custom.conf
[SERVICE]
    Flush 1
    Grace 30
    Log_Level info
    Streams_File stream_processor.conf
    Parsers_File parser.conf

[FILTER]
    Name parser
    Match *
    Key_Name container_name
    Parser container
    Reserve_Data true

[OUTPUT]
    Name cloudwatch
    Match combine.nginx
    log_key log
    region ap-northeast-1
    log_group_name /ecs/firelens-sample
    log_stream_name nginx

[OUTPUT]
    Name cloudwatch
    Match combine.go
    log_key log
    region ap-northeast-1
    log_group_name /ecs/firelens-sample
    log_stream_name go

[OUTPUT]
    Name firehose
    Match container
    delivery_stream my-firehose
    region ap-northeast-1

以下の箇所でログのパースをおこないます。

[FILTER]
    Name parser
    Match *
    Key_Name container_name
    Parser container
    Reserve_Data true

さてここで、そもそもFireLensからはどのようなログが収集されているのでしょうか。
当然、自身で流したアプリケーション用のログ、Nginxのアクセスログなどをイメージしますが
実はそのほかのメタ情報を付与した状態で収集しています。
以下を確認してください。

{
    "container_id": "abcde12345",
    "container_name": "/ecs-firelens-sample-1-nginx-xxxyyyzzz",
    "ecs_cluster": "arn:aws:ecs:ap-northeast-1:1234567890123:cluster/firelens-sample",
    "ecs_task_arn": "arn:aws:ecs:ap-northeast-1:1234567890123:task/aaa-bbb-ccc-ddd-eee",
    "ecs_task_definition": "firelens-sample:1",
    "log": "10.0.0.0 - - [14/Mar/2020:10:00:00 +0000] \"GET /healthcheck HTTP/1.1\" 200 0 \"-\" \"ELB-HealthChecker/2.0\" \"-\"",
    "source": "stdout"
}

firelensが収集するログの例です。logというキーが出力されるログですが、それ以外にタスク定義の情報やコンテナ名などもあります。
今回はこれらの情報でパースをしてみます。

container_nameをパースしてnginxという部分を抜き出します。
Goコンテナの場合はgoという文字を抜き出します。

/docker/fluentbit/parser.conf
[PARSER]
    Name container
    Format regex
    Regex ^\/(?<task_name>(ecs-firelens-sample))-(?<task_revision>\d+)-(?<container_name>.+)-(?<target_name>.+)$

正規表現で抜き出してみました。以下のようにパースされました。


{
    "task_name": "ecs-firelens-sample",
    "task_revision": 1,
    "container_name": "nginx",
    "target_name": "xxxyyyzzz",
}

container_nameが「nginx」となっています。
これでコンテナ別にログの出力先を分けるということができそうです。

もちろん、ログそのものに対してパースをしてログを振り分けることができれば様々な要件にも対応できるかと思います。
いろいろなパターンを考慮して要件に沿った設計を考えてみてください。

Stream Processor

ログの種類別にタグを振り分けることができます。
先ほどのcontainer_name「nginx」「go」をそれぞれ「combine.nginx」「combine.go」というタグを付与してみましょう。

/docker/fluentbit/stream_processor.conf
[STREAM_TASK]
    Name nginx
    Exec CREATE STREAM nginx WITH (tag='combine.nginx') AS SELECT log FROM TAG:'*-firelens-*' WHERE container_name = 'nginx';

[STREAM_TASK]
    Name go
    Exec CREATE STREAM go WITH (tag='combine.go') AS SELECT log FROM TAG:'*-firelens-*' WHERE container_name = 'go';

# すべてのログ
[STREAM_TASK]
    Name container
    Exec CREATE STREAM container WITH (tag='container') AS SELECT * FROM TAG:'*-firelens-*';

SQLのような形式で記載します。
FireLensのログは元々*-firelens-*にマッチするタグが付与されています。
それにWHERE container_name = 'nginx'などとすることでnginxの新しいタグを追加できます。

追加したタグは、[OUTPUT]セクションで指定しましょう。

[OUTPUT]
    Name cloudwatch
    Match combine.nginx
    log_key log
    region ap-northeast-1
    log_group_name /ecs/firelens-sample
    log_stream_name nginx

Docker定義

続いてDocker定義です。

amazon/aws-for-fluent-bitコンテナをベースに使います。

./docker/fluentbit/Dockerfile
FROM amazon/aws-for-fluent-bit:2.1.1

COPY ./docker/fluentbit/fluent-bit_custom.conf /fluent-bit/etc/fluent-bit_custom.conf
COPY ./docker/fluentbit/stream_processor.conf /fluent-bit/etc/stream_processor.conf
COPY ./docker/fluentbit/parser.conf /fluent-bit/etc/parser.conf

これでECRにプッシュすれば立派なログ収集コンテナとして活躍できるでしょう。

$ aws ecr get-login-password | docker login --username AWS --password-stdin ${AWS_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com

$ docker build -t firelens-sample-fluentbit:latest -f docker/fluentbit/Dockerfile .
$ docker tag firelens-sample-fluentbit:latest ${AWS_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/firelens-sample/fluentbit:latest
$ docker push ${AWS_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/firelens-sample/fluentbit:latest

おわり

fluentbitを使ったログ収集用コンテナのカスタマイズについて解説しました。
この記事で使ったECSアプリケーション用のコード(Nginx、Goアプリ)についてはサンプルコードを確認してください。

誰かの参考になれば幸いです。

flatnyat
フリーランスエンジニア AWS Scala Go Rust PHP Node.js 専門はサーバーサイドです
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away