Docker Swarm内の分散したログをFluentdでまとめてElasticsearchとKibanaでログを確認する

概要

Docker Swarmで複数のコンテナがあると、コンテナが起動している複数のnodeに入ってdocker logsは大変です。2台や3台程度であればdocker logsで十分かもしれませんが、5台やそれ以上になると大変です。
また、一番大きい問題としてコンテナのログはapplication imageを更新するたびに揮発しまうことがあります。

揮発対策のためにtaskを実行しているホストマシン上にファイル出力する方法もありますが、分散したnodeに入ってログを確認しなければいけない問題が解決しません。
そのため、Docker Swarmで複数のコンテナのログを一箇所に収集するようにします。

また一箇所に集めたログをlessなどで見てもいいですが、APIアクセスやエラーの統計を取れるようにしたいので、ElasticsearchとKibanaを使用します。

全体の流れは以下のようになります。

Docker container -> Fluentd -> Elasticsearch -> Kibana

  1. Docker containerからFluentdにログを送信
  2. FluentdでElasticsearchにログを送信するようにする。ここでElasticsearchのフォーマットに合わせてログを変換
  3. Kibanaでログを確認

Docker containerからFluentdにログを送信する

Dockerサービスを作成時に--log-driver=fluentdでFluentdに出力するように指定します。Dockerは標準でFluentdをサポートしています。次に--log-opt fluentd-address=127.0.0.1:24224で送信先とポートを指定します。--log-opt tag='developer_center.clientでFluentdのタグを指定します。

docker service create --replicas 1 --name api --log-driver=fluentd --log-opt fluentd-address=127.0.0.1:24224 --log-opt tag='developer_center.api' api

FluentdからElasticsearchにログを加工して送信する

Fluentdは0.12を使用しています。

NginxのログをElasticsearchに送信してみます。

Docker containerから送信されたログは以下のようにJSON形式になっています。

2017-07-19 11:54:27,165 [application-package-actor-7] INFO  application - make directories: mkdir -p /myapp/example/124223/50001

以下はFluentdでアプリケーションのログをパースしてElasticsearchに送信する設定です。

<filter application>
  @type parser
  format /^(?<time>.*) \[(?<thread>.*)\] (?<level>[\w]*)\s+(?<class>[\w.]*) - (?<message>.*)|\s*(?<system_message>.*)$/
  time_format %Y-%m-%d %H:%M:%S,%L
  key_name log
  keep_time_key true
  reserve_data true
</filter>

<filter application>
  @type record_transformer
  enable_ruby true
  <record>
    timestamp ${ require 'time'; Time.now.utc.iso8601(3) }
  </record>
</filter>

<match application>
  @type copy
  <store>
    @type elasticsearch
    host sample-s18kfo2pa387njeiowbf9km4oe38by.ap-northeast-1.es.amazonaws.com
    port 80
    logstash_format true
    logstash_prefix application
    time_key timestamp
    buffer_type file
    buffer_path /dev/shm/fluentd_buffer/application.buf
    buffer_queue_limit 128
    buffer_chunk_limit 32m
    flush_interval 1s
  </store>
  <store>
    @type file_alternative
    path /home/fluentd/logs/application.log
    time_slice_format %Y%m%d%H00
    time_format %Y-%m-%dT%H:%M:%SZ
    buffer_type file
    buffer_path /dev/shm/fluentd_buffer/application.buf
    buffer_queue_limit 64
    buffer_chunk_limit 32m
    flush_interval 1s
    flush_at_shutdown
    add_newline true
  </store>
</match>

最初のfilterではアプリケーションのログをパースします。formatはパースするフォーマットです。ここではアプリケーションのログフォーマットに合わせています。time_formatも同じように時刻のフォーマットを合わせています。key_name logはどのJSONのkeyをパース対象にするか指定します。ここでは log を対象にしています。reserve_data truekey_name logで指定した元のログをパース後も保持するか指定します。 false にするとkeyが log のレコードが削除されます。

2番目のfilterではパースしたレコードにElasticsearchで使用するタイムスタンプを追加しています。タイムスタンプはすでにあるのですが、Fluentd 0.12はミリ秒に対応していないので、ここでrubyのTimeを使ってログを処理した時刻にミリ秒を含めたタイプムスタンプをレコードに追加しています。

matchではElasticsearchに送信する設定とローカルディレクトリにログを保存する設定です。

1番目の<store>はElasticsearchに送信する設定です。ここでElasticsearchに送信するための設定としてhostport指定しています。次にlogstash_formatlogstash_prefixを指定しています。logstash_formatはKibanaで加工しやすいようにインデックスを付けます。logstash_prefixではインデックスのprefixを指定します。Kibanaでインデックスを指定してログを見ることになるのでわかりやすい名前をつけます。上記の設定ではclient-2017.01.01のようなインデックスが作成されます。また、time_key timestampfilterで追加したタイムスタンプを使用するようにkeyを指定しています。

2番目の<store>はローカルディレクトリにログを保存する設定です。
詳しい設定はこちらを参照してください。

ElasticsearchとKibanaを準備する

ElasticsearchとKibanaを使用するにはAWSで提供されているAmazon Elasticsearch Serviceを使用しています。インスタンスを立ち上げるだけで使用できるようになるのでとても簡単です。もちろん、Elasticsearchにログを流す前にインスタンスを立ち上げておく必要があります。

Kibanaでログを確認する

Kibana上で、Management -> Index Patterns -> [Add New]でindex patternを指定します。

Index name or patternにはlogstash_prefixで指定した値を入力します。
正しくログが入っているとTime-field name@taimestampを選択できるようになるので、選択して[Create]します。

あとは、Discover から先ほど登録したindex patternを指定すればログを確認できるようになります。

課題

複数行になるログ(スタックトレースなど)をKibanaで見るとタイムスタンプが同じになってしまい、本来の行とは異なった並びになってしまうことがあります。複数行のログをうまく一つにまとめられるとログが見やすくなると思います。何か方法はないかな…

Leave a Reply

Your email address will not be published. Required fields are marked *