FargateからFireLensで三つ股出力（2 of 2）

[前回](https://qiita.com/itsuki3/items/cf717d72bd139af4d647)のあらすじ：FargateタスクからFireLensに吐き出す準備ができた。

## Step by Step

### 1. CWL

まずCWLへの吐き出しだが、前回記事の追記にも書いたように、いくつか注意点がある。

- マネジメントコンソールの「FireLensの統合を有効にする」チェックボックスは、**あくまでlog_routerコンテナをアプリコンテナのサイドカーとして追加するまで**しかやらないので、ログを構成するための設定を忘れず行う必要がある。
- **1) アプリコンテナのログ構成（logConfiguration）セクションに、`awsfirelens`ドライバーへの書き出しを明記すること。**
  - これによって、アプリコンテナからlog_routerコンテナへの書き出し経路が成立する。
  - そのままだと宛先不明でどこにもログが書かれないので、[公式の例](https://github.com/aws-samples/amazon-ecs-firelens-examples/tree/mainline/examples/fluent-bit/cloudwatchlogs)を参考にlogConfiguration→optionsを記述し、FireLensにCWL設定の詳細を教えてやる必要がある。この設定がFireLensコンテナに渡され、fluent-bit.confが自動生成される仕組み。
  - 最終的にCWLに書くのはFireLensコンテナ。
- **2) log_routerコンテナのlogConfiguration→optionsに、`awslogs`ドライバーへの書き出しを明記すること。**
  - 書かないとエラーになる。
  - これはlog_routerコンテナ自身（fluent-bit）のシステムログ記録用で、アプリコンテナのログ出力設定はあくまでアプリコンテナ側のoptionsで書く（カスタム構成ファイルの場合はやや扱いが異なるが、これについては後述する）。
