前回の いまさらだけど fluentd に入門した - えいのうにっき に引き続き、「いまさら fluentd」シリーズ(?)。
事例としてはめちゃくちゃありそうなこの組み合わせ、「自分的にかゆいところ」に手が届いてるかんじの記事がないような気がしたので、それを少しでも増やすことができれば、という思いで、書いてみる。
nginx のアクセスログの形式を ltsv 形式にする
まずはこれから。
そのために nginx の conf に手を加える必要がある。たとえばこんな↓かんじかな。
log_format ltsv "local_time:$time_local" "\thost:$remote_addr" "\tforwardedfor:$http_x_forwarded_for" "\treq:$request" "\tstatus:$status" "\tsize:$body_bytes_sent" "\treferer:$http_referer" "\tua:$http_user_agent" "\treqtime:$request_time" "\tcache:$upstream_http_x_cache" "\truntime:$upstream_http_x_runtime" "\tvhost:$host" "\tmethod:$request_method" "\turi:$request_uri"; access_log /var/log/nginx/access.log ltsv;
ltsv、 Labeled Tab-serapated Values
の略で、上記のような設定だと、下記のようなレコードがログファイルに出力されることになる。
local_time:02/Feb/2016:17:52:29 +0900 host:59.106.108.116 forwardedfor:- req:GET / HTTP/1.1 status:200 size:15488 referer:- ua:mackerel-http-checker/0.0.1 reqtime:0.035 cache:- runtime:- vhost:home.a-know.me method:GET uri:/
このアクセスログファイルを、fluentd の in_tail プラグインを使って随時読み込み、BigQuery に送りたいわけなんだけど。
2016-02-03 追記
LTSV において使用するラベルには、推奨されるものが https://ltsv.org/ で定められている、とのこと、コメントでご指摘頂きました。...し、しらなかった...! 皆様はご注意あれ!
in_tail プラグインの設定を行う
こんな↓かんじ。
<source> type tail format ltsv time_format %Y-%m-%d %H:%M:%S %z path /var/log/nginx/home.a-know.me.access.log pos_file /var/log/td-agent/home.a-know.me.access_log.pos tag log.nginx </source>
いまさらだけど fluentd に入門した - えいのうにっき でも勉強したように、入力プラグインからのひとつのイベント・メッセージは、 [ time, tag, record ]
という配列で構成され、これが fluentd のルーティングエンジンに送信されることになる。
なので、今回の場合は↓のようなイベントが送信されることになる。
2016-02-02T09:12:26+09:00<TAB>log.nginx<TAB>{"local_time":"02/Feb/2016:19:04:31 +0900","host":"59.106.108.116","forwardedfor":"-","req":"GET / HTTP/1.1","status":"200","size":"71808","referer":"-","ua":"mackerel-http-checker/0.0.1","reqtime":"0.036","cache":"-","runtime":"-","vhost":"home.a-know.me","method":"GET","uri":"/"}
投入先のテーブル名に年月をつける、そのための準備
BigQuery は、その性格的に、あまり気にせずクエリを投げるのがいいんだけど、かといってその探索対象が大きくなると、その料金が気になってくる。 そのため、どうしても肥大しがちなログなんかは投入先のテーブルをある単位で分割しておくのが良い。(その気になれば、複数テーブルを対象に SELECT できる)
その目的として、今回は下記のようなディレクティブをひとつ挟んだ。
<match log.nginx> type record_reformer enable_ruby true tag ${tag}.${time.strftime('%Y%m')} </match>
要、 fluent-plugin-record-reformer
。tag の書き換えを目的として使う。書き換え後の tag は、例えば log.nginx.201602
のようになる。このタグの末尾の数字を、テーブル名に使う魂胆。
後述する fluent-plugin-bigquery
は、それ単体で、書き出し先テーブル名に年月を加える、くらいのことはできるので、fluent-plugin-bigquery
の README を見てみても良いと思う。
BigQuery 側の準備
ここらで BigQuery サイドでも、以下の様な準備を予めしておく必要がある。
- GCP project の作成
- サービスアカウントの作成
- dataset の作成
これらについては、FluentdでGoogle BigQueryにログを挿入してクエリを実行する がとても明るいので、ぜひ参考にすると良いと思う。 ただテーブルについては、今回は年月ごとに動的に作成することになるので、手動では作成しないこと。
fluent-plugin-bigquery の設定
今回、下記のような設定を行った。(ゆくゆく、同内容を別の場所にも投入したくて fluent-plugin-forest
, copy プラグインも合わせて使っている。)
<match log.nginx.**> type forest subtype copy <template> <store> type bigquery method insert auth_method json_key json_key /etc/td-agent/.keys/nginx-log-to-bigquery-jsonkey.json project a-know-home dataset centrage_nginx_log flush_interval 1 buffer_chunk_records_limit 1000 buffer_queue_limit 1024 num_threads 16 auto_create_table true table nginx_access_log_${tag_parts[-1]} time_format %s time_field time schema_path /etc/td-agent/settings/nginx_access_log_schema.json </store> </template> </match>
要、fluent-plugin-bigquery
。以下、ひとつずつに関してメモ。
json_key
BigQuery 側の準備
でサービスアカウントを作成したが、その認証情報(json形式)。それを、サーバ内の任意の場所に置いておき、そのパスを指定する。
project
GCP には project id
と project number
があって非常にややこしいんだけど、前者の方で ok.
dataset
BigQuery 側の準備
で BigQuery 上に作成した、dataset 名。
auto_create_table
必要に応じて、動的にテーブルを作成したい場合は、これに true
を指定する。
table
投入先のテーブル名を指定する。 nginx_access_log_201602
のようなテーブルを作りたいので、上記のような指定。
tag_parts
プレースホルダは、 fluent-plugin-forest
によって使えるようになったもの。
schema_path
投入先のテーブルのスキーマを json 形式のファイルとして用意しておくんだけど、そのパスを指定する。 今回だと、以下の様なファイルを作成しておいた。
[ { "name": "time", "type": "timestamp" }, { "name": "local_time", "type": "string" }, { "name": "host", "type": "string" }, { "name": "forwardedfor", "type": "string" }, { "name": "req", "type": "string" }, { "name": "status", "type": "integer" }, { "name": "size", "type": "integer" }, { "name": "referer", "type": "string" }, { "name": "ua", "type": "string" }, { "name": "reqtime", "type": "float" }, { "name": "cache", "type": "string" }, { "name": "runtime", "type": "float" }, { "name": "vhost", "type": "string" }, { "name": "method", "type": "string" }, { "name": "uri", "type": "string" } ]
name
に指定する文字列は、nginx の ltsv ログファイル内の各ラベルに一致している必要がある。順番は無関係(なはず)。
よくみると、ログファイルには指定していない time
があるんだけど、これについては↓で。
time_format
time_field
これが少しややこしかった。個人的には以下の様な理解なんだけど、違っていれば教えて欲しい m(_ _)m
たとえば、テーブル定義に abc
があって、そこに [time, tag, record] の time
を入れたいときは、
time_field abc
と書く。そのときのフォーマットの指定を、 time_format
でできる。というかんじ。
FluentdでGoogle BigQueryにログを挿入してクエリを実行する では BigQueryにはTIMESTAMP型は存在しないため、unix epochを指定する。unix epoch形式で挿入しておけばクエリで後から変換が可能だ
とあるんだけど、今の BigQuery には TIMESTAMP型は上述の json ファイルの通り、存在するので、今回は time_format %s
で取得したものを time_field time
とすることで TIMESTAMP 型な time
カラムに投入している。それにより、クエリの結果もいいかんじに表示される。
なので、nginx の ltsv アクセスログファイルの時点で time
というラベルを付けて出力してやるのなら、
time_field
の指定はいらない、はず。一応 fluent-plugin-bigquery
の当該コードを読んで、その指定が無い場合の考慮もされているように見えたので、そのはず。
2016-02-04 追記
time 周りに関して、上記のような理解のまま、今度は Rails のアプリケーションログを BigQuery に送ろうと、 Rails のアプリケーションログも BigQuery に送る by a-know · Pull Request #58 · a-know/a-know-home-server · GitHub で作業を進めていた。
が、自分の理解したようにはならず。ただ、そのときの挙動(警告メッセージ)から、in_tail プラグインの time_format って、format ltsv のときは ltsv の time を time_format のフォーマットに従って変換して、それを [time, tag, record] の time として扱うのかな と思った。まだ in_tail プラグインのソースを見てないから、想像になっちゃうけど。
ただ、そう考えた上での修正を実施したところ、意図通りの挙動をしてくれたので、そうなのかな、と思っているイマココ。あとでまた確認したいところだけど。
2016-02-10 追記
format ltsv
などとしたときの parser は、デフォルトでは record 内の time フィールドを除去してしまうそう。そのため、これを残したい場合は keep_time_key
を指定すれば良かった。
Parser removes time field from event record by default. If you want to keep time field in record, set true to keep_time_key. Default is false. - keep_time_key / tail Input Plugin | Fluentd
参考までに、この機能が入れられたときの PR 。
2017-03-15 追記
さらに追記。以下のようなメモを見つけた。
fluentd で time というキーを in_tail -> out_xxx で出力したい場合の注意点 · GitHub
include_time_key true かつ time_key を time 以外にする
。なるほど。ここらへん、奥が深い。。。
結果
こんな↓かんじで、nginx のアクセスログをクエリで検索できるようになった。
まとめ
...とまぁ、そんなかんじのことを、nginx のアクセスログを fluentd 経由で BigQuery に投げてみる by a-know · Pull Request #54 · a-know/a-know-home-server · GitHub の PullRequest(ぼっちプロジェクト・chef 使用)でも試行錯誤の過程も含め、残しているので、この記事と合わせて、参考にしてもらえればと。
参考リンク
- https://blog.howtelevision.co.jp/entry/2014/08/07/201247
- https://www.totalsolution.biz/nginx-to-google-bigquery/
- GitHub - kaizenplatform/fluent-plugin-bigquery
- FluentdでGoogle BigQueryにログを挿入してクエリを実行する
- fluent-plugin-bigqueryを使ってログをbigqueryに流してみる - tjinjin's blog