アプリケーションログをFluent BitでCloudWatch Logsに集約した話

# 概要

今回は、EC2で稼働している弊社のアプリケーションログをCloudWatch Logsへ集約しました。
その中で調査したツールの比較や、設定方法について紹介します。

# 背景

弊社の主要なアプリケーションは2台のEC2で稼働しています。
これまではエラーを調査する際、
1. EC2（A）にsshで接続してログファイルをgrepして探し、
2. 該当エラーがない場合はEC2（B）にsshして...

とそれぞれのインスタンスを行き来することになり、調査に時間がかかっていました。

そこで、アプリケーションログをCloudWatch Logsに集約することでログを一元管理し、ブラウザからエラー調査ができるようにする必要がありました。

# 各ツールの比較

## FireLens

ECSの場合使用可能。今回は通常のEC2なので候補から外れました。

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

> ### なぜ単に Fluentd や Fluent Bit を推奨しないのですか？なぜ FireLens なのですか？
> Fluentd と Fluent Bit は強力ですが、たくさんの機能セットには常に複雑さが伴います。FireLens を設計する際、2 つの主要なユーザーセグメントを想定しました。
>1. *Fluentd と FluentBit を使用して*、ログをどこにでも簡単に送信する方法を必要としているユーザー。
> 2. *Fluentd と FluentBit のフルパワー*を必要としており、タスクのログをこれらのログルーターにパイプするために必要な差別化につながらない重労働は AWS に管理して欲しいユーザー。
> 最初のグループに対する私たちの答えは、私たちが作成した [FireLens サンプルの GitHub リポジトリ](https://github.com/aws-samples/amazon-ecs-firelens-examples)によって最もよく実証されています。これらの例のほとんどは、タスク定義での設定のみを必要とし、誰でもわずかな変更だけで使用することができます。簡単であり、Fluentd や Fluent Bit の知識を必要としません。
> 
> 2 番目のグループに対する私たちのソリューションは、タスク定義の [FireLensConfiguration オブジェクト](https://docs.aws.amazon.com/AmazonECS/latest/APIReference/API_FirelensConfiguration.html)の options フィールドに示されています。

このように、ECSを利用している場合は後述のFluentdやFluent Bitよりも推奨されています。
FireLensを利用することで内部的にFluentdやFluent Bitが使用できます。
利点としては、設定ファイルをS3から読み込めるので、設定ファイルをイメージに含める必要がなくなるようです。

## Fluentd vs Fluent Bit

それぞれ以下のような違いがあります。
拡張性を持たせたい場合はFluentdに軍配が上がりますが、そうでない場合はFluent Bitが良さそうです。

|  | Fluentd | Fluent Bit |
| --- | --- | --- |
| 対応 | コンテナ/サーバー | コンテナ/サーバー/組み込みLinux |
| 言語 | C、Ruby | C |
| メモリ | 60MB以上 | <b>1MB以下</b> |
| パフォーマンス | 中 | <b>ハイパフォーマンス</b> |
| 依存関係 | Ruby Gemとして構築されているので、一定数のGemが必要 | <b>基本なし</b> |
| プラグイン | <b>1000以上</b> | 100以上 |
| ライセンス | Apacheライセンスv2.0 | Apacheライセンスv2.0 |

> 参考: https://docs.fluentbit.io/manual/about/fluentd-and-fluent-bit

AWSからも推奨されているようですね。

> Fluent Bit が AWS の推奨ですが、それは Fluentd に比べてリソース使用量が大幅に少ないからです。Fluentd も何百ものプラグインが存在する定評のあるツールであり、サポートします。長期的には、FireLens がオープンソースコミュニティを活性化し、Fluentd がサポートする多くの機能とすべての送信先が Fluent Bit に追加されることを期待しています。AWS は Fluent Bit にプラグインや改善を提供し続けることで、この役割を果たしていきます。

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

# 今回のユースケースにはどちらが合うか

<b>Fluent Bit</b>を使用することにしました。

理由としては、
- 今回は、単純にログをCloudWatch Logsに出力できればよかった
- より軽量でハイパフォーマンス
- 外部依存がない

からです。

その他の理由として、Container InsightsではFluentdは非推奨になりつつあることも挙げられます。
将来的にEC2→ECSに移行する可能性もあるため、Fluent Bitの方がより安全策とも言えますね。

> Container Insights の Fluentd のサポートは現在メンテナンスモードになっています。つまり、AWS はこれ以上 Fluentd の更新を提供せず、近い将来に廃止する予定です。
さらに、Container Insights の現在の Fluentd 構成は、最新の改善およびセキュリティパッチがない古いバージョンの Fluentd Image `fluent/fluentd-kubernetes-daemonset:v1.10.3-debian-cloudwatch-1.0` を使用しています。

https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/monitoring/Container-Insights-setup-logs.html


# 設定方法

ここからは具体的な設定方法を紹介します。
以下の資料を参考にしています。

https://docs.fluentbit.io/manual/pipeline/outputs/cloudwatch

https://github.com/aws/amazon-cloudwatch-logs-for-fluent-bit

## 1. EC2のOSを確認する
まずは、Fluent BitをインストールするEC2のOSを確認します。

EC2上で、以下のコマンドを実行することで確認できます。
```
$ cat /etc/os-release
```
OSはubuntuでした。

## 2. Fluent Bitをインストールする

[こちら](https://docs.fluentbit.io/manual/installation/getting-started-with-fluent-bit#install-on-linux-packages)から対応しているものを選択します。

今回はhttps://docs.fluentbit.io/manual/installation/linux/ubuntu を使用しました。

```
// fluent-bitをインストール
$ curl https://raw.githubusercontent.com/fluent/fluent-bit/master/install.sh | sh

// 有効化
$ sudo systemctl start fluent-bit

// ステータスの確認
$ systemctl status fluent-bit                                                          
● fluent-bit.service - Fluent Bit
   Loaded: loaded (/lib/systemd/system/fluent-bit.service; disabled; vendor preset: enabled)
   Active: active (running) since 水 2024-02-28 08:49:34 UTC; 1min 11s ago
     Docs: https://docs.fluentbit.io/manual/
 Main PID: 32313 (fluent-bit)
    Tasks: 4
   Memory: 13.2M
      CPU: 53ms
   CGroup: /system.slice/fluent-bit.service
           └─32313 /opt/fluent-bit/bin/fluent-bit -c //etc/fluent-bit/fluent-bit.conf
```

この時点では、ログはsudo tail -f /var/log/syslogで確認できます。

デフォルトでは1秒ごとにcpuのログが吐かれ続けるので、一旦止めておきます。
```
$ sudo systemctl stop fluent-bit
```


## 3. 出力先をCloudWatch Logsに変更

上記出力より、設定は<b>/etc/fluent-bit/fluent-bit.conf</b>を見ていることが分かります。

このファイルを編集し、各アプリおよびnginxのログの設定をします。
以下は設定の例です。

各設定の意味は後述します。

```/etc/fluent-bit/fluent-bit.conf
[INPUT]
    Name tail 
    Path /var/log/hogehoge/app_A/stdout.log
	Tag  app_A.log

[INPUT]
    Name tail 
    Path /var/log/hogehoge/app_B/stdout.log
	Tag  app_B.log

[INPUT]
    Name tail 
    Path /var/log/hogehoge/nginx/access.log
	Tag  nginx.access_log

[INPUT]
    Name tail 
    Path /var/log/hogehoge/nginx/error.log
	Tag  nginx.error_log

[OUTPUT]
    name cloudwatch_logs
    match   app_A.log
    region ap-northeast-1
    log_group_name /aws/ec2/app_A
    log_stream_name ${EC2_TAG_NAME}-${APP_A_DEPLOY_DATETIME}
    auto_create_group true
    workers 1
	log_retention_days 3
	log_key log

[OUTPUT]
    name cloudwatch_logs
    match   app_B.log
    region ap-northeast-1
    log_group_name /aws/ec2/app_B
    log_stream_name ${EC2_TAG_NAME}-${APP_B_DEPLOY_DATETIME}
    auto_create_group true
    workers 1
	log_retention_days 3
	log_key log

[OUTPUT]
    name cloudwatch_logs
    match  nginx.*
    region ap-northeast-1
    log_group_name /aws/ec2/nginx
    log_stream_prefix ${EC2_TAG_NAME}-
    auto_create_group true
    workers 1
	log_retention_days 3
	log_key log
```

## 4. configファイルをアプリごとに分離する

<details>
<summary>/path/to/fluent-bit-app_A.conf</summary>

```
[INPUT]
    Name tail 
    Path /var/log/hogehoge/app_A/stdout.log
	Tag  app_A.log

[OUTPUT]
    name cloudwatch_logs
    match   app_A.log
    region ap-northeast-1
    log_group_name /aws/ec2/app_A
    log_stream_name ${EC2_TAG_NAME}-${APP_A_DEPLOY_DATETIME}
    auto_create_group true
    workers 1
	log_retention_days 3
	log_key log
```
</details>

<details>
<summary>/path/to/fluent-bit-app_B.conf</summary>

```
[INPUT]
    Name tail 
    Path /var/log/hogehoge/app_B/stdout.log
	Tag  app_B.log

[OUTPUT]
    name cloudwatch_logs
    match   app_B.log
    region ap-northeast-1
    log_group_name /aws/ec2/app_B
    log_stream_name ${EC2_TAG_NAME}-${APP_B_DEPLOY_DATETIME}
    auto_create_group true
    workers 1
	log_retention_days 3
	log_key log
```
</details>

<details>
<summary>/path/to/fluent-bit-nginx.conf</summary>

```
[INPUT]
    Name tail 
    Path /var/log/hogehoge/nginx/access.log
	Tag  nginx.access_log

[INPUT]
    Name tail 
    Path /var/log/hogehoge/nginx/error.log
	Tag  nginx.error_log

 [OUTPUT]
    name cloudwatch_logs
    match  nginx.*
    region ap-northeast-1
    log_group_name /aws/ec2/nginx
    log_stream_prefix ${EC2_TAG_NAME}-
    auto_create_group true
    workers 1
	log_retention_days 3
	log_key log
```

</details>

上記のconfファイルを以下のように読み込みます。

```/etc/fluent-bit/fluent-bit.conf
@INCLUDE /path/to/fluent-bit-wk_web.conf
@INCLUDE /path/to/fluent-bit-wvd_userui.co
@INCLUDE /path/to/fluent-bit-nginx.conf
```


### 各設定項目について

```
[INPUT]
    // tail -fコマンドのように、ファイル末尾をログとして吐く
    Name tail
    // ソースとなるログファイル
    Path /var/log/supervisor/vk_web/stdout.log
    // タグ名。[OUTPUT]へのルーティングのために使用する。
    Tag  vk_web.log

[OUTPUT]
    // ログ出力先にcloudwatch_logsを指定
    name cloudwatch_logs
    // [INPUT]のうち、Tagが一致するものをこのOUTPUTにルーティングする。ワイルドカード指定もできる。
    match   vk_web.log
    // リージョン指定
    region ap-northeast-1
    // ロググループ名
    log_group_name /aws/ec2/vk_web
    // ログストリーム名（詳細は後述）
    log_stream_name ${EC2_TAG_NAME}-${VK_WEB_DEPLOY_DATETIME}
    // trueの場合、ロググループを自動的に作成する
    auto_create_group true
    // cloudwatchへの出力用にスレッドを確保する。ほとんどの場合0か1で問題ない
    workers 1
    // ログストリームの保持日数
    log_retention_days 3
    // fluent-bitの出力のうちcloudwatchに送るkeyを指定、logの場合は純粋なログのみ出力する
    log_key log
```

上記のうち、log_stream_nameでは`${EC2_TAG_NAME}-${VK_WEB_DEPLOY_DATETIME}`のように`${}`のフォーマットで環境変数を読み込んでいます。

## 5. 環境変数の設定

Fluent Bitは、環境変数をwebサーバー上の`/etc/default/fluent-bit`から読み込みます。

今回の記事からは省略しますが、
Github Actionsの中でshellを実行し、環境変数としてデプロイ日時を設定することで
デプロイごとにログストリームが分かれるようにしています。

# コストの考慮

https://dev.classmethod.jp/articles/tsnote-how-to-optimize-cloudwatch-logs-cost/

ログの保存にも以下のようにお金がかかります。
基本的にログ調査は、当日や土日に起こったエラーの調査をすることがほとんどであり、
最悪元々のようにEC2にsshしてログファイルを直接確認することもできます。

よって、ログの保持期間は３日と設定しています。

```
収集 (データの取り込み)・・・0.50USD/GB

保存 (アーカイブ)・・・0.03USD/GB

分析 (Logs Insights のクエリ)・・・スキャンしたデータ 1 GB あたり 0.005USD

検出およびマスク (データ保護)・・・スキャンされたデータ 1 GB あたり 0.12USD

分析 (Live Tail)・・・0.01 USD/分
```

# 完了！

変更した設定を読み込ませるために、Fluent Bitを再起動させます。
```
$ sudo systemctl restart fluent-bit
```

アプリケーションへのアクセスがあった時、
`auto_create_group true`としているためロググループ、ログストリームが存在しなければ自動生成してくれます。

# まとめ

Fluent Bitが自動でロググループやログストリームを生成してくれるのには感動しました。
今後ログ調査環境を整える際は、ぜひ使用を検討してみてください！