- **3) タスク実行ロールのポリシーに、CreateLogGroup権限を加えること。**
    - 前も似たようなハマリポイントがあったが、今回も要注意。
    - デフォルトで作成されるタスク実行ロールである`ecsTaskExecutionRole`に`AmazonECSTaskExecutionRolePolicy`というAWSマネージドなポリシーが割り当てられているが、このポリシーには`logs:CreateLogStream`と`logs:PutLogEvents`は許可されているが`logs:CreateLogGroup`の許可が入ってない。[本来CWLプラグインに必要な権限](https://github.com/aws-samples/amazon-ecs-firelens-examples/blob/mainline/examples/fluent-bit/cloudwatchlogs/permissions.json)が足りておらず、いつまで経ってもロググループが作成されず、従ってタスクも正常に作成されない。これはアプリコンテナだけでなく、log_routerコンテナも同様なので、双方でちゃんとロググループが作成されていることを確認すること（面倒なら自分で作成してもよい）。

わかってみれば理屈は通るのだが、若干**面倒くさい。**

実際の（アプリコンテナの）設定はこちら。
なお、前回流用したコンテナはログ出力検証には不向きだったので、タスク定義を`nginx:latest`に差し替えている。
<img width="854" alt="スクリーンショット 2020-08-12 午後3.37.27.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609876/8b3158d0-ac08-cdae-178c-fc6a9673abb3.png">

新しいタスク定義のリビジョンが作成されたら、当該サービスでこのタスクを選択して更新。
<img width="525" alt="スクリーンショット 2020-08-12 午後4.56.43.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609876/b91fcb21-1c6c-db13-5d98-ee4c5f44c2dd.png">

`ab`で軽く負荷をかけてみる。

```
% ab -n 1000 -c 5 http://XXX.XXX.XXX.XXX/
This is ApacheBench, Version 2.3 <$Revision: 1843412 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

（中略）

Percentage of the requests served within a certain time (ms)
  50%     15
  66%     16
  75%     17
  80%     17
  90%     31
  95%    104
  98%    233
  99%    236
 100%    271 (longest request)
```
無事、FireLens（log_routerコンテナ）経由でCWLにログが出力された。
![スクリーンショット 2020-08-12 午後4.29.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609876/71b657c1-4ea8-aa19-5ebb-4410c53af75c.png)

### 2. KFH

二股にする前に、KFHとの1:1構成も試しておく。
KFHの作成手順は割愛するが、入力元をDirect PUT、出力先をS3とし、テストしやすいよう1MBまたは60秒で吐き出すバッファ設定にした以外は全てデフォルトで作成した。

|設定|値|
|:--|:--|
|ストリーム名|kfh-firelens|
|入力元|Direct PUT|
|出力先|S3|
|バッファ設定|1MBまたは60秒|

アプリコンテナの設定はこちら。
<img width="837" alt="スクリーンショット 2020-08-12 午後2.52.27.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609876/c1baad78-89bd-acbf-d9ca-1219ba132721.png">

FireLensコンテナの設定は変更不要。
あとは、タスクロール（ログ出力など実行環境を整えるタスク実行ロールとは異なり、コンテナ上のアプリにAWSサービスへのアクセス権を与えるためのロール。役割としてはEC2におけるインスタンスプロファイルに近い）に、KFHへの書き込み権限も追加しておく。タスク実行ロールの方は、ここでは変えなくても動作する。

先ほど同様、サービスを新たなタスク定義で更新。デプロイが終わったのを確認後、`ab`を流すと、KFHを経由してS3にログが出た。
<img width="697" alt="スクリーンショット 2020-08-12 午後3.28.58.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609876/5814b216-15f1-00e2-18b9-d8e8197f29ea.png">

### 3. CWL+KFH

#### 3-1. 二股構成について

ここから二股。
普通に考えれば、アプリコンテナに記載するFireLens設定に二つ目の吐き出し先を追加するだけの話に思えるのだが、どうもECSやFargateのlogConfigurationからFireLensへは、一つしか設定のセットを流し込めないっぽい。直接、本家の書式でfluent-bit.confを作って、カスタム設定ファイルとして投入してやる必要があるようだ。
おまけにFargateの場合、設定ファイルをS3から取ってくる構成が現状[取れない](https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/using_firelens.html)ため、設定ファイルを焼き込んだカスタムfluent-bitイメージを作らないといけない（ECSにはこの制約はない）。

なお、これまではアプリコンテナに諸々設定してきたが、カスタム構成ファイルの場合、投入する先はアプリコンテナのlogConfiguration→Optionsではなく、どうやらFireLensコンテナ（fluent-bit）側のfirelensConfiguration→optionsであるようだ。逆にアプリコンテナ側のOptionsは空のままでよい。
本来、FireLensはアプリコンテナに書いたoptionsを自動的に読み取ってFireLensコンテナ上のfluent-bit.conf（またはfluentd.conf）をよしなに生成してくれる、fluent-bitやfluentdのコンテナ設定は忘れてOK、というところが魅力な気がするのだが、カスタム構成ファイルの場合はもう普通にfluent-bitコンテナにfluent-bitネイティブの設定を食わせてしまえ、ということらしい。

色々言いたいことはあるが、とりあえず[公式](https://github.com/aws-samples/amazon-ecs-firelens-examples/tree/mainline/examples/fluent-bit/send-to-multiple-destinations)の記載や先人の経験を参考に、カスタム構成ファイルを作ってみることにする。

※こちらのサイトを参考にさせていただきました。深謝。
[FireLens を利用した Fargate タスクのログルーティング](https://yasuharu519.hatenablog.com/entry/2019/12/19/142454)
[AWS FireLensでfluentbitコンテナをカスタマイズする具体的な方法](https://qiita.com/flatnyat/items/3cad79fc5a323ae3245c)

#### 3-2. タスク定義の作成
multiple-firelens-targetsという名前で、新たなタスク定義を作成する。

アプリコンテナのlogConfiguration。**`Name`も含めて、optionsセクションは空にしておく（`Name`が残っていると、前回書いた通りエラーになる）。**
<img width="857" alt="スクリーンショット 2020-08-12 午後5.02.37.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609876/c538b6ad-b339-8cd3-e3f1-f6a5abe7f70c.png">

log_routerコンテナのlogConfiguration。Auto-configureしつつ、ロググループの自動作成のみ手動で追加している。
<img width="879" alt="スクリーンショット 2020-08-12 午後5.05.05.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609876/963a931a-d50a-3a68-5c40-aa01c40604b3.png">

いったん保存し、手元のターミナルを開く。

#### 3-3. カスタム構成ファイルの作成

`extra.conf`の名前で、これまで設定した二つの設定に相当するOUPUTセクションを持つ、以下のファイルを作成する。
FireLensは、このカスタム構成ファイルを取り込んでfluent-bit.confを生成してくれるようだ。

```text:extra.conf
[OUTPUT]
 Name cloudwatch
 Match *
 region ap-northeast-1
 log_group_name web-firelens-fluent-bit
 log_stream_prefix web
 auto_create_group true

[OUTPUT]
 Name firehose
 Match *
 region ap-northeast-1
 delivery_stream kfh-firelens
```

なお、タブ区切りではなくスペース区切りが作法のようなので注意。途中まで、以下のようなエラーが多発してプチハマリしていた。

>[ Error] Error in line 2: Each key must have a value

また、困った時はfluent-bitをデバッグモードにすると手掛かりが得られる可能性がある（自分は、KFHのストリーム名を微妙に間違えていた・・・）。
ファイル冒頭に以下の設定を入れるだけ。log_routerのCWLログストリームにwarnやdebugが出てくるので、それでデバッグできる。ファクトベースの切り分けをする上で、やっぱりログって大事だと再認識。

```text:extra.conf
[SERVICE]
 Log_Level debug

...
```

#### 3-4. カスタムlog_routerイメージの作成

ローカルのDocker環境で、FireLensイメージ（AWSカスタムのfluent-bitイメージ）をpullする。

```
% docker pull amazon/aws-for-fluent-bit
```

以下の`Dockerfile`を`extra.conf`と同じフォルダに用意し、イメージをbuildする（ここではイメージ名は`firelens-multiple-targets`としている）。

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

```
% docker build -t firelens-multiple-targets
```

#### 3-5. ECRにカスタムイメージをpush

ECRレポジトリに対し、buildしたカスタムイメージをpushする。
ECRレポジトリの作成とログインは割愛するが、そこの準備ができていれば、あとは色々設定を調整しつつ以下を繰り返すだけ（ここでは、ECRのレポジトリ名を`firelens-ecr`としている。数字はお使いの環境の12桁のAWSアカウント番号）。

```
% docker build -t firelens-multiple-targets .
% docker tag firelens-multiple-targets:latest 012345678901.dkr.ecr.ap-northeast-1.amazonaws.com/firelens-ecr:latest
% docker push 012345678901.dkr.ecr.ap-northeast-1.amazonaws.com/firelens-ecr:latest
```

最終的に、`012345678901.dkr.ecr.ap-northeast-1.amazonaws.com/firelens-ecr:latest`のような形で、カスタムイメージがECRに登録できていればOK。

このあたりで疲労の色が濃くなってくる。
やっぱり、せめてS3から設定ファイル取れるようにして欲しいなあ。。。

#### 3-6. カスタム構成ファイルの指定

カスタムlog_routerイメージのpushが完了したら、ECSに戻って、先ほど保存したタスク定義に新たなリビジョンを作成する。
「JSONによる設定」を開き、imageセクションおよびfirelensConfigurationセクションを修正する（現時点では、これらのセクションはJSONでしか設定できない）。

![スクリーンショット 2020-08-12 午後7.40.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609876/fc0b2471-fb4f-cb76-98e2-c3f50609f7e6.png)

ここで`"config-file-type" : "s3"`が使えるようになれば、3-3から3-5の作業は要らなくなるのだが、まあ仕方ない。

#### 3-7. 動確

例によって、サービスから先程作成したリビジョンを選択し、デプロイ。
<img width="520" alt="スクリーンショット 2020-08-12 午後11.27.14.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609876/d01b60fd-13f5-604b-4486-ba5ececb7773.png">

うまく行っていれば、`ab`で負荷をかければCWLとKFH（S3）の両方にログが出力されるわけだが、
![スクリーンショット 2020-08-12 午後11.26.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609876/0427a714-ea61-0c75-b3a7-39e8b6524610.png)
<img width="1110" alt="スクリーンショット 2020-08-12 午後11.26.54.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609876/1b13d655-cbd0-5751-c587-ff2b0b396e64.png">

どうやら成功したようだ。

### 4. CWL+KFH+AES

最後はAESを加えた三つ股。ここまで来ていれば、さほど難しい話ではない。
ちなみに、少し前まではKFHを介して送る必要があったが、[FireLensでElasticsearchプラグインがサポート](https://aws.amazon.com/jp/about-aws/whats-new/2020/07/fluent-bit-supports-amazon-elasticsearch-destination-route-container-logs/)され、直接出力が可能になっている。
[公式のタスク定義例](https://github.com/aws-samples/amazon-ecs-firelens-examples/tree/mainline/examples/fluent-bit/amazon-elasticsearch)を参考に、OUTPUTの追加を行う。

#### 4-1. AESドメインの作成

まずはログの最後の投げ込み先となるAESドメインを作成する。だいたい10分から15分ほどかかる。
KFH同様、詳細は割愛するが、ほぼテスト用ドメインのデフォルトで作成した。

|設定|値|
|:--|:--|
|ドメイン名|aes-firelens|
|タイプ|開発およびテスト|
|バージョン|7.7(latest)|
|データノード|r5.large.elasticsearch x1、EBS|
|ネットワーク構成|パブリックアクセス|
|ドメインアクセスポリシー|カスタムアクセスポリシー（アクセス元を現在のIPに制限）|

#### 4-2. タスクロールの更新

タスクからAESにもアクセスする必要があるので、タスクロールにAESへの書き込み許可を忘れずに追加しておく。
今回は一時的なテストなので、対象ドメインに対し`"es:*"`を許可する。

#### 4-3. カスタム構成ファイルの更新

`extra.conf`を三つ股に拡張する。ついでに、先んじてデバッグも有効化しておく。

```text:extra.conf
[SERVICE]
 Log_Level debug

[OUTPUT]
 Name cloudwatch
 Match *
 region ap-northeast-1
 log_group_name web-firelens-fluent-bit
 log_stream_prefix web
 auto_create_group true

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

[OUTPUT]
 Name es
 Match *
 Host search-aes-firelens-[文字列].ap-northeast-1.es.amazonaws.com
 Port 443
 Aws_Auth On
 Aws_Region ap-northeast-1
 tls On
```

AESのOUTPUT設定は正確には把握していなかったが、[公式のlogConfiguration→option内容](https://github.com/aws-samples/amazon-ecs-firelens-examples/blob/mainline/examples/fluent-bit/amazon-elasticsearch/task-definition.json)から大体推測できるのと、[fluent-bitのElasticsearch用書式](https://docs.fluentbit.io/manual/v/1.5/pipeline/outputs/elasticsearch)に準拠しているようだったので、そちらも参照しつつ記載した。

#### 4-4. カスタムイメージの作成、ECRへのpush、リビジョンの更新

3-4、3-5、3-6と同様の操作を行う。

#### 4-5. 動確

`ab`でアクセスを発生させてみると、log_routerのシステムログに色々出てくる。`fluent-bit`という名前でインデックスを作成してくれている模様。
<img width="1499" alt="スクリーンショット 2020-08-13 午前0.22.12.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609876/e267796f-2e6c-0079-8a15-b07d58254456.png">

AESドメインを見ると、fluent-bitインデックスに数件のドキュメントが登録されている。`_doc`タイプも、FireLensのスキーマを正確に反映しているようなので、どうやら無事書き込めているらしいことがわかる。
<img width="559" alt="スクリーンショット 2020-08-13 午前0.24.55.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609876/f0a92d4b-4125-89ef-cf03-d99440d67132.png">

Kibanaでも問題なく可視化できた。
<img width="1113" alt="スクリーンショット 2020-08-13 午前0.29.45.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609876/f3064f1f-790c-a2ef-31c8-cdbe5e4bcc6b.png">
![スクリーンショット 2020-08-13 午前0.30.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609876/f7c82722-b98b-9cf6-2789-f929b74e77b2.png)

CWL、KFHも同時に出力されており、**晴れて三つ股が完成**したことになる。
![スクリーンショット 2020-08-13 午前0.37.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609876/79f8dd5e-7317-8bc2-53a3-0f0721763f71.png)
<img width="1087" alt="スクリーンショット 2020-08-13 午前0.35.10.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609876/4b376b18-e248-73a6-8601-daeca8cfc081.png">

## まとめ
思わぬ長編になってしまったことからわかる通り、現状はそれなりに気合いが要求される。
仮にFargateでもS3からカスタム構成ファイルを取れるようになればコンテナのbuild/push回りは随分簡素化されるだろうし、そもそも普通にマネジメントコンソールから複数optionsが投入可能であって欲しいところ。

それでも、従来のCWLに一択に比べればかなり柔軟になったとは感じるし、カスタム構成ファイルを使えばサードパーティー製品への書き込みもできるようなので、あれこれ試行錯誤しただけの甲斐はあった。
あとは、CWLで課題だった「STDOUT出力されたものは何でもかんでも処理されてしまう問題」を解決できそうな正規表現によるフィルタリングに習熟すれば、大抵のログ出力要件には対応できるのではないだろうか。