🔰

AWS firelens 小ネタ1 最初はこうすればわかりやすいのでは?

2024/09/22に公開

firelens(fluentbit)とっつきにくいよね

罠仕様がいくつもあり、このせいで何人が無駄な時間を浪費しているんだろうかと思うと
心が痛くなってきたので、ちょっと導入部分を真面目に日本語で書いておいた方が良いのかなと。

中堅エンジニアさんでもfirelensやfluentbitに関して間違った知識をずっとお持ちだったりして(そのうち書く予定)、、、そういう間違った認識を日本語で正しく取っ払っておいた方が良いかな、
というのがこの記事を書こうと思った理由です。

使うとかなり便利なんですよ。kinesisに送ってS3に配置する場合、動的パーティショニングが使えるので、最初からJSTのパーティショニングでログ配置出来たりする、athenaで検索などの際にUTC変換を考えなくて良いとか。また、もちろんログを検索しやすい形で配置する重要さは、開発している人間ならわかりますよね。その最初の入り口ですので是非マスターしてもらいたい箇所なのです。

読む上で必要な知識

dockerの知識 docker build , runのコマンドの意味と内容がわかる
aws cliの知識 cuiでコマンドは叩ける。
ECR, ECSの知識 ECRにpush pull出来る。 ECSのタスク定義とは何かわかる。
CloudWatch logsの見方が分かる。
という方向けです。

今回の想定

firelensは単純な出力先の設定だけであれば、Task定義に直接記述することができます。
が、こんなことは通常、ビジネス上で運用しているアプリケーションではほとんどなく、
実際は

  • コンテナはnginx等とアプリケーションコンテナといった複数
  • 幾つかのフィルターとパーサーを経由してログを加工し
  • 必要な部分だけ抽出し
  • outputは複数に分ける

とかかと思います。

よって今回は、応用も効くfluentbit.confを上書きする方法で進めたいと思います。
あと、「最初はこうすればわかりやすいのでは?」のタイトルを付けた通り、「設定の探り方」の記事です。

ベストプラクティスではなく、firelens、fluentbitを理解するうえでの、ベストアプローチ的になっておりますのであしからず。

firelensカスタムイメージ作成、今回の小ネタ本体

最初にaws firelensのimageを元に、カスタマイズした設定を追加したimageを作成します。

dockerfile

FROM amazon/aws-for-fluent-bit:debug-latest

COPY ./fluentd.conf /fluent-bit/etc/fluent-bit_custom.conf
COPY ./filter.lua /fluent-bit/etc/filter.lua

fluentd.conf

[Service]
    Flush 1
    Grace 30
    Log_Level debug

# Luaスクリプトデバッグ
[FILTER]
    Name   lua
    Match  *
    script /fluent-bit/etc/filter.lua
    call   output_debug_log

[OUTPUT]
    Name cloudwatch_logs
    Match *
    region ap-northeast-1
    log_stream_prefix all_
    log_group_name /ecs/fluentbit-output
    auto_create_group true
    log_retention_days 30

filter.lua

function output_debug_log(tag, timestamp, record)
    print("Tag: " .. tostring(tag))
    print("Timestamp: " .. tostring(timestamp))
    print("Record:")
    for key, value in pairs(record) do
        print("  " .. key .. ": " .. tostring(value))
    end
    return 0, timestamp, record
end

はい。今回の小ネタとして紹介したかったのはこのfilter.luaのログ出力部分です。
そして、アプローチの方法として、最初は欲張らずにCloudWatchに全部ログを出して、一つ一つ分類加工していきましょうね。
というアプローチです。

一応ビルドとecrにpushを行うコマンドはこんな感じです。
docker-hubとか別の使う場合は各自変更してください。

aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin 123456789.dkr.ecr.ap-northeast-1.amazonaws.com
docker build -t 123456789.dkr.ecr.ap-northeast-1.amazonaws.com/fluentbit:test .
docker push 123456789.dkr.ecr.ap-northeast-1.amazonaws.com/fluentbit:test

firelensで一番の罠、はやはり、タグ名ですよね。

Match *-firelens-*

こんなタグが来るとか、ね、絶対罠なんですよね。[1]
なので、どんなタグが来て、加工などした場合、どのような形になったか。
と言うのを見られるようにするのが一番手っ取り早い方法なんですが、これを出す方法にたどり着くまで中々わからないんですよね。出せればまぁ、こっちのモノ、printfデバッグが可能です。

