Amazon Elasticsearch Serviceにfluentdでnginxのアクセスログを流し込んでKibanaで可視化する。

splapiのアクセスログの可視化を始めてみた。 – return $lock;

導入にめちゃくちゃ苦労してしまったので、後で記事にしようと思います。

記事にしました。

前置き

Kibanaで可視化するにあたってnginxのアクセスログをltsv形式にしてfluentdで送出しています。
導入当初はt2.microインスタンス(Amazon EC2の無料枠)をログ収集サーバ(fluentd→elasticsearch)にした上で、Kibana 4を動かしていたのですが、以下の問題がありほぼまともに運用出来てない状態でした。

  • kibanaがリソース不足(多分)で5時間と起動し続けられない
  • elasticsearchがリソース不足(多分)で2日くらいすると落ちる(1日大体10万ドキュメント程度流し込まれる)
  • elasticsearchが起動直後に即落ちするようになる。(多分リソース不足)
  • fluentdが起動直後に即落ちするようになる。(多分リソース不足)

Kibanaのほうは使うたびに起動させればいいのでとりあえずどうにかなってたのですが、elasticsearchのほうがチューニングしても全く安定しない状態でした。(スワップ1GB増やした状態でヒープ固定で512mにしてたけどだめだった。)

ちなみに当初の構成はこんな感じでした。

昔の構成
昔の構成

困ったので、EC2の無料枠でどこまで出来るのか調べるのに「AWS クラウドサービス無料利用枠 | AWS」を見ていたらすごいことに気づきました。

ESには無料枠がある。
ESには無料枠がある。

Elasticsearch Service、無料枠あるのかよ!!!!!!(ないと思ったからEC2使ってた。)Prime Musicでキリンジのアルバムとファンタビジョンのサントラが聴けるのを知った時くらい驚きました。情報弱者はこういうところで世間と差がついていくのですね。
とりあえず構成を以下に変更して、Elasticsearch Service (ES)を使うことにしました。

新しい構成。
新しい構成。

今後のことを考えると、APPサーバとESの間にAPPサーバとは別の環境で動く収集用のfluentdサーバを置くべきなんだろうなとは思うのですが、elasticsearchが動かなくなったEC2インスタンスのfluentdも何故か即落ちしてしまうので諦めました。完全にインスタンスまるごとぶっ壊したわこれ。(インスタンスは廃棄した)

ログ送信側の設定

環境は以下。

  • OS: CentOS 7
  • fluentd: td-agent 0.12.20

nginxのログ設定

    log_format  ltsv  'time:$time_localt'
                      'host:$remote_addrt'
                      'request:$requestt'
                      'status:$statust'
                      'size:$body_bytes_sentt'
                      'referer:$http_referert'
                      'ua:$http_user_agentt'
                      'reqtime:$request_timet'
                      'upsttime:$upstream_response_time';

どこかの設定を参考にした気がするのですが失念しました…。Labeled Tab-separated Values (LTSV)に”Recommendations for labeling”の項目があるのを後から知ったのですが、もうelasticsearchにドキュメント流し込みまくってて手遅れな段階だったのでこのままの運用でいくことにしました。
注意点として、td-agentの実行ユーザ(デフォルトはtd-agent)がnginxのログを読めるようにパーミッション周りを変更しておく必要があります。logrotateを設定しているなら、logrotateの出力設定も変更しておかないと、ローテートしたタイミングでtd-agentがログを読めなくなってしまいます。(設定忘れててはまった。)

fluentd(td-agent)と、使用するプラグインのインストール

Installing Fluentd Using rpm Package | Fluentdを参照しています。

$sudo curl -L https://toolbelt.treasuredata.com/sh/install-redhat-td-agent2.sh | sh
$sudo td-agent-gem install fluent-plugin-elasticsearch

fluentdの設定

tailプラグインからログをltsv形式で読み込んだ後、数値・時刻部分をキャストしてelasticsearchに送出しています。キャスト周りはtail Input Plugin | Fluentdを参考にしています。

<source>
    type tail
    path /var/log/nginx/xxx/access.log
    pos_file /var/log/td-agent/buffer/api.access.log.pos
    format ltsv
    time_format %d/%b/%Y:%H:%M:%S %z
    types size:integer, status:integer, reqtime:float, upsttime:float, time:time
    tag nginx.access
</source>

<match nginx.access>
    type_name nginx
    type elasticsearch
    include_tag_key true
    tag_key @log_name
    host search-elasticsearch-(>_<).amazonaws.com
    port 443
    scheme https
    logstash_format true
    flush_interval 10s
    retry_limit 5
    buffer_type file
    buffer_path /var/log/td-agent/buffer/casted.nginx.access.buffer
    ##############################################################
    #!!! DO NOT SET THIS PROPERTY TO true FOR Amazon Elasticsearch Service !!!
    #Elasticsearch service doesn't return addresses of nodes and Elasticsearch client
    #fails to reload connections properly.
    ############################################################
    reload_connections false
</match>

