FargateでFireLensログドライバを使い自前で用意したfluentdを動かす

# 背景
FargateのログドライバはCloudWatch Logs,Splunkしか使用できず、その他のログ基盤に連携を行いたい場合はCloudWatch Logsから転送するか(CloudWatch Logsの料金がかかる)、ログドライバを使用せず共有ボリュームにログファイルを出力してサイドカーのFluentdで収集する(ボリュームは8GBまで)といろいろと制約があり、なかなかFargateに移行ができないでいました。

この度、FireLensがGAになり、自前のFluentdに転送できるとのことだったので試してみたところ、色々と詰まりましたのでメモとして残します。

# 開発
##0. FireLensについて
クラスメソッド様の[記事](https://dev.classmethod.jp/cloud/aws/ecs-firelens/)がとてもわかり易いと思います。

##1. 概要
作成、使用した環境は[GitHub](https://github.com/yoshihir/firelens-fluentd-sample)にあります。
構成としてはFargateでnginxのログをFirelens(fluentd)を使い、S3に出力しています。
`nginx -> firelens(fluentd) -> S3`

##2. パッケージ構成
ざっくりとしたパッケージ構成とその説明です。

```
.
├── fluentd                         
│   ├── conf.in                 <-- fluentdのcofigディレクトリ。input情報を書いている
│   ├── conf.out                <-- fluentdのcofigディレクトリ。output情報を書いている
│   ├── Dockerfile              <-- fluentdのDockerfile
│   ├── build.sh                <-- Dockerfileでbuildし、ECRにpushしています
│   └── fluent.conf             <-- fluentdのcofigファイル。conf.inとconf.outをincludeしている
├── nginx                    
│   ├── Dockerfile              <-- nginxのDockerfile
│   └── build.sh                <-- Dockerfileでbuildし、ECRにpushしています
├── terraform                   <-- AWS環境一式をterraform化してあります 
└── README.md
```


##3. メモ
fluentdのDockerfileです。
firelensに対応するため、`COPY fluent.conf /fluentd/etc/fluent-custom.conf`と名前を変更しています。これは後述の`task-definition.json`で使用するためです。

```dockerfile:Dockerfile
FROM fluent/fluentd:v1.7-1

# Use root account to use apk
USER root

# below RUN includes plugin as examples elasticsearch is not required
# you may customize including plugins as you wish
RUN apk add --no-cache --update --virtual .build-deps \
        sudo build-base ruby-dev \
 && sudo gem install fluent-plugin-rewrite-tag-filter -v 2.2.0 \
 && sudo gem install fluent-plugin-s3 -v 1.1.11 \
 && sudo gem sources --clear-all \
 && apk del .build-deps \
 && rm -rf /tmp/* /var/tmp/* /usr/lib/ruby/gems/*/cache/*.gem

COPY fluent.conf /fluentd/etc/fluent-custom.conf
COPY conf.in      /fluentd/etc/conf.in
COPY conf.out     /fluentd/etc/conf.out

USER fluent
```

次に`fluent.conf`ですが、input情報である`<source>`の記述を消しています。理由は記述するともうポートが空いていると**error**になるため削除しています。おそらく、AWS側で記述があるためだと思われます。

```conf:fluent.conf
<match>
  @type relabel
  @label @firelens_log
  @id    in_forward_all
</match>

@include conf.in/*.conf
@include conf.out/*.conf
```

最後に`task-definition.json`ですが、

- `config-file-type` -> `file`
- `config-file-value` -> `/fluentd/etc/fluent-custom.conf`

としています。`fluent.conf`と記述してしまうと予約後なのか、**error**になるため、このような記述になります。
[公式ドキュメント](https://docs.aws.amazon.com/AmazonECS/latest/userguide/using_firelens.html)には特に記述は見当たりませんでした。

```json:task-definition.json
[
  {
    "name": "nginx",
    "image": "xxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/infra/nginx-1.17.4:latest",
    "cpu": 256,
    "memory": 512,
    "essential": true,
    "portMappings": [
      {
        "containerPort": 80,
        "protocol": "tcp",
        "hostPort": 80
      }
    ],
    "logConfiguration": {
      "logDriver": "awsfirelens"
    }
  },
  {
    "name": "fluentd",
    "image": "xxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/infra/fluentd-1.7:latest",
    "cpu": 256,
    "memory": 512,
    "essential": true,
    "logConfiguration": {
      "logDriver": "awslogs",
      "options": {
        "awslogs-group": "/ecs/firelens-fluentd",
        "awslogs-region": "ap-northeast-1",
        "awslogs-stream-prefix": "ecs"
      }
    },
    "firelensConfiguration": {
      "type": "fluentd",
      "options": {
        "config-file-type": "file",
        "config-file-value": "/fluentd/etc/fluent-custom.conf"
      }
    }
  }
]
```

他に気をつけることとしては、tagが`コンテナ名-firelens-コンテナID`とハイフン区切りで渡されてきます。
fluentdはドット区切りなため、`rewrite_tag_filter`を使い、正規表現でtagをrewriteしています。
[ソースコード](https://github.com/aws/amazon-ecs-agent/blob/master/agent/engine/docker_task_engine.go#L91)を見たのですが、こちらから上書きする手段はなさそうです。
Fluent Bitだったらドット区切りではないため、困らないからでしょう。
ECSのfluentdログドライバだったらoptionでtagを渡せたため、そうしてくれるとありがたいと思いました。

```conf:in-match.conf
<label @firelens_log>
  <filter>
    @type record_transformer
    <record>
    tag ${tag}
    </record>
  </filter>
  <match>
    @type rewrite_tag_filter
    @label @create_tag
    <rule>
      key tag
      pattern /^nginx-firelens-(\w.+)$/
      tag "nginx.sample.$1"
    </rule>
  </match>
</label>

〜〜省略〜〜
```

# まとめ
色々と書きましたが、十分本番環境に耐えられる状態として提供されたのではないでしょうか。
既存のログ基盤がすでにあるサービスにとっては、Fargateへの移行に関しての懸念事項がまた一つ消えたと思います。