20160815_Fluentd_horizontal

モチベーション

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.subtag1tag.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.10192.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関連

rubyのインストール

[1] fluentdのインストールは、td-agentをインストールすることで行う。td-agentはfluentd配布用のパッケージで、ruby、fluentd、推奨設定、fluentd pluginが含まれている。

[2] ディレクティブ内でtypeと@typeを使っているケースをしばしば見かけるが、もともとtypeやidを使っていたがシステムとpluginの設定でコンフリクトを避けるため、現在はにシステムの設定では@プレフィックスパラメータを付与して@typeや@idのように記述するのが推奨されている