Fargateコンテナの特定ログをFireLensを使って別々に振り分けようとしたけどボツった話

この記事は、[「AWS Advent Calendar 2021」](https://qiita.com/advent-calendar/2021/aws)の19日目の記事です。

## 背景
弊社で開発しているとあるプロダクトでFargateで起動しているコンテナのアプリログの中で特定のログを収取集計してcsvなどに出力できたらいいなという要望があり、Fargateログの振り分けならFireLensがあったよなということでそれを調査検証してみるというタスクがありました。

色々調査していく過程で、別の方法でいいかもという結論となったのでここで供養させる。

## FireLensとは
>デプロイメントスクリプトを修正したり、手動で追加のソフトウェアをインストールしたり、追加コードを書き込んだりすることなく、コンテナログをストレージや分析ツールに直接追加できます。Amazon ECS または AWS Fargate の設定をいくつか更新することにより、必要な場所にコンテナログを送信することを FireLens に指示するため、宛先を選択し、オプションでフィルターを定義します。FireLens は Fluent Bit または Fluentd と動作します。このことは、それらのオープンソースのプロジェクトによりサポートされた宛先にログを送信できることを意味します。

通常、Fargateのコンテナログはawslogsを用いてCloudWatchlogsに転送されますが、FireLensを用いることでCloudWatchlogsだけでなく、S3やKinesisに直接送ることが出来るようになりました。タスク定義にてFireLensの使用を設定する事で、コンテナログをサイドカーとして配置されたFluentdまたはFluent Bitに転送され、それらを経由することでS3やKinesisなどに直接振り分けるという仕組みです。

<img width="500"　alt="1" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354022/05b87c53-8278-f87a-e2d7-ff2edd126dfe.png">


## やろうとしたこと
1. 既存のログはそのままCloudWatchlogsに転送したい
2. タスク定義にはWebとApp用のコンテナがあり、それぞれ別のロググループに転送したい
3. 出力されるコンテナログの中でも特定文字列を含むログに関するものだけを別の出力先に送って分析/csv出力したい。

③ではS3にログを転送して、Athenaでクエリ打ってcsv出力を考えておりました。

![スクリーンショット 2021-12-19 17.44.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354022/6c23ce48-9cae-4871-93ea-adfe86fe2383.png)


## FireLensによるログ振り分け
### FireLens用コンテナイメージ作成準備

下記ファイルをそれぞれ用意する

```sh
├── Dockerfile
├── fluent-bit-custom.conf
└── stream_processor.conf
```

 - Dockerfile

```Dockerfile.
FROM amazon/aws-for-fluent-bit:2.16.1

COPY ./fluent-bit-custom.conf /fluent-bit/custom.conf
COPY ./stream_processor.conf /fluent-bit/etc/stream_processor.conf

RUN ln -sf /usr/share/zoneinfo/Asia/Tokyo /etc/localtime
```


```stream_processor.conf
[STREAM_TASK]
    Name s3tag
    Exec CREATE STREAM s3tag WITH (tag='s3tag') AS SELECT log FROM TAG:'*-firelens-*' ;
```

```fluent-bit-custom.conf
[SERVICE]
    Flush 1
    Grace 30
    Streams_File stream_processor.conf

[FILTER]
    Name grep
    Match s3tag
    Regex log <マッチさせたい値(正規表現)>

[OUTPUT]
    Name cloudwatch
    Match <コンテナ名1>-firelens-*
    region ap-northeast-1
    log_group_name <ロググループ1>
    log_stream_prefix from-fluentbit/
    auto_create_group true
    log_key log

[OUTPUT]
    Name cloudwatch
    Match <コンテナ名2>-firelens-*
    region ap-northeast-1
    log_group_name <ロググループ2>
    log_stream_prefix from-fluentbit/
    auto_create_group true
    log_key log

[OUTPUT]
    Name s3
    Match s3tag
    region ap-northeast-1
    bucket <S3 バケット名>
    total_file_size 1M
    upload_timeout 1m
    json_date_key false
```

Firelensを使用する場合、コンテナのログは`コンテナ名-firelens-タスクID`というタグが付与されるため、例えばコンテナ名が`app`でタスクIDが`abcdefghijklmnopqrstuvwxyz123456`の場合はのタグは`app-firelens-abcdefghijklmnopqrstuvwxyz123456`となる。

つまり、それぞれのタグに対してOUTPUTにてロググループを設定する事でコンテナログをそれぞれ別のロググループに転送する事が出来ます。

その際に、logの部分だけを出力させたいので`log_key`パラメータに`log`を指定する。この指定が無いと下記の様にJSONでログが出力がされ、やや情報過多に

```log.json
{
    "container_id": "f186706ff1bd4fe8885d79030d05b5b9-527074092",
    "container_name": "app",
    "ecs_cluster": "sample-cluster",
    "ecs_task_arn": "arn:aws:ecs:ap-northeast-1:xxxxxxxxxxxx:task/sample-cluster/f186706ff1bd4fe8885d79030d05b5b9",
    "ecs_task_definition": "sample-front:1",
    "log": "> npx blitz start --port 80",
    "source": "stdout"
}
```

また、ログの中にある特定文字列を含む場合、S3へ転送としたいのでFILTERとGrepを使って抽出させる。その際に、Cloudwatchlogsに転送するログにはFILTERが適用させないために、StreamProcessorに専用のタグを付与してそのタグにだけFILTERを適用させる。

### FireLens用コンテナイメージ作成
 - ビルド

```sh
docker build -t ${AWS_ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com/${REPOSITORY_NAME}:v1.0 .
```

 - ECRにログイン

```sh
aws ecr get-login-password --profile ${PROFILE} | docker login --username AWS --password-stdin https://{AWS_ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com
```

- コンテナイメージPush

```
docker push {AWS_ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com/${REPOSITORY_NAME}:v1.0
```

### タスク定義設定
#### IAMロール
既存の設定がCloudwatchlogsへ転送のみで、今回S3への転送が増えましたのでECSタスクロールに対象S3への書き込み権限を追加しました。

```sample-firelens-s3.json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "firelens",
            "Effect": "Allow",
            "Action": "s3:PutObject",
            "Resource": "arn:aws:s3:::<S3 バケット名>"
        }
    ]
}
```

###　ログルータの統合(FireLensの使用)
 - タスク定義の設定下部の`ログルータの統合`より、`FireLens統合を有効にする`にチェックを入れる
 - `タイプ`に`fluentbit`、`イメージ`にECRにPushしたコンテナイメージを指定(例：xxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/fluentbit:v1.0)する。
 - 適用を押下する

<img width="500"　alt="1" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354022/b65dce5f-fd06-5077-03f2-cec7de2ba3d9.png">


`FireLensコンテナが更新されました。`の表示がでればOK

<img width="500"　alt="1" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354022/93db993e-840d-fad5-fb67-2cbd8ddc7a94.png">

また、コンテナの項目で`log_router`のコンテナが追加されているのが、分かる

<img width="500"　alt="1" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354022/79abddf7-6bfc-6b25-1247-36bdeb8cdf7e.png">


#### FireLens用ロググループの作成
アプリのログとFireLensのログが混在しないようにFireLens単独のロググループを、Cloudwatchlogsに任意の命名(例:/ecs/firelens)で作成します。

#### web/appコンテナのログ出力設定
- web/appそれぞれのコンテナを選択して`ログ設定`項目のログドライバーを`awslogs`から`awsfirelens`に変更して更新を押下する

<img width="500"　alt="1" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354022/512aa1eb-e00d-86b4-9da5-83ae0a4a2e26.png">

- 何故かログオプションが空にならないので、再度web/appそれぞれのコンテナを選択して、ログオプションで`x`を押下して空にした状態で`更新`を押下する。

<img width="500"　alt="1" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354022/12d360fa-0119-c649-14c2-1478fc61a2a9.png">

#### FireLensログ出力設定
- log_routerのコンテナを選択する
- FireLens自身は`awslogs`のログドライバーを使用
- ログをプションをそれぞれ指定して更新を押下する

|Key|Value|
|-|-|
|awslogs-group|/ecs/firelens|
|awslogs-region|ap-northeast-1|
|awslogs-stream-prefix|firelens|


#### JSONによる設定
コンテナイメージに配置したカスタム定義を読み込ませます。

- `ボリューム`項の`JSONによる設定`をクリックする

<img width="500"　alt="1" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354022/7d08d6de-a560-cc6b-310a-50f08e6cbe27.png">


- firelensConfiguration部分を下記のように編集して`保存`を押下する

```json
            "firelensConfiguration": {
                "type": "fluentbit",
                "options": {
                    "config-file-type": "file",
                    "config-file-value": "/fluent-bit/custom.conf"
                }
            },
```

<img width="500"　alt="1" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354022/873017f0-41a9-4f29-4f7d-0f6f974faa66.png">


 - それぞれ完了したら、タスク定義作成画面最下部より`作成`を押下する。

<img width="500"　alt="1" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354022/ce4a326f-9b4d-009e-7135-5a6291c89d51.png">


### サービスの更新
作成したリビジョンを指定してサービスを更新
- web/app/log_routerのコンテナがそれぞれ`RUNNING`であることを確認

<img width="500"　alt="1" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354022/f1742c85-5141-bd0c-563c-1ddfe165d513.png">


- web/app/log_routerがそれぞれのロググループにログが転送されていることを確認

<img width="500"　alt="1" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354022/279ccdca-61bc-1a14-1e91-ff2e32e4391e.png">

<img width="500"　alt="1" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354022/8c62af82-064c-a3d8-efa9-f586110bd013.png">

<img width="500"　alt="1" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354022/fa7d5049-6cf5-1bb4-5c82-063ecd676b84.png">


- S3にログが転送されていることを確認

<img width="500"　alt="1" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354022/cb2a881b-1338-fc9a-292b-b3e75e7f4f5e.png">


## 最終的に
FireLensによるログ転送を調査してきた訳ですが、最終的には`log Insight`を用いる事となりました。理由としては以下になります。

- FireLensによるログ転送にはlog_router用のコンテナが必須であり、検証当時コスト削減も丁度課題に上がり始めていた事もあって、より低コストで実現できる方式が望ましかった。
- 自分の調査不足かもしれませんが、「特定文字列を含むログは別ターゲットに転送」をFireLensに当初期待していたが、どうやら、上記方法の場合「指定したロググループにコンテナログが吐き出される」それに+して「指定した文字列にマッチした物は別ターゲットにも転送」という形式であった
- ログ転送自体が目的ではなく、その先の分析が出来ればいい
- そういった事もあって、S3+AthenaではなくCloudWatchlogs内で完結できる`log Insight`でよくないかととなり、こんな感じで`log Insight`で出来るんだけどこれで十分だったりする？と改めてプロダクトチームに相談したところ、それでOKとなった。

### log Insight
>CloudWatch Logs Insights では、Amazon CloudWatch Logs のログデータをインタラクティブに検索して分析できます。クエリを実行することで、運用上の問題に効率的かつ効果的に対応できます。
問題が発生した場合は、CloudWatch Logs Insights を使用して潜在的原因を特定し、デプロイした修正を検証できます。 CloudWatch Logs Insights には専用のクエリ言語といくつかのシンプルで強力なコマンドが含まれています。また、サンプルクエリ、コマンドの説明、クエリの自動補完、ログフィールドの検出を使用して CloudWatch Logs Insights を開始できます。サンプルクエリは、AWS のサービスの複数のログタイプ向けに用意されています。

要するに、logsに格納されているログに対してクエリを実行して分析できる機能。
ログのクエリ分析はS3+Athenaがメジャーだが態々S3にログをエクスポートしなくてもlogsだけで完結できるのが利点で、CSV出力も可能

## まとめ
結果的にFireLensを使う事は無かったですが、調査の過程で複数コンテナを持つタスクにおけるFluent-bitでのロググループの振り分けであったり、log_keyの指定やフィルタリング、S3への転送、log Insightでのクエリなど様々な知見が得られました。今後Firelensを使う場面が得られたらこれである程度出来るはず。

## 参考
[詳解 FireLens – Amazon ECS タスクで高度なログルーティングを実現する機能を深く知る]
(https://aws.amazon.com/jp/blogs/news/under-the-hood-firelens-for-amazon-ecs-tasks/)
[Fluent Bit: Official Manual / Fluent Bit + SQL / WHERE Condition]
(https://docs.fluentbit.io/manual/stream-processing/getting-started/fluent-bit-sql#where-condition)
[Fluent Bit: Official Manual / Grep]
(https://docs.fluentbit.io/manual/pipeline/filters/grep)