えいのうにっき

主に Web 系技術ネタ。背景画像 is powered by grass-graph.moshimo.works

いまさらだけど fluentd に入門した

仕事で僕が担当してるプロダクトでも、「そろそろ fluentd を…」という話題が出始めた。

社内の別プロダクトでは既に導入されてるものもあったりして、たぶん会社的にはノウハウも溜まり始めてきてるとは思うんだけど、なにせ僕自身が fluentd について雰囲気しかわかってないままだったので、それじゃイカンなと、いまさらだけどようやく fluentd に入門した。そのついでにメモった内容を晒す。

入門前のイメージ

  • みんな「アプリは何も考えず、とりあえず fluentd に投げとけば幸せになれる」って言ってる
  • なので、そういう感じの存在なんだろうな fluentd は、くらいの理解しかない
    • 具体的にどう幸せになるのかはあんまり想像が付いてない
  • “d” がついてるので、デーモン的な何かかな?
  • ロゴがイカしてる くコ:彡

f:id:a-know:20150613171659p:plain

ゴール

  • 実際に導入するとなったら具体的にどんなことをしなければいけないのか、と、そのだいたいの作業感をつかむ
  • 導入済みプロダクトのエンジニアと基本的な会話をできるようになる

何を使って入門したか

会社にあった下記2冊の本の該当箇所を読んだ。

  • Web エンジニアの教科書
  • サーバ/インフラエンジニア 養成読本

Webエンジニアの教科書

Webエンジニアの教科書

入門してわかったこと

fluentd とは

fluentd は “ログ集約ソフトウェア” である、と、きっぱりと書かれていた。でも、"ログ" って言い切ってるけど、slack や Twillio などへの出力も後述するプラグインで対応できるので、広義での “ログ"、と思ったほうがよさそう(いわゆるアプリケーションログだけではなく)。"データから価値を創造するコストを最小限にするミドルウェア” なんていうカッコいい書かれ方もしてた。

"d" がついてるので、デーモン的な何かかな? って書いたけど、これはあたってた。このデーモンに対して入力を行うと、設定された通りに出力が行われる。というイメージ。アプリケーションは、今までであれば出力先ごとにそれぞれ実装をしなければならなかったものを、fluentd への出力だけ考えれば良くなる。受けた情報ごとにどのように出力するか、は、fluentd への設定だけでまかなえる。…なるほどこれは確かに幸せになれそう。

fluentd への入力、fluentd からの出力について

fluentd に対するログの入力・出力処理の部分はプラグインで設定できるようになっているので、いろんなケースに対してとても柔軟に対処できる。fluentd でのログの処理は、基本的には以下の様な流れになる。

  1. “入力プラグイン” でデータの fluentd への入力(収集)方式の設定と、"タグ付け“ を行う
  2. どんなタグを付けるか” という設定を入力プラグインに対して行う
  3. その付けられたタグに応じて、出力処理(プラグイン)を切り替えて出力する
  4. このタグのとき、どの出力プラグインを用いるか” という設定を行う

fluentd において、タグ重要。タグによって入出力のルーティング・データフローの制御を行うイメージ。

fluentdtd-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> を複数記述すれば、それぞれ別の出力プラグインを利用させることもできる、んだろな。

ことはじめとしては、こんなかんじ。

実際に使えるようにするためには

↓こんなかんじかな。

  1. td-agent をサーバにインストー
  2. 外部プラグインを使うなら、それもインストー
  3. td-agent.conf に望みの設定を書く
  4. アプリ側は、td-agent.conf に書いた通りになるようにタグを付けて fluentd に投げる
    • Rails だと、fluent-logger を使って投げる、のが一般的…なのかな?
    • 既に出力しているアプリケーションログファイルなどを読み込ませる場合は、source ディレクティブでタグの設定をすればいいはず

で、もし chef で構成管理しているようなら、上記のようなことができるような recipe を書く。と。

感想

これは確かにワクワクするプロダクトだ、と思った。たしかに楽そうだし幸せにもなれそうだなと。シンプルかつ高機能なのが、素直に凄いと思う。

あと、out_tail プラグインとか特にそうだけど、今既にやっていることをジャマしない感じがすごくいい。「今吐き出してるログファイルありますよね、それウチでもちょっと見させてもらいますね!見るだけ、見るだけですから!」感。

あと、GCP ユーザーとしては、これはたしかに BigQuery と連携させたくなってくるね。

関連エントリ

blog.a-know.me

blog.a-know.me

blog.a-know.me



follow us in feedly