概要
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
- Docker containerからFluentdにログを送信
- FluentdでElasticsearchにログを送信するようにする。ここでElasticsearchのフォーマットに合わせてログを変換
- 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 true
はkey_name log
で指定した元のログをパース後も保持するか指定します。 false にするとkeyが log のレコードが削除されます。
2番目のfilter
ではパースしたレコードにElasticsearchで使用するタイムスタンプを追加しています。タイムスタンプはすでにあるのですが、Fluentd 0.12はミリ秒に対応していないので、ここでrubyのTime
を使ってログを処理した時刻にミリ秒を含めたタイプムスタンプをレコードに追加しています。
match
ではElasticsearchに送信する設定とローカルディレクトリにログを保存する設定です。
1番目の<store>
はElasticsearchに送信する設定です。ここでElasticsearchに送信するための設定としてhost
とport
指定しています。次にlogstash_format
とlogstash_prefix
を指定しています。logstash_format
はKibanaで加工しやすいようにインデックスを付けます。logstash_prefix
ではインデックスのprefixを指定します。Kibanaでインデックスを指定してログを見ることになるのでわかりやすい名前をつけます。上記の設定ではclient-2017.01.01
のようなインデックスが作成されます。また、time_key timestamp
はfilter
で追加したタイムスタンプを使用するように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で見るとタイムスタンプが同じになってしまい、本来の行とは異なった並びになってしまうことがあります。複数行のログをうまく一つにまとめられるとログが見やすくなると思います。何か方法はないかな...