ECS on Fargate (with ecs-cli)でfluentbitを使ってログを複数箇所に送信する

## ゴール
ECS on Fargateでfluentbitを利用してログを複数箇所に送信する

#### 前提条件
* ECS on Fargate
    * Ruby on Rails (今回の箇所に関係ないですが)
    * 既に標準出力にログをはいており、awslogsドライバーを利用してcloudwatchにログを送信している
    * 環境はecs-cliを利用している(書き方がecs-cliによっているので必要に応じて利用しているもので読み替えていただければ)

#### 達成条件
* APM(Datadog)にログを送信したいモチベーションが出てきたので以下の２箇所に同時にログを送信する構成を取りたい
    * CloudWatch Logs
    * Datadog


今回はDatadogさんの話によった話は書きませんがAPMやインフラ監視を一元管理してくれるsaasサービスになります

https://www.datadoghq.com/ja/

## まとめ
* [fluentbit](https://fluentbit.io/) をサイドカーとして立ち上げてCloudWatchLogsとDatadogに送信するようにする
* アプリケーションログをfluentbitに連携するにあたり [AWS FireLens](https://aws.amazon.com/jp/blogs/news/under-the-hood-firelens-for-amazon-ecs-tasks/) を利用する
    * アプリケーションのログドライバーでFireLensを指定 `driver: awsfirelens`
* 複数箇所にログを送信するためにはfluentbitに対して自前でconfigファイルを書く必要がある(単一箇所に送信する場合はFireLensの設定だけで可能そう)
    * AWS FireLensの設定でconfigのパスを指定する方法として「ローカルファイルの参照」「S3上のファイルの参照」の二つがあるがFargateではローカルファイルの参照しかサポートしていない ([参考](https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/userguide/firelens-taskdef.html#firelens-taskdef-customconfig))
    * fluentbitのcontainer内にconfigファイルを注入する必要があるのでfluentbitのイメージをラップしたimageを自前で用意する必要がある


## fluentbitイメージの用意
複数箇所にログを送信する場合はFireLensの設定のみでは実現できず、自前でconfigファイルを用意する必要があります。
ただしconfigファイルのパスをFireLens側で指定すればfluentbitがいい感じに動いてくれるとのこと
(やったことないのですがfluentbit側でこれを設定すると大変なのかな？)

https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/userguide/firelens-taskdef.html#firelens-taskdef-customconfig

FireLensからconfigファイルを指定する方法として二つあるがFargateには一つしか選択肢がない、、、、、
* `file`: fluentbitコンテナ内のファイルパスを指定する => Fargateはこちらのみ
* `s3`: S3のファイルパスを指定する

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

ここは早く改修されてほしいTT (FireLensからの指定の仕方は後述)

なんとかしてfluentbit container内にローカルファイルを置かないといけないので悲しいDockerfileを爆誕させた
(fluentbitは現時点の最新でとってるので適宜変更するのが良さそうです)

```Dockerfile
FROM amazon/aws-for-fluent-bit:2.23.3
COPY ./extra.conf /fluent-bit/etc/extra.conf
```

confファイルは参考程度に
秘匿情報をどうしようと模索していたのですが `${...}` でいい感じに読み込んでくれました
```extra.conf
[OUTPUT]
    Name                cloudwatch
    Match               *
    region              ap-northeast-1
    log_group_name      sample-application
    log_stream_prefix   rails

[OUTPUT]
    Name              datadog
    Match             *
    Host              http-intake.logs.datadoghq.com
    TLS               on
    compress          gzip
    apikey            ${DD_API_KEY}
    dd_service        your-service-name
    dd_source         your-source-name
    provider          ecs
```

これをデプロイ時に新しく作成したECRにpushして使ってます


## AWS FireLensを利用してfluentbitにログを流す
ここはecs-cliの書き方によっていると思うので適宜ドキュメントを見てください

docker-composeでfluentbitのサイドカーコンテナを追加してアプリケーションのlogging周りを変更
```docker-compose.yml
version: "3"
services:
  app: # 既存のRailsアプリケーション
    ...
    depends_on: # fluentbitが立ち上がるまで待つ
      - log_router
    logging:
      driver: awsfirelens # 今までawslogsを指定していたのをFireLensに変更

　　　　log_router: # fluentbitのcontainerを追加
    image: {ECRのパス}
    build:
      context: ./fluentbit # fluentbit用のDockerfileのパス(今回はfluentbitディレクトリに押し込んだ)
    restart: always
    logging:
      driver: awslogs # fluentbit自体のログはawslogsドライバーでCloudWatchLogsに投げる
      options:
        awslogs-group: {...}
        awslogs-region: ap-northeast-1
        awslogs-stream-prefix: {...}
```

```ecs-params.yml
task_definition:
  ...
  services:
    ...
    log_router:
      ...(cpu/memoryなどは適宜設定)
      firelens_configuration:
        type: fluentbit
        options:
          config-file-type: "file"
          config-file-value: "/fluent-bit/etc/extra.conf" # Dockerfileで指定したパス
      secrets:
        - value_from: {...} # 今回はDatadog用のAPIキーを入れたので追加しましたが必要に応じて無視してください
          name: DD_API_KEY
```

## fluentbitからCloudWatch Logsにログを書き込むための権限を追加
fluentbitからCloudWatch Logsにログを書き込むためにタスクロールにポリシーの追加が必要でした
```json
    {
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": ["{書き込み先のパスを列挙}"]
    }
```

## コメント
FargateからもS3でのファイル指定ができるようになればconfをS3配置して公式のイメージが使えるのでDockerfileを用意してECRにプッシュしてみたいな作業が不要になるので待ち遠しいです。。。
これがベストかはわからないので気づいた点があればコメントお願いします

## 参考

https://aws.amazon.com/jp/blogs/news/under-the-hood-firelens-for-amazon-ecs-tasks/

https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/userguide/firelens-taskdef.html#firelens-taskdef-customconfig

https://dev.classmethod.jp/articles/fargate-fiirelens-fluentbit/