fluentd(td-agent)の設定を空でできるようにする
モチベーション
PRレビュー依頼でたまにfluentdの設定レビューをすることがあるが、その場でドキュメントを読むと効率や精度が悪い。業務効率化のために今一度復習して頻出の設定に関して空で書ける・読めるようにする。
fluentdの周辺知識を復習する
各種ディレクティブで使えるパラメータは公式ドキュメントを見た。全体像をつかむなら以下の本が分かりやすい。だいぶ前に読んだのを読み返した。
検証環境
本記事の検証環境構築用Vagrantfileと設定ファイルはここで管理している。以下の構成を作り、webで生成されたログをelasticsearchに突っ込む。webではnginxのアクセスログだけでなく、簡単なrubyスクリプトで生成されたログもfluentdでルーティングすることとする。
[ web ✕ 2 ]<-->[ aggregator ✕ 2 ]<-->[ elasticsearch ]
準備
共通設定
公式ドキュメントに従い、ファイルディスクリプタの上限を変更しておく。またホスト名やntpの設定など共通の設定をする。
$ ulimit -n 1024 $ sudo tee -a <<EOF /etc/security/limits.conf root soft nofile 65536 root hard nofile 65536 * soft nofile 65536 * hard nofile 65536 EOF $ sudo tee -a <<EOF /etc/hosts 192.168.33.10 web1 192.168.33.11 web2 192.168.33.20 aggregator1 192.168.33.21 aggregator2 192.168.33.30 elasticsearch EOF $ reboot
また、公式ドキュメントでは以下の設定をすることが推奨されているが、tcp_tw_recycle
を有効にすると同一ipからから同じタイミングでアクセスされる接続を破棄するようになるので、考えて設定した方がいい。というか基本的には必要ない気がする。
ntpをインストールして時刻を合わせる。
$ sudo apt-get install -y ntp
/etc/ntp.conf
の以下のコメント付近のサーバ情報をnictのサーバに書き換える。
# Use servers from the NTP Pool Project. Approved by Ubuntu Technical Board
# on 2011-02-08 (LP: #104525). See http://www.pool.ntp.org/join.html for
# more information.
server ntp.nict.jp
server ntp.nict.jp
server ntp.nict.jp
タイムゾーンをJSTに変更する。
vagrant@web1:~$ echo "Asia/Tokyo" | sudo tee /etc/timezone Asia/Tokyo vagrant@web1:~$ sudo dpkg-reconfigure --frontend noninteractive tzdata Current default time zone: 'Asia/Tokyo' Local time is now: Thu Aug 11 16:14:08 JST 2016. Universal Time is now: Thu Aug 11 07:14:08 UTC 2016.
nginxのインストール
web1、2にてnginxをインストールする。
sudo apt-get install -y nginx sudo chmod 755 /var/log/nginx
また、/etc/nginx/nginx.conf
にてnginxのアクセスログのフォーマットを以下のように変更しておく。
log_format ltsv 'status:$status\t'
'time:$time_iso8601\t'
'reqtime:$request_time\t'
'method:$request_method\t'
'uri:$request_uri\t'
'protocol:$server_protocol\t'
'ua:$http_user_agent\t'
'forwardedfor:$http_x_forwarded_for\t'
'host:$remote_addr\t'
'referer:$http_referer\t'
'server_name:$server_name\t'
'vhost:$host\t'
'size:$body_bytes_sent\t'
'reqsize:$request_length\t';
access_log /var/log/nginx/access.log ltsv;
error_log /var/log/nginx/error.log;
設定を反映させておく。
sudo service nginx reload
Elasticsearchのインストール
elasticsearchにて以下を実行する。
sudo apt-get update sudo apt-get -y install openjdk-7-jre curl -L -O https://download.elastic.co/elasticsearch/release/org/elasticsearch/distribution/tar/elasticsearch/2.3.4/elasticsearch-2.3.4.tar.gz tar -xvf elasticsearch-2.3.4.tar.gz
また解凍したディレクトリ内の//config/elasticsearch.yml
にてElasticsearchの待受ポートとアドレス、クラスタ名を変更する。
tee <<EOF cluster.name: my-application network.host: 192.168.33.30 # to use ipv4 http.port: 9200 EOF
最後にElasticsearchを起動する。
./elasticsearch-2.3.4/bin/elasticsearch
td-agentのインストール
web1,2,aggregator1,2にてapt repositoryを更新しつつ、td-agent[1]のインストールを行う。
curl -L https://toolbelt.treasuredata.com/sh/install-ubuntu-trusty-td-agent2.sh | sh
プラグインのインストール
td-agentが使うruby gemのインストールはtd-agent-gem install
を使う。
web1,2では以下を実行して、
sudo td-agent-gem install fluent-plugin-multiprocess sudo td-agent-gem install fluent-plugin-copy_ex
aggregator1,2では以下を実行する。
sudo td-agent-gem install fluent-plugin-copy_ex sudo td-agent-gem install fluent-plugin-elasticsearch
td-agentの設定おさらい
いよいよtd-agentの設定をしていくが、sourceディレクティブとmatchディレクティブを中心に書いていく。また、ディレクティブのなかでtypeパラメータ[2]でプラグインを指定する。それぞれのディレクティブでで良く使うプラグインをおさらいしておく。
sourceディレクティブ
データソースの設定をするディレクティブ。sourceディレクティブでメッセージにタグをつけてたり、fluentdの待受アドレス、ポートを指定して、matchディレクティブでタグベースでルーティング先の制御や様々な処理をするの一般的な流れ。
####プラグイン
in_tail
指定したフォーマットでログファイルに1行ログが追加された時にtagをつけたりするときに使う。
in_forward
他のfluentdプロセスやプログラムのメッセージを待ち受けるアドレスやポートを指定する。
in_multiprocess
上述の通り、configファイルを分けて別プロセスで実行させたいような時はこのプラグインを使う。td-agentを使うときはデフォルトではインストールされていないので自分でインストールする必要あり。
in_monitor_agent
各プラグインの処理状況や内部メトリック取得に使う。
matchディレクティブ
sourceディレクティブやプログラムでメッセージにつけたタグを使って、条件分岐を行うためのディレクティブ。wildcardを使った表記ができ、tag.*
はtag.subtag1
やtag.subtag2
には一致するが、tag.subtag.nested.tag
には一致しない。複数のドットをワイルドカードで表現した時はtag.**
のようにする。
プラグイン
out_forward
タグが一致したメッセージserverディレクティブで指定したホストにルーティングする。serverディレクティブは複数記載することでロードバランスできる。また、fluentはbuffer機構を実装しているがflush_interval
パラメータでbufferにメッセージを溜めておく時間を制御できる。デフォルトだと60秒になっている。
out_copy_ex
out_copyプラグインが以下のような設定の時、plugin1がエラーを吐いた時、plugin2は実行されない。
<match **>
type copy
<store>
type plugin1
</store>
<store>
type plugin2
</store>
</match>
copy_exを使うとignore_error
オプションをつけることで他のプラグインのエラーがによる実行中断を無視できる。
out_elaticsearch
Elasticsearchにfluentdでメッセージを送る時に使う。
out_relabel
以下のように内部のルーティング識別子のlabelを付与するときに使う。
<match pattern>
@type relabel
@label @foo
</match>
<label @foo>
<match patter>
...
</match>
</label>
labelディレクティブ
ラベルは内部でルーティングする情報を格納するのに使う識別子で上述のrelabelプラグインで付与されたlabelを使う。ラベルきのうについてはFluentd v0.12 ラベル機能の使い方とプラグインの改修方法 - Qiitaがわかりやすい。
デフォルトの設定を見る
td-agentはデフォルトで127.0.0.1:8888に対するhttpリクエストをモニタリングしており以下を実行すると、td-agent.logにログが残る。
curl -X POST -d 'json={"json":"message"}' http://localhost:8888/debug.test
また以下のようにdebug用のport、24230が127.0.0.1でオープンになっており、fluent-debugコマンドやirbからDRbObjectを生成してfluentdのプロセスにアタッチできる。
<source>
@type debug_agent
bind 127.0.0.1
port 24230
</source>
td-agentのルーティング設定をする
web1,2の設定
nginxのログとapplicationのログをそれぞれ別のtd-agentプロセスで監視するため、multiprocessを使う。
sudo tee <<EOF /etc/td-agent/td-agent.conf <source> type multiprocess <process> cmdline -c /etc/td-agent/conf.d/nginx_access.conf --log /var/log/td-agent/nginx_access.log sleep_before_start 1s sleep_before_shutdown 5s </process> <process> cmdline -c /etc/td-agent/conf.d/app.conf --log /var/log/td-agent/app.log sleep_before_start 1s sleep_before_shutdown 5s </process> </source> EOF
集約ノードにnginxへのリクエストを送信する設定をする。nginxで設定しているtime:
の値は自動でパースされないので、自分でtime_formatを設定して上げる必要ありClass: Time (Ruby 1.9.3)
sudo mkdir -p /etc/td-agent/conf.d sudo tee <<EOF /etc/td-agent/conf.d/nginx_access.conf #### ## Source descriptions: ## <source> @type tail tag log.nginx.web format ltsv time_format %FT%T%:z path /var/log/nginx/access.log pos_file /var/log/td-agent/ngix_access_log.pos </source> <source> @type forward port 24225 </source> #### ## Output descriptions: ## <match **> @type copy_ex <store ignore_error> type relabel @label @nginx </store> <store ignore_error> type relabel @label @stdout </store> </match> <label @nginx> <match log.nginx.*> @type forward flush_interval 1s <server> host aggregator1 port 24224 </server> <server> host aggregator2 port 24224 standby </server> </match> </label> <label @stdout> <match log.nginx.*> @type stdout </match> </label> EOF
同様にapplication監視用のプロセスの設定をする。
sudo tee <<EOF /etc/td-agent/conf.d/app.conf #### ## Source descriptions: ## <source> @type forward port 24224 </source> #### ## Output descriptions: ## <match **> @type copy_ex <store ignore_error> type relabel @label @app </store> <store ignore_error> type relabel @label @stdout </store> </match> <label @app> <match app.*> @type forward flush_interval 1s <server> host aggregator1 port 24224 </server> <server> host aggregator2 port 24224 standby </server> </match> </label> <label @stdout> <match app.*> @type stdout </match> </label> EOF
さらにプログラムからログをはきださせるためにfluent-logger
のgemをインストールして、
sudo apt-get install -y ruby-dev #mkmfをインストールする sudo gem install fluent-logger
以下のスクリプトを適当に配置する。
require 'fluent-logger'
log = Fluent::Logger::FluentLogger.new('app', :host=>'localhost', :port=>24224)
hostname = `hostname`.strip
log.post("#{hostname}", {"message_from"=>"#{hostname}","message"=>"foo"})
log.post("#{hostname}", {"message_from"=>"#{hostname}","message"=>"bar"})
log.post("#{hostname}", {"message_from"=>"#{hostname}","message"=>"baz"})
aggregator1,2の設定
sudo tee <<EOF /etc/td-agent/td-agent.conf <source> @type forward port 24224 </source> <source> type monitor_agent bind 0.0.0.0 port 24220 </source> #### ## Output descriptions: ## <match **> @type copy_ex <store ignore_error> type relabel @label @elasticsearch </store> <store ignore_error> type relabel @label @stdout </store> </match> <label @elasticsearch> <match log.nginx.*> @type copy_ex <store ignore_error> type elasticsearch @id log_nginx_id host elasticsearch port 9200 index_name log_nginx_idx type_name log_nginx_type flush_interval 1 </store> <store ignore_error> type file @id log_nginx_id path /tmp/fluent-nginx-stdout time_slice_format %Y%m%d time_slice_wait 10m time_format %Y%m%dT%H%M%S%z compress gzip </store> </match> <match app.*> @type copy_ex <store ignore_error> type elasticsearch @id app_id host elasticsearch port 9200 index_name app_idx type_name app_type flush_interval 1 </store> <store ignore_error> type file @id app_id path /tmp/fluent-app-stdout time_slice_format %Y%m%d time_slice_wait 10m time_format %Y%m%dT%H%M%S%z compress gzip </store> </match> </label> <label @stdout> <match log.nginx.*> @type stdout </match> <match app.*> @type stdout </match> </label>
動作確認
webにてrubyスクリプトを数回実行したり、ブラウザから192.168.33.10
と192.168.33.11
にアクセスしてからwebにてログを確認する。
#web1にて tail -n 6 /var/log/td-agent/nginx_access.log /var/log/td-agent/app.log ==> /var/log/td-agent/nginx_access.log <== 2016-08-15 03:18:24 +0900 log.nginx.web: {"status":"304","reqtime":"0.000","method":"GET","uri":"/","protocol":"HTTP/1.1","ua":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36","forwardedfor":"-","host":"192.168.33.1","referer":"-","server_name":"localhost","vhost":"192.168.33.10","size":"0","reqsize":"776"} 2016-08-15 03:20:54 +0900 log.nginx.web: {"status":"304","reqtime":"0.000","method":"GET","uri":"/","protocol":"HTTP/1.1","ua":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36","forwardedfor":"-","host":"192.168.33.1","referer":"-","server_name":"localhost","vhost":"192.168.33.10","size":"0","reqsize":"776"} 2016-08-15 03:20:56 +0900 log.nginx.web: {"status":"304","reqtime":"0.000","method":"GET","uri":"/","protocol":"HTTP/1.1","ua":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36","forwardedfor":"-","host":"192.168.33.1","referer":"-","server_name":"localhost","vhost":"192.168.33.10","size":"0","reqsize":"776"} 2016-08-15 03:20:58 +0900 log.nginx.web: {"status":"304","reqtime":"0.000","method":"GET","uri":"/","protocol":"HTTP/1.1","ua":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36","forwardedfor":"-","host":"192.168.33.1","referer":"-","server_name":"localhost","vhost":"192.168.33.10","size":"0","reqsize":"776"} 2016-08-15 03:23:04 +0900 log.nginx.web: {"status":"200","reqtime":"0.000","method":"GET","uri":"/","protocol":"HTTP/1.1","ua":"curl/7.35.0","forwardedfor":"-","host":"127.0.0.1","referer":"-","server_name":"localhost","vhost":"localhost","size":"612","reqsize":"73"} 2016-08-15 03:23:05 +0900 log.nginx.web: {"status":"200","reqtime":"0.000","method":"GET","uri":"/","protocol":"HTTP/1.1","ua":"curl/7.35.0","forwardedfor":"-","host":"127.0.0.1","referer":"-","server_name":"localhost","vhost":"localhost","size":"612","reqsize":"73"} ==> /var/log/td-agent/app.log <== 2016-08-15 03:18:33 +0900 app.web1: {"message_from":"web1","message":"foo"} 2016-08-15 03:18:33 +0900 app.web1: {"message_from":"web1","message":"bar"} 2016-08-15 03:18:33 +0900 app.web1: {"message_from":"web1","message":"baz"} 2016-08-15 03:18:34 +0900 app.web1: {"message_from":"web1","message":"foo"} 2016-08-15 03:18:34 +0900 app.web1: {"message_from":"web1","message":"bar"} 2016-08-15 03:18:34 +0900 app.web1: {"message_from":"web1","message":"baz"}
aggregator1にてログを確認する。
tail /var/log/td-agent/td-agent.log -n 10 2016-08-15 03:18:33 +0900 app.web1: {"message_from":"web1","message":"bar"} 2016-08-15 03:18:33 +0900 app.web1: {"message_from":"web1","message":"baz"} 2016-08-15 03:18:34 +0900 app.web1: {"message_from":"web1","message":"foo"} 2016-08-15 03:18:34 +0900 app.web1: {"message_from":"web1","message":"bar"} 2016-08-15 03:18:34 +0900 app.web1: {"message_from":"web1","message":"baz"} 2016-08-15 03:20:54 +0900 log.nginx.web: {"status":"304","reqtime":"0.000","method":"GET","uri":"/","protocol":"HTTP/1.1","ua":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36","forwardedfor":"-","host":"192.168.33.1","referer":"-","server_name":"localhost","vhost":"192.168.33.10","size":"0","reqsize":"776"} 2016-08-15 03:20:56 +0900 log.nginx.web: {"status":"304","reqtime":"0.000","method":"GET","uri":"/","protocol":"HTTP/1.1","ua":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36","forwardedfor":"-","host":"192.168.33.1","referer":"-","server_name":"localhost","vhost":"192.168.33.10","size":"0","reqsize":"776"} 2016-08-15 03:20:58 +0900 log.nginx.web: {"status":"304","reqtime":"0.000","method":"GET","uri":"/","protocol":"HTTP/1.1","ua":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36","forwardedfor":"-","host":"192.168.33.1","referer":"-","server_name":"localhost","vhost":"192.168.33.10","size":"0","reqsize":"776"} 2016-08-15 03:23:04 +0900 log.nginx.web: {"status":"200","reqtime":"0.000","method":"GET","uri":"/","protocol":"HTTP/1.1","ua":"curl/7.35.0","forwardedfor":"-","host":"127.0.0.1","referer":"-","server_name":"localhost","vhost":"localhost","size":"612","reqsize":"73"} 2016-08-15 03:23:05 +0900 log.nginx.web: {"status":"200","reqtime":"0.000","method":"GET","uri":"/","protocol":"HTTP/1.1","ua":"curl/7.35.0","forwardedfor":"-","host":"127.0.0.1","referer":"-","server_name":"localhost","vhost":"localhost","size":"612","reqsize":"73"}
elasticsearchにてapplicationログを検索する。
curl -XGET ‘http://192.168.33.30:9200/app_idx/app_type/_search?q=message_from:web1&pretty' { “took” : 9, “timed_out” : false, “_shards” : { “total” : 5, “successful” : 5, “failed” : 0 }, “hits” : { “total” : 9, “max_score” : 0.81767845, “hits” : [ { “_index” : “app_idx”, “_type” : “app_type”, “_id” : “AVaKQbY06Dyiup5srrKi”, “_score” : 0.81767845, “_source” : { “message_from” : “web1”, “message” : “foo” } }, { “_index” : “app_idx”, “_type” : “app_type”, “_id” : “AVaKQbY06Dyiup5srrKj”, “_score” : 0.81767845, “_source” : { “message_from” : “web1”, “message” : “bar” } }, { “_index” : “app_idx”, “_type” : “app_type”, “_id” : “AVaKQbmX6Dyiup5srrKm”, “_score” : 0.81767845, “_source” : { “message_from” : “web1”, “message” : “bar” } }, { “_index” : “app_idx”, “_type” : “app_type”, “_id” : “AVaKQb2C6Dyiup5srrKp”, “_score” : 0.81767845, “_source” : { “message_from” : “web1”, “message” : “bar” } }, { “_index” : “app_idx”, “_type” : “app_type”, “_id” : “AVaKQb2C6Dyiup5srrKq”, “_score” : 0.81767845, “_source” : { “message_from” : “web1”, “message” : “baz” } }, { “_index” : “app_idx”, “_type” : “app_type”, “_id” : “AVaKQbY06Dyiup5srrKk”, “_score” : 0.5945348, “_source” : { “message_from” : “web1”, “message” : “baz” } }, { “_index” : “app_idx”, “_type” : “app_type”, “_id” : “AVaKQbmX6Dyiup5srrKl”, “_score” : 0.5945348, “_source” : { “message_from” : “web1”, “message” : “foo” } }, { “_index” : “app_idx”, “_type” : “app_type”, “_id” : “AVaKQb2C6Dyiup5srrKo”, “_score” : 0.30685282, “_source” : { “message_from” : “web1”, “message” : “foo” } }, { “_index” : “app_idx”, “_type” : “app_type”, “_id” : “AVaKQbmX6Dyiup5srrKn”, “_score” : 0.30685282, “_source” : { “message_from” : “web1”, “message” : “baz” } } ] } }
最後にelasticsearchにてnginxログを検索する。
curl -XGET 'http://192.168.33.30:9200/log_nginx_idx/log_nginx_type/_search?q=status:200&pretty' { "took" : 7, "timed_out" : false, "_shards" : { "total" : 5, "successful" : 5, "failed" : 0 }, "hits" : { "total" : 2, "max_score" : 1.4054651, "hits" : [ { "_index" : "log_nginx_idx", "_type" : "log_nginx_type", "_id" : "AVaKRclU6Dyiup5srrK0", "_score" : 1.4054651, "_source" : { "status" : "200", "reqtime" : "0.000", "method" : "GET", "uri" : "/", "protocol" : "HTTP/1.1", "ua" : "curl/7.35.0", "forwardedfor" : "-", "host" : "127.0.0.1", "referer" : "-", "server_name" : "localhost", "vhost" : "localhost", "size" : "612", "reqsize" : "73" } }, { "_index" : "log_nginx_idx", "_type" : "log_nginx_type", "_id" : "AVaKRcGC6Dyiup5srrKz", "_score" : 1.0, "_source" : { "status" : "200", "reqtime" : "0.000", "method" : "GET", "uri" : "/", "protocol" : "HTTP/1.1", "ua" : "curl/7.35.0", "forwardedfor" : "-", "host" : "127.0.0.1", "referer" : "-", "server_name" : "localhost", "vhost" : "localhost", "size" : "612", "reqsize" : "73" } } ] } }
ちゃんと動作している。とりあえず、空で読み書きできるようになった。
参考
fluent関連
- fluentdで始めるログ管理【フォワード設定まとめ】 | Tech-Sketch
- いまさらだけど、Nginx が出力したアクセスログ(ltsv形式)を fluentd 経由で BigQuery に送ってみたよ - えいのうにっき
- Fluentdの設定を考えるときはこんなかんじで考えると便利 - Qiita
rubyのインストール
[1] fluentdのインストールは、td-agentをインストールすることで行う。td-agentはfluentd配布用のパッケージで、ruby、fluentd、推奨設定、fluentd pluginが含まれている。
[2] ディレクティブ内でtypeと@typeを使っているケースをしばしば見かけるが、もともとtypeやidを使っていたがシステムとpluginの設定でコンフリクトを避けるため、現在はにシステムの設定では@プレフィックスパラメータを付与して@typeや@idのように記述するのが推奨されている