DO NOT SET…の部分ですが、設定しないと以下エラーが発生してelasticsearchにアクセスできなくなります。

  2016-06-13 10:03:04 +0900 [warn]: temporarily failed to flush the buffer. next_retry=2016-06-13 10:03:05 +0900 error_class="Elasticsearch::Transport::Transport::Error" error="Cannot get new connection from pool." plugin_id="object:3fda5fb49af0"
  2016-06-13 10:03:04 +0900 [warn]: /opt/td-agent/embedded/lib/ruby/gems/2.1.0/gems/elasticsearch-transport-1.0.15/lib/elasticsearch/transport/transport/base.rb:193:in `perform_request'
  2016-06-13 10:03:04 +0900 [warn]: /opt/td-agent/embedded/lib/ruby/gems/2.1.0/gems/elasticsearch-transport-1.0.15/lib/elasticsearch/transport/transport/http/faraday.rb:20:in `perform_request'
  2016-06-13 10:03:04 +0900 [warn]: /opt/td-agent/embedded/lib/ruby/gems/2.1.0/gems/elasticsearch-transport-1.0.15/lib/elasticsearch/transport/client.rb:125:in `perform_request'
  2016-06-13 10:03:04 +0900 [warn]: /opt/td-agent/embedded/lib/ruby/gems/2.1.0/gems/elasticsearch-api-1.0.15/lib/elasticsearch/api/actions/bulk.rb:87:in `bulk'
  2016-06-13 10:03:04 +0900 [warn]: /opt/td-agent/embedded/lib/ruby/gems/2.1.0/gems/fluent-plugin-elasticsearch-1.5.0/lib/fluent/plugin/out_elasticsearch.rb:278:in `send'
  2016-06-13 10:03:04 +0900 [warn]: /opt/td-agent/embedded/lib/ruby/gems/2.1.0/gems/fluent-plugin-elasticsearch-1.5.0/lib/fluent/plugin/out_elasticsearch.rb:271:in `write'
  2016-06-13 10:03:04 +0900 [warn]: /opt/td-agent/embedded/lib/ruby/gems/2.1.0/gems/fluentd-0.12.20/lib/fluent/buffer.rb:345:in `write_chunk'
  2016-06-13 10:03:04 +0900 [warn]: /opt/td-agent/embedded/lib/ruby/gems/2.1.0/gems/fluentd-0.12.20/lib/fluent/buffer.rb:324:in `pop'
  2016-06-13 10:03:04 +0900 [warn]: /opt/td-agent/embedded/lib/ruby/gems/2.1.0/gems/fluentd-0.12.20/lib/fluent/output.rb:329:in `try_flush'
  2016-06-13 10:03:04 +0900 [warn]: /opt/td-agent/embedded/lib/ruby/gems/2.1.0/gems/fluentd-0.12.20/lib/fluent/output.rb:140:in `run'

このエラーについての詳細は以下。
Elasitcsearch-ruby raises “Cannot get new connection from pool” error – Elasticsearch – Discuss Elasticsearch, Logstash and Kibana | Elastic
ええ…バグじゃねえのこれ…。

pos_fileとbuffer_pathの出力先は予め作成しておく必要がありました。詳しくはtailプラグインのドキュメントとelasticsearchプラグインのドキュメントを参照してください。

ログ受信側の設定

[新機能]Amazon Elasticsearch Serviceがリリースされました! | Developers.IO
この記事通りにセットアップすると10分後くらいに自動的にelasticsearchとkibana 4が使えるようになっています。すごいですね。ただし、無料枠で使用する場合、t2.microかつ、Single-AZ構成にする必要があります。
注意点としては、アクセスポリシーを再変更すると、ステータスがActiveになるのに10分くらいかかるのが結構罠です。(自分の端末のIPアドレス許可するの忘れてKibanaにアクセスできなくなったので変更した。)
私はIPベースでアクセス許可していますが、IAMベースでアクセスコントロールする場合、fluent-plugin-elasticsearchは使用できず、fluent-plugin-aws-elasticsearch-serviceを使用する必要があります。
fluent-plugin-aws-elasticsearch-service 導入時にハマった箇所 – Qiita
これ見て「めんどくさそう」という感想しか湧いてこなかったのでIPベースでいいやと思いました。
ちなみにindex名がlogstash-YYYY.MM.DD固定になる問題の回避策は上記Qiitaに記載があります。私は特に困ってないのでそのまま運用しました。

マッピングの設定

このまま受信するとUserAgentやリファラが謎文節で区切られてすこぶるわかりづらくなるので、お好みに応じてマッピング用のjsonをPUTする必要があります。私はこんな感じで設定しました。

{
   "template":"logstash-*",
   "mappings":{
      "nginx":{
         "properties":{
            "host":{
               "index":"not_analyzed",
               "type":"string"
            },
            "request":{
               "index":"not_analyzed",
               "type":"string"
            },
            "ua":{
               "index":"not_analyzed",
               "type":"string"
            },
            "referer":{
               "index":"not_analyzed",
               "type":"string"
            }
         }
      }
   }
}
curl -X PUT https://search-elasticsearch-(>_<).amazonaws.com/_template/xxx --data @template.json

Kibanaで可視化した結果

splapiの1日のアクセスログのDiscover。予想してたけどステージ変更時はアクセス多くなりますね。

一日のアクセス。
一日のアクセス。