task defのサンプル

ネタとしてはこれだけなんですけど、一応taskdefもサンプルとして書いておきます。

関係ないところは省略しております。

{
    "taskDefinitionArn": "arn:aws:ecs:ap-northeast-1:123456789:task-definition/sample-taskdef:1",
    "containerDefinitions": [
        {
            "name": "nginx",
            "image": "nginx",
            "portMappings": [
                {
                    "containerPort": 80,
                    "hostPort": 80,
                    "protocol": "tcp"
                }
            ],
            "logConfiguration": {
                "logDriver": "awsfirelens"
            },
        },
        {
            "name": "app",
            "image": "123456789.dkr.ecr.ap-northeast-1.amazonaws.com/app:latest",
            "logConfiguration": {
                "logDriver": "awsfirelens"
            },
        },
        {
            "name": "log_router",
            "image": "123456789.dkr.ecr.ap-northeast-1.amazonaws.com/fluentbit:test",
            "logConfiguration": {
                "logDriver": "awslogs",
                "options": {
                    "awslogs-group": "/ecs/fluentbit-self",
                    "awslogs-region": "ap-northeast-1",
                    "awslogs-stream-prefix": "ecs"
                }
            },
            "firelensConfiguration": {
                "type": "fluentbit",
                "options": {
                    "config-file-type": "file",
                    "config-file-value": "/fluent-bit/etc/fluent-bit_custom.conf",
                    "enable-ecs-log-metadata": "true"
                }
            }
        }
    ]
}

解説しておきますと、nginxと独自のAPP、上記でカスタマイズしたfirelensをecrにpushしたという想定のタスク定義です。
実際にはCPU割り当てやPortの設定、必須定義、起動順序等が入りますがこちらは今回は置いておきます。
因みにfirelensコンテナの必須定義

"essential": true,

を強くお勧めします。
これを設定していないと、ログルータのコンテナだけ落ちて、ログが分配されず、ログの消失につながるといったケースが発生する可能性があるからです。

また、さらっと書いていますが、送信側のコンテナが複数でも1つのログルータ―で受け取ることが可能です。今回のデバッグログで、タグ名でそれらが分類できることを確認してもらえば良いかと思います。
しかし、どこまでのログを1つのログルーターで受信可能か?等は検証しておりませんので極端な多さの場合、ログルータを分割する等も検討した方が良いのかもしれません。udpだからちょっと同梱で振り分けはしんどい気はしますが、、、portを分ければ出来るかな??

試す場合はfluentd.confに記述した
/ecs/fluentbit-output

このタスク定義で定義した
/ecs/fluentbit-self
のロググループはあらかじめ作成しておいた方が無難でしょう。

用意したデバッグ用のlua関数のoutputはfluentbit-selfの方に出力されます。
こちらで、実際にfirelens(fluentbit)が受け取っているタグ情報などを参考にしていけば間違いが少ないです。

おまけ

このデバッグ関数は何度でもfluentd.confに出現してもOKです。デバッグログなので本番用のイメージでは使わないようにしましょうね。
変更前、変更後に差し込めばより、理解も進むでしょう。

# 変更前デバッグログ
[FILTER]
    Name   lua
    Match  *
    script /fluent-bit/etc/filter.lua
    call   output_debug_log

# ログに不要な固定カラムを削除
[FILTER]
    Name record_modifier
    Match *
    Remove_key source
    Remove_key ecs_task_arn

# 変更後デバッグログ
[FILTER]
    Name   lua
    Match  *
    script /fluent-bit/etc/filter.lua
    call   output_debug_log

このようにして、希望のFileterやパーサーの動きを一つ一つ確認して行けば、各々が目的とするゴールに一歩ずつ近づいていけるはずです。
(変更後のデバッグログは別の関数で、出力に工夫をするともっと見やすいか?と書いていて思いました。)

では、小ネタという事なのでこの辺で。
初めて書く記事なので、分かりずらかったらすみません。

この記事が誰かの時間節約に役立つと幸いです。

脚注
  1. ただしこれは、コンテナのstdout/stderrの場合の話。アプリケーション側でfluentdに直接ログを出力する対応を行えば、任意のタグでログルータ―に送信することができます。少なくともjava(logback使用)では可能でした。 ↩︎

Discussion