ローカルで、fluentbitからdatadog/S3へデータを転送できるdockerイメージを作成する


Firelens構築記事の２回目となります。

[１回目](https://qiita.com/antennix/items/fb1b7c19ebac6c53d408) firelensを使用した、ログ分割と監視処理

[３回目](https://qiita.com/antennix/items/d6d19be67dff43ab34e8) Firelensで、datadog/S3へデータを転送できるdockerイメージを作成する

githubのサンプルはfirelensとの連携まで完成しており、文章中の解説はサンプルベースで行います。

[サンプル(Github)](https://github.com/antennix/firelens)

### ローカルで動くサンプルの作成

fluentbitの調整をfargate上で行うと疲弊してしまうので
docker-composeで構築のサンプルを作成し、正しく動作するfluentbitの構成ファイルを作成することにしました。

#### docker-compose.yml
```docker-compose.yml
version: '3.5'
services:
  web:
    build: app
    #image: *****/firelens:web-container
    ports:
      - "80:80" 
    depends_on:
      - fluentbit
    logging:
      driver: fluentd    
      options:
        fluentd-address: "localhost:24224"
        fluentd-async-connect: "true"
  fluentbit:
    build:
      context: fluentbit
    # image: *****/firelens:fluentbit
    env_file: ./.env
    ports:
      - "24224:24224"
      - "24224:24224/udp"
```

apache-phpコンテナとfluentbitコンテナを連携させただけのcomposeファイルです。
imageのビルド目的なので、全般的にvolumeのmountを行いません。
.envファイルには事前に取得したdatadogのAPIキー、S3の認証情報等を設定して下さい。

logging driverに、fluentdを指定します。
本来のログに、fluentd用のメタデータが付与されます。
http://docs.docker.jp/engine/admin/logging/fluentd.html

fluentd-async-connectオプションは
コンテナ起動完了順により、fluentbitコンテナへログが送信出来ない場合の、バッファ処理です。
http://docs.docker.jp/engine/admin/logging/fluentd.html#fluentd-async-connect



### webコンテナ
webアプリケーションコンテナの解説です。
#### apache(出力ログ調整部分）

サンプルでは、apacheでJSON形式のログを出力するように設定しています。

```apache:log.conf
  ErrorLogFormat "{\"service\":\"error.log\", \"message\":\"%M\"}" 
  ErrorLog /dev/stderr
  LogFormat "{\"service\":\"access.log\", \"message\":\"%h - - %t %r %>s %b %{Referer}i %{User-Agent}i %D\"}" combined
  CustomLog /dev/stdout combined
```

実サービスでは、datadogの予約済属性をここで定義しておくことで
難解なログ整形処理を行わず、datadog側でスムーズなログ管理が行えるようにしています。
https://docs.datadoghq.com/ja/logs/processing/

今回、ログをJSON形式に整形できるタイミングは
1.アプリケーション(php/apache）
2.fluentbit（filterプラグイン）
3.datadog（pipeline）

また、下記のログを全て最終的にS3に格納します。
日次50万件のアクセスログ
日次5万件のアプリケーションログ
日次1000件以下のエラー系ログ

整形タイミングはdatadogでも良いのですが、上記全てのログをdatadogに転送しなければいけなくなるので
datadogのコスト増を懸念しました。資金との兼ね合いかと思います。
https://www.datadoghq.com/pricing/#section-log-management
fluentbitでも可能ですが、設定ファイルが複雑になりそうだったので、ここは最低限としました。

結果、取り回しの容易さを考え,サンプルではアプリケーション（apache）で対応しています。
webアプリケーションでフレームワークを使用しているのであれば、ログのモジュール等で対応できるかと思います。


#### php
余り深く考えずに書きました。
サンプルは、アプリケーションログに相当するものは出力していません。

```php:index.php
<?php
if (isset($_GET['exception'])) {
	error_log("Exception.");
	exit;
}
phpinfo();
```
http://localhost
➡︎アクセスログを標準出力

```json
{"service":"access.log", "message":"172.19.0.1 - - [07/Nov/2019:02:49:33 +0000] GET / HTTP/1.1 200 24000 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.87 Safari/537.36 3457"}
```

http://localhost?exception
▶︎アクセスログに加えて、エラーログが標準エラー出力されます。

```json
{"service":"error.log", "message":"Exception."}
```


### fluentbitコンテナ

fluentbitコンテナの解説です。
firelensは、デフォルトのfirelens用のfluentbit dockerイメージが用意されているので
最低限のログ処理を行い、一箇所だけにログ送信を行うのであれば、dockerイメージは不要です。

[ドキュメント](https://docs.aws.amazon.com/en_us/AmazonECS/latest/userguide/using_firelens.html#firelens-using-fluentbit)
[dockerhubのfirelens用dockerイメージ（fluentbit ver1.2.2）](https://hub.docker.com/r/amazon/aws-for-fluent-bit/)

#### firelens用のfluentbit dockerイメージの要件
・verisonは1.2.2
・カスタム構成ファイルは、fargateではファイルパスのみ指定できる

```
For tasks using the Fargate launch type, the only supported config-file-type value is file.
```
⇨fluentbit.conf,Parsers_File,Plugins_File,Streams_File、fluentbit外部プラグインを使う場合、ボリュームのマウントが必要

#### 今回のfluentbitの要件
・fluentbitで、ログの内容により、出力先を分けて送信したい
・出力先はdatadog(標準プラグイン)と、S3（外部プラグイン）の二箇所
構成が複雑になる恐れがあったため、aws提供のものは使わず、カスタムdockerイメージを使用します。

### fluentbit構成ファイル
#### dockerfile
長いので省略します。
[dockerfile](https://github.com/antennix/firelens/blob/master/fluentbit/dockerfile)
カスタムした構成ファイル群をコンテナに内包してビルドを行います。
fluent-bit-go-s3プラグインの生成は、マルチステージビルドを使用しています。

#### fluent-bit.conf
ログ処理の概要です。

1.[INPUT]24224ポートで、webコンテナからの標準出力を取得
2.[PARSER/FILTER]「log」キーの値は、webコンテナから送信されたjson文字列が入っているので
パース処理を行い、大元のJSONと結合する。※元のlogキーは削除
3.[STREAM PROCESSOR]stream_processorを使用し、serviceキーの中身でログを分類
4.[ROUTE/OUTPUT]分類したログを、それぞれに適した出力先に送信する

```
# https://docs.fluentbit.io/manual/configuration/file
[SERVICE]
    Log_Level       info
    Streams_File    stream_processor.conf
    Parsers_File    parser.conf

[INPUT]
    Name    forward
    Tag     service-firelens-xxx
    alias   combine_log
    Listen  0.0.0.0
    Port    24224

# https://docs.fluentbit.io/manual/filter/parser
[FILTER]
    Name parser
    Match *
    Key_Name log
    Parser json
    Preserve_Key false
    Reserve_Data true

[OUTPUT]
    Name        datadog
    Match       error
    Host        http-intake.logs.datadoghq.com
    TLS         on
    apikey      ${DATADOG_APIKEY}
    dd_source   ${DATADOG_SOURCE}

[OUTPUT]
    Name        datadog
    Match       ap
    Host        http-intake.logs.datadoghq.com
    TLS         on
    apikey      ${DATADOG_APIKEY}
    dd_source   ${DATADOG_SOURCE}

[OUTPUT]
    Name s3
    Match        access
    AccessKeyID     ${OUT_S3_ACCESS_KEY}
    SecretAccessKey ${OUT_S3_SECRET_ACCESS_KEY}
    Bucket          ${OUT_S3_BUCKET}
    S3Prefix        apache
    Region ap-northeast-1
```

fluentbitのStream Processorの処理フローの概要です。
分類されたログは、INPUTに戻り、再処理が行われる点のみ注意し設計しました。
https://docs.fluentbit.io/stream-processing/overview

なお、今回はservice属性の値をログ分類の判断基準としましたが
fluentd形式のJSONは、付加情報としてsource属性(stdout/stderr)を付与する為
標準出力とエラー出力の２種類の分類だけで良いのであれば
webコンテナから、service属性を出力する必要もないかと思います。


#### 整形前

```json
■　エラーログ
[0] f18902c94e19: [1573096151.000000000, {"container_name"=>"/firelens_web_1", "source"=>"stderr", "log"=>"{"service":"error.log", "message":"Exception."}", "container_id"=>"f18902c94e1916cf01fe986eee0e20d4c0ccbdd1bc419d1b122cc6fddd5b8522"}]
```

```json
■アクセスログ
[1] f18902c94e19: [1573096151.000000000, {"container_id"=>"f18902c94e1916cf01fe986eee0e20d4c0ccbdd1bc419d1b122cc6fddd5b8522", "container_name"=>"/firelens_web_1", "source"=>"stdout", "log"=>"{"service":"access.log", "message":"172.19.0.1 - - [07/Nov/2019:03:09:11 +0000] GET /?exception HTTP/1.1 200 - - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.87 Safari/537.36 6693"}"}]
```

#### 整形後

```json
■　エラーログ → datadogに送信される
{"container_name"=>"/firelens_web_1", "source"=>"stderr", "service":"error.log", "message":"Exception.", "container_id"=>"f18902c94e1916cf01fe986eee0e20d4c0ccbdd1bc419d1b122cc6fddd5b8522"}]
```

```json
■　アクセスログ → S3に送信される
{"container_id"=>"f18902c94e1916cf01fe986eee0e20d4c0ccbdd1bc419d1b122cc6fddd5b8522", "container_name"=>"/firelens_web_1", "source"=>"stdout", service":"access.log", "message":"172.19.0.1 - - [07/Nov/2019:03:09:11 +0000] GET /?exception HTTP/1.1 200 - - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.87 Safari/537.36 6693"}
```


### 実行
docker-compose up を実施し、http://localhost?exception にアクセスします。
fluentbitコンテナのログを確認すると、datadogへの転送が行われている事が確認できます。

```
21:24:45 datadog $docker logs -f e848ed87df3a0285bc82fb26a0796b00d00ddd443f14b2c0c5883d028c64dbea
Fluent Bit v1.3.0
Copyright (C) Treasure Data

[2019/11/09 12:24:30] [ info] [storage] initializing...
[2019/11/09 12:24:30] [ info] [storage] in-memory
[2019/11/09 12:24:30] [ info] [storage] normal synchronization mode, checksum disabled, max_chunks_up=128
[2019/11/09 12:24:30] [ info] [engine] started (pid=1)
[2019/11/09 12:24:30] [ info] [in_fw] binding 0.0.0.0:24224
[2019/11/09 12:24:30] [ info] [sp] stream processor started
[2019/11/09 12:24:30] [ info] [sp] registered task: access
[2019/11/09 12:24:30] [ info] [sp] registered task: ap
[2019/11/09 12:24:30] [ info] [sp] registered task: error
[2019/11/09 12:24:41] [ info] [out_datadog] https://http-intake.logs.datadoghq.com, port=443, HTTP status=200 payload={}
```

datadog logsを確認すると、エラーログを管理できていることを確認できます。
![スクリーンショット 2019-11-09 21.26.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/477542/36c519c1-57ff-a749-6ea8-a5de9248eff8.png)

S3も同様に、アクセスログのアップロードを確認できました。
![スクリーンショット 2019-11-09 21.26.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/477542/3cb67036-b2ce-44fa-b142-d4d241477e64.png)
### 次回

以上で、ローカルの開発環境上で作成したfluentbitの構成ファイルの
動作確認を行う事ができました。

次回は、構成ファイルをfirelensと連携させ、fargate上での正常動作を確認していきます。