仕事で僕が担当してるプロダクトでも、「そろそろ fluentd を...」という話題が出始めた。
社内の別プロダクトでは既に導入されてるものもあったりして、たぶん会社的にはノウハウも溜まり始めてきてるとは思うんだけど、なにせ僕自身が fluentd について雰囲気しかわかってないままだったので、それじゃイカンなと、いまさらだけどようやく fluentd に入門した。そのついでにメモった内容を晒す。
入門前のイメージ
- みんな「アプリは何も考えず、とりあえず fluentd に投げとけば幸せになれる」って言ってる
- なので、そういう感じの存在なんだろうな fluentd は、くらいの理解しかない
- 具体的にどう幸せになるのかはあんまり想像が付いてない
- "d" がついてるので、デーモン的な何かかな?
- ロゴがイカしてる くコ:彡
ゴール
- 実際に導入するとなったら具体的にどんなことをしなければいけないのか、と、そのだいたいの作業感をつかむ
- 導入済みプロダクトのエンジニアと基本的な会話をできるようになる
何を使って入門したか
会社にあった下記2冊の本の該当箇所を読んだ。
- Web エンジニアの教科書
- サーバ/インフラエンジニア 養成読本
- 作者: 佐々木達也,瀬川雄介,内藤賢司
- 出版社/メーカー: シーアンドアール研究所
- 発売日: 2015/03/26
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (4件) を見る
サーバ/インフラエンジニア養成読本 ログ収集〜可視化編 [現場主導のデータ分析環境を構築!] Software Design plus
- 出版社/メーカー: 技術評論社
- 発売日: 2014/08/14
- メディア: Kindle版
- この商品を含むブログを見る
入門してわかったこと
fluentd とは
fluentd は "ログ集約ソフトウェア" である、と、きっぱりと書かれていた。でも、"ログ" って言い切ってるけど、slack や Twillio などへの出力も後述するプラグインで対応できるので、広義での "ログ"、と思ったほうがよさそう(いわゆるアプリケーションログだけではなく)。"データから価値を創造するコストを最小限にするミドルウェア" なんていうカッコいい書かれ方もしてた。
"d" がついてるので、デーモン的な何かかな?
って書いたけど、これはあたってた。このデーモンに対して入力を行うと、設定された通りに出力が行われる。というイメージ。アプリケーションは、今までであれば出力先ごとにそれぞれ実装をしなければならなかったものを、fluentd への出力だけ考えれば良くなる。受けた情報ごとにどのように出力するか、は、fluentd への設定だけでまかなえる。...なるほどこれは確かに幸せになれそう。
fluentd への入力、fluentd からの出力について
fluentd に対するログの入力・出力処理の部分はプラグインで設定できるようになっているので、いろんなケースに対してとても柔軟に対処できる。fluentd でのログの処理は、基本的には以下の様な流れになる。
- "入力プラグイン" でデータの fluentd への入力(収集)方式の設定と、"タグ付け" を行う
- "どんなタグを付けるか" という設定を入力プラグインに対して行う
- その付けられたタグに応じて、出力処理(プラグイン)を切り替えて出力する
- "このタグのとき、どの出力プラグインを用いるか" という設定を行う
fluentd において、タグ重要。タグによって入出力のルーティング・データフローの制御を行うイメージ。
fluentd
? td-agent
?
fluentd について勉強してるはずなのに、急に td-agent
なるものが fluentd 顔をして出てきたので面食らった。けど、これらふたつの違いについては、以下のようなことらしい。
なので、普通であれば td-agent
をインストールすればよさそう。
fluentd ことはじめ
概要がわかったところで、 実際に導入するとなったら具体的にどんなことをしなければいけないのか、と、そのだいたいの作業感をつかむ
ことができるように、具体的なところを見ていく。
インストールは、↓なかんじでできる。
curl -L http://toolbelt.treasuredata.com/sh/install-redhat-td-agent2.sh | sh
そして、/etc/td-agent/td-agent.conf
が設定ファイルになる。設定ファイルにはディレクティブを書くことで設定できるようなんだけど、主なディレクティブは下記の3種類。
<source>〜</source>
このディレクティブで、入力プラグインの指定を行う。type forward
のように書くと、in_forward
プラグインが使われる...というかんじ。プラグインごとに設定する項目がある場合も、ここに記述 することになる。
<match>〜</match>
このディレクティブで、出力プラグインの指定を行う。入力プラグインと同じく、type stdout
のように書くと、out_stdout
プラグインが使われる。
source ディレクティブと異なるのが、<match debug.test>
のように、その出力プラグインに振り分けるタグの指定を行わなければならない、というところ。ここで指定したタグにマッチした出力プラグインが使われる。タグでルーティング、と言われるゆえんがこれ、かな。このタグの指定、<match *.*>
のようにワイルドカードの指定や、<match debug.{test, development}>
のように {}
を使った OR 条件の指定も可能。
<system>〜</system>
このディレクティブで、fluentd のコア部分の動作(ログ出力周りの挙動)を決められる。例えば以下の様なもの。
log_level
suppress_repeated_stacktrace(連続した同一エラーの抑制)
emit_error_log_interval(指定時間内の同一エラー出力を抑制)
suppress_config_dump(起動時に設定ファイルの標準ログ出力を抑制)
これらは起動時のコマンドライン引数よりも優先される、とのこと。
td-agent.conf
の具体例
例えば、上で挙げたようなディレクティブを使って、このように↓書くことになる。
<source> type forward </source> <match debug.test> type stdout </match>
うーん、シンプル。in_forward
は単に forward するだけの入力プラグインで、out_stdout
は入力を標準出力するプラグイン。
これを手軽に確認するには、fluent-cat
というコマンドを下記のように使えば ok。
$ echo '{ "key" : "sample" }' | /usr/lib64/fluent/ruby/bin/fluent-cat debug.test
ちゃんと debug.test
というタグを指定してる。
もう少し複雑な td-agent.conf
次は少し複雑な cnf の例。"nginx のアクセスログを別のファイル(/tmp/test
)に書き出す" 場合。
<source> type tail tag nginx.access_log path /var/log/nginx/access.log # pos_file は、読み込んだ位置を記録するファイルを指定する。 # fluentd の再起動などがあっても、既に読み込んだ位置から続きを読み込んでくれる。 pos_file /var/log/td-agent/httpd-access.log.pos format (略) time_format %d/%b/%Y:%H:%M:%S %z </source> <match nginx.access_log> type file path /tmp/test </match>
in_tail
は、tail
コマンドよろしく、指定したファイルの一行一行を fluentd に入力するためのプラグイン。out_file
は入力をファイルに出力するためのプラグインになる。
これはこれでまぁいいと思うんだけど、仮にこれを複数台ある Web サーバに対して行うと、どのレコードがどのホストからのログかわかんなくなっちゃう。となると、各レコードにホスト名とかを付けたくなるんだけど、そういうときには外部プラグインである fluent-plugin-record-reformer
が有用。
外部のプラグインは fluent-gem install
コマンドを使えばインストールできる。 fluent-plugin-record-reformer
を使って各レコードにホスト名を付与する場合の td-agent.conf
は以下のようになる。
<source> type tail tag nginx.access_log path /var/log/nginx/access.log # pos_file は、読み込んだ位置を記録するファイルを指定する。fluentd の再起動などで、既に読み込んだ位置から続きを読み込んでくれる。 pos_file /var/log/td-agent/httpd-access.log.pos format (略) time_format %d/%b/%Y:%H:%M:%S %z </source> <match nginx.access_log> type record_reformer tag hostname.nginx.access_log <record> # メッセージの中に hostname の情報が追記される hostname ${hostname} </record> </match> <match hostname.nginx.access_log> (どっかのサーバに集約、とか) </match>
ここでひとつ整理する。入力プラグインからのひとつのイベント・メッセージは、 [ tag, time, record ]
という配列で構成され、これが fluentd のルーティングエンジンに送信されることになる。今回 fluent-plugin-record-reformer
によって付加されるホスト名の情報は、これでいうと record
の部分に、 { 'hostname' : 'web-01' }
みたいな形で追加されることになる。
また、"入力と出力の間で何かを行いたい" 場合、その前後関係をタグで調整する、というこのかんじは、fluentd での基本っぽいのでおさえておきたいところ。最終的にやりたいこと(ここだと "どっかのサーバに集約" )の前に、別のやりたいこと(ここだと "メッセージの中に hostname の情報を追記する")を挟んで、その際にタグを書き換えつつチェインさせていくかんじ。(今までは input -> nginx.access_log
だったのを、 input -> nginx.access_log(ここで hostname を追記する) -> hostname.nginx.access_log
にしてる。)
また、データソースが同じでも、違うタグを付ける <source>
を複数記述すれば、それぞれ別の出力プラグインを利用させることもできる、んだろな。
ことはじめとしては、こんなかんじ。
実際に使えるようにするためには
↓こんなかんじかな。
td-agent
をサーバにインストール- 外部プラグインを使うなら、それもインストール
td-agent.conf
に望みの設定を書く- アプリ側は、
td-agent.conf
に書いた通りになるようにタグを付けて fluentd に投げる- Rails だと、
fluent-logger
を使って投げる、のが一般的...なのかな? - 既に出力しているアプリケーションログファイルなどを読み込ませる場合は、
source
ディレクティブでタグの設定をすればいいはず
- Rails だと、
で、もし chef で構成管理しているようなら、上記のようなことができるような recipe を書く。と。
感想
これは確かにワクワクするプロダクトだ、と思った。たしかに楽そうだし幸せにもなれそうだなと。シンプルかつ高機能なのが、素直に凄いと思う。
あと、out_tail
プラグインとか特にそうだけど、今既にやっていることをジャマしない感じがすごくいい。「今吐き出してるログファイルありますよね、それウチでもちょっと見させてもらいますね!見るだけ、見るだけですから!」感。
あと、GCP ユーザーとしては、これはたしかに BigQuery と連携させたくなってくるね。