fluent-bitからCloudWatch LogsとFirehoseにログ転送する

# この記事は

Amazon EKSで稼働するfluent-bitから同EKSクラスタで稼働するPodのログをCloudWatch LogsとData Firehoseへ転送する設定について記します。
2ヶ所にログ転送を行う場合に大事な部分についても併せて記します。

# 環境
Amazon EKS 1.27
fluent-bit(aws-for-fluent-bit 2.31.12)
Amazon Data Firehose
CloudWatch Logs

## fluent-bit.conf

まずは最低限の設定を記載し解説していきます。

```fluent-bit.conf
[SERVICE]
    Flush                1
    Daemon               Off
    Log_Level            info
    Parsers_File         parsers.conf

[INPUT]
    Name                tail
    Path                /var/log/containers/*.log
    Parser              cri
    Tag                 kube.*
    Mem_Buf_Limit       5MB

[FILTER]
    Name                kubernetes
    Match               kube.*

[FILTER]
    Name                rewrite_tag
    Match               kube.*
    rule                $log (.*ERROR.*|.*FATAL.*) to-firehose false

[OUTPUT]
    Name                cloudwatch_logs
    Match               kube.*
    region              ap-northeast-1
    log_group_name      /eks/$(kubernetes['namespace_name'])/$(kubernetes['pod_name'])
    log_stream_name     $(kubernetes['namespace_name'])/$(kubernetes['container_name'])
    auto_create_group   On
    retry_limit         false

[OUTPUT]
    Name               kinesis_firehose
    Match              to-firehose
    region             ap-northeast-1
    delivery_stream    firehose.stream.name
```
## parsers.conf
バージョン1.24以降のEKSクラスタはdockerではなくcontainerdを使うのでログパーサーはcriを利用します。
parsers.confに含まれているのでそちらを利用します。

```parsers.conf
[PARSER]
    # http://rubular.com/r/tjUt3Awgg4
    Name cri
    Format regex
    Regex ^(?<time>[^ ]+) (?<stream>stdout|stderr) (?<logtag>[^ ]*) (?<message>.*)$
    Time_Key    time
    Time_Format %Y-%m-%dT%H:%M:%S.%L%z
    Time_Keep   On
```

## Kubernetes フィルター 
https://docs.fluentbit.io/manual/installation/kubernetes

Kubernetesフィルターを通すことで、次のようなメタデータをログに含められます。
- Pod Name
- Namespace
- Container Name
- Container ID

もう一つ嬉しいことがあって、fluent-bit.confのCloudWatch Logsへ転送するセクションでメタデータを変数のように呼び出すことができるようになります。CloudWatch Logsのロググループ名やログストリーム名にNamespaceやPod Nameを付与することで動的な名前のリソースを作成できます。

## rewrite_tag フィルター
https://docs.fluentbit.io/manual/pipeline/filters/rewrite-tag

rewrite_tagフィルターでは任意の文字列が含まれる場合など条件に合致したらタグを変更できます。

criパーサーを通ったログのメッセージは`$message`に格納されています。`$message`のメッセージ文字列にERRORまたはFATALが含まれていたら、タグを`to-firehose`に変更します。

```
rule                $message (.*ERROR.*|.*FATAL.*) to-firehose false
```

## CloudWatch LogsとFirehoseへの転送

```
[OUTPUT]
    Name                cloudwatch_logs
    Match               kube.*
    region              ap-northeast-1
    log_group_name      /eks/$(kubernetes['namespace_name'])/$(kubernetes['pod_name'])
    log_stream_name     $(kubernetes['namespace_name'])/$(kubernetes['container_name'])
    auto_create_group   On
    retry_limit         false

[OUTPUT]
    Name               kinesis_firehose
    Match              to-firehose
    region             ap-northeast-1
    delivery_stream    firehose.stream.name
```

`kubernetesフィルター`と`rewrite_tagフィルター`を通って来たログに付与されたタグによってCloudWatch LogsかFirehoseに転送されます。

rewrite_tagフィルターでタグを`to-firehose`に変更されている場合はkinesis_firehoseにマッチします。
そうでないタグを持つログはCloudwatch_logsにマッチします。

もう一つのパターンとしてrewrite_tagフィルターのtrue/false部分がtrueだった場合には、タグが`kube.*`と`to-firehose`の2種類のログがフィルターから出ていきます。

```
rule                $message (.*ERROR.*|.*FATAL.*) to-firehose true
```

つまり、全てのログはCloudWatch Logsに転送され、メッセージ文字列にERRORまたはFATALを含むログだけFirehoseへ転送する動作に変わります。

ここで気を付けておきたいのは、rewrite_tagフィルターでログの複製が生まれることでその分のメモリリソースが消費されてしまう点です。fluent-bitのログやコンテナ内のリソース状況を確認し、枯渇しそうであればリソース割り当て量を増やす対応が必要です。

もしリソース枯渇が発生してしまった場合はログ転送の遅延だけでなく、Podを強制的に終了させるOOM Killer(Out of Memory)が発生します。

# おわりに

ログの用途によって転送先を分けることで障害調査をしやすくすることもできますし、不要なログを省くこともできる便利な機能の紹介でした。

以上です。