この記事は、株式会社うるる(ULURU) Advent Calendar 2023 の15日目の記事です。
サービスは AWS で動いているものの、各種ログや、それらを基にしたダッシュボードは Google の BigQuery で参照・集計したい、という需要はそれなりにあるように思います。
今年、うるるの入札情報サービス NJSS のバックエンドでも、上記の仕組みを導入しましたので、構成や設定について簡単に書き残してみます。
構成図
FireLens について
最初は、「FireLens」というのが何を指す言葉なのかよくわかっていませんでした。公式ブログにある「シンタックスシュガー」という表現が一番しっくりくるように思います。
awsfirelens ログドライバーは、タスク定義用のシンタックスシュガーであり、Fluentd または FluentBit のアウトプットプラグインの設定を指定できます。
内部では、以下のようなことをやっているのだと思います。
- fluentbit(or fluend)のコンテナを起動して24224ポートで待ち受け
- 出力元コンテナは(おそらく)log driverにfluentdを指定したような動作になる
一瞬、単に「fargateでもlogdriverにfluentdが選べるようになりました」じゃダメなのかな?とも思ったのですが、driverにfluentdを選んだらそれを受け取るfluentbit(かfluentd)は絶対に必要になるし、その場合はINPUTの定義は固定でよくて、FILTERとOUPUTが制御できればOKなので、その辺をひとかたまりにしてAWS側が用意してくれた仕組みがFireLens、という理解です。
タスク定義
こちら を参考に作成しました。
image
FireLensの基本的な使い方としてよく紹介されているのは、以下のようなパターンです。
- AWSの提供するイメージをそのまま使う
- ユーザ側で個別に設定したいものがある場合は、S3に配置してタスク定義で
config-file-value
で指定する
ただ、この方法だと、以下2点の対応ができなかったため、今回は公式イメージを基に自前でdocker imageを buildしてECRにpushしたものを使いました。
-
Mem_Buf_Limit
の設定 - GCPの認証を通すための環境変数の引き渡し
それぞれ、詳細は後述します。
name
(コンテナ名)
これは log_router
という名前にしないとうまく動きませんでした。
(最初、適当な名前をつけてデプロイしたらエラーになってしまいました)
fluentbit 設定
INPUT
過去にfluendを運用していたことたあるのですが、その際「変なログを投げつけられてfluendで処理が詰まってしまい、fluendがメモリを食い潰してしまう」といったトラブルに悩まされたことがあり、「fluentbitでもMem_Buf_Limit
(参考)の設定を入れたい!」という思いがありました。
ただ、この設定はINPUTセクションにしか記述できません。そして、AWSの公開イメージをそのまま使ってユーザ定義をconfig-file-value
で指定する場合は、INPUTセクションは編集できない(ユーザ定義でINPUTセクションを記述しても無視される)ため、INPUT含めて自分で記述したconfigを使っています。
これが、fluentbitコンテナは自前でbuildしたものを使っている理由の1つです。
なお、ユーザ定義ファイルを指定した場合に、firelensとして起動したfluentbitコンテナ(log_routerコンテナ)の中で設定ファイルがどうなっているかは、以下の記事が参考になります。
ローカルとAWS上でコンテナ内部から見た /fluent-bit/etc/fluent-bit.conf
の中身が変わっている理由が最初わからなくて戸惑ったのですが、実際動かしてdf
で見たら/fluent-bit/etc/fluent-bit.conf
に外部ボリュームか何かがマウントされていました。
ログの分類
今回は、fluentbitにログを渡す経路は以下2種類になります。
- アプリケーションからfluentドライバを使って直接
24224
ポートに出力 - 標準出力/標準エラー出力が logdriver 経由で出力
前者のパターンでは、出力時に任意のタグをセットすることができます。
一方、標準出力/標準エラー出力の場合は、内容に応じてタグを付与するといったことは(自分の知る限り)できません。
ただ、例えばnginxのログであれば、普通のリクエストログと起動時のログは分けて扱いたく、ただしそれらは全部標準出力として出力され、同じコンテナから出力されたものは全部同じタグがついてfluentbitに到達するので、そこをどうやって区分けしようかな、ということで、今回は以下のように対応しました。
nginx設定(抜粋)
# nginx設定
log_format main 'NGINX_ACCESS_LOG $msec $remote_addr $request_method $request_uri $status $body_bytes_sent $http_referer $http_user_agent $request_time $http_x_forwarded_for';
fluenbit設定(抜粋)
[FILTER]
Name rewrite_tag
Match *-nginx
Rule $log (NGINX_ACCESS_LOG) nginx-access-log false
- log_formatで固定の文字列を埋め込み
- fluentbitの
rewrite_tag
で、$log
本文と上で埋め込んだ文字列でマッチング - hitしたら
nginx-access-log
のタグを付与 - 以降はこのタグとの
Match
でハンドリング
このあたりは、もしかしたらもう少しうまいやり方があるのかもしれません。
Parse
時刻はTIMESTAMP
型、request_timeはNUMERIC
型でテーブルを作りたいですが、テーブルと一致する型でないとレコードが登録されません。
Type ConverterやLuaのFilter Pluginで変換するといった方法もありそうですが、今回はParserでTypesを指定して対応しました。
上で nginx-access-log
タグを付与したログにParser Filterを適用。
[FILTER]
Name parser
Match nginx-access-log
Key_Name log
Parser nginx_access_log
対応するparserの定義は以下の通り。
[PARSER]
Name nginx_access_log
Format regex
Regex ^[^ ]* (?<timestamp>[^ ]*) (?<remote_addr>[^ ]*) (?<method>[^ ]*) (?<path>[^ ]*) (?<status>[^ ]*) (?<body_bytes>[^ ]*) (?<referer>[^ ]*) (?<ua>[^\"]*) (?<request_time>[^ ]*) (?<x_forwarded_for>[^\"]*)
Time_Key timestamp
Types status:integer body_bytes:integer request_time:float
Output
Bigquery output pluginを使用します。
ここで一つ困ったのが、GoogleのCredentialの設定でした。
AWSのパラメータストアに格納して読み込ませようとしたのですが、構築時点(この記事執筆時点でも)では、$GOOGLE_SERVICE_CREDENTIALS
には「credntialの中身」ではなく「credentialファイルのパス」を指定する必要があります。
イメージの中にcrednetialファイルを埋め込むのは避けたいので、ちょっと悩んだ結果、entrypoint.sh
でfluentbit
プロセス起動前に以下を追加して対応しました。
- パラメータストアの中身をファイルに出力
- ファイルパスを環境変数にセット
echo ${GCP_CREDENTIAL_VALUE} > /fluent-bit/etc/gcp.json
export GOOGLE_SERVICE_CREDENTIALS=/fluent-bit/etc/gcp.json
(GCP_CREDENTIAL_VALUE
はタスク定義のsecrets
でパラメータストアと紐付け)
これが、Buildし直したイメージを使う理由の2つ目です。
おわりに
もう少し楽に書けるかと思ったのですが、色々思い出したり確認し直したりで意外と時間がかかってしまいました。
誰かのお役に立てば幸いです。