Apacheログをfluentdで読み出してelasticsearchに転送する流れをdockerでシミュレートするハンズオン


こんにちはみなさん

Elasticsearchって、AWSのサービスじゃなかったんです。

そんな感想を持っているのが私だけじゃなかったので、少しうれしくなりつつ、業務でアクセスログやアプリログの格納先として使用することになりました。
そもそも、AWSにはElasticsearchのホスティングサービス( ESS )というのができているし、イマドキだとログはMongoDBに入れるよりもElasticsearchに入れたほうが、後々扱いやすくなるっぽいです。

急にバックグラウンドでdockerを動かすと突然落ちたりするところに不安をいだきつつ、今回はハンズオン形式でログをElasticsearchに突っ込んで、kibanaで見物するところまで見てみましょう。

事前準備

前提として、以下の環境で行います

  • docker: 1.12.2
  • docker-compose: 1.8.1
  • Mac 10.11.6

Apacheのログをfluentdに読ませる

Apacheの設定

まず、Apacheのコンテナを作りましょう
全体のプロジェクトディレクトリを作成し、そこにさらにapacheディレクトリを作ります。
以降のファイルパスなどは以下のプロジェクトディレクトリ、log_elasticを起点にしたパスを記載しています。

$ mkdir log_elastic
$ cd log_elastic
$ mkdir apache
$ cd apache
$ mkdir htdocs log conf
$ ls
conf htdocs log

続いて、apacheディレクトリ上にDockerfileを配置します。

FROM httpd

COPY conf/httpd.conf /usr/local/apache2/conf/httpd.conf

ここで設置する設定ファイルは以下のようにします。

apache/conf/httpd.conf
Timeout 300
KeepAlive On
MaxKeepAliveRequests 100
KeepAliveTimeout 5
User www-data
Group www-data
HostnameLookups Off
LogLevel warn

LoadModule authn_file_module modules/mod_authn_file.so
LoadModule authn_core_module modules/mod_authn_core.so
LoadModule authz_host_module modules/mod_authz_host.so
LoadModule authz_groupfile_module modules/mod_authz_groupfile.so
LoadModule authz_user_module modules/mod_authz_user.so
LoadModule authz_core_module modules/mod_authz_core.so
LoadModule access_compat_module modules/mod_access_compat.so
LoadModule auth_basic_module modules/mod_auth_basic.so
LoadModule reqtimeout_module modules/mod_reqtimeout.so
LoadModule filter_module modules/mod_filter.so
LoadModule mime_module modules/mod_mime.so
LoadModule log_config_module modules/mod_log_config.so
LoadModule env_module modules/mod_env.so
LoadModule headers_module modules/mod_headers.so
LoadModule setenvif_module modules/mod_setenvif.so
LoadModule version_module modules/mod_version.so
LoadModule unixd_module modules/mod_unixd.so
LoadModule status_module modules/mod_status.so
LoadModule autoindex_module modules/mod_autoindex.so

# ports.conf
Listen 80
<IfModule ssl_module>
    Listen 443
</IfModule>
<IfModule mod_gnutls.c>
    Listen 443
</IfModule>

DocumentRoot /usr/local/apache2/htdocs
<Directory /usr/local/apache2/htdocs>
    AllowOverride All
    Require all granted
</Directory>

AccessFileName .htaccess
<FilesMatch "^\.ht">
    Require all denied
</FilesMatch>

LogFormat "domain:%V\thost:%h\tserver:%A\tident:%l\tuser:%u\ttime:%{%d/%b/%Y:%H:%M:%S %z}t\tmethod:%m\tpath:%U%q\tprotocol:%H\tstatus:%>s\tsize:%b\treferer:%{Referer}i\tagent:%{User-Agent}i\tresponse_time:%D" apache_ltsv

CustomLog /var/log/apache2/access.log apache_ltsv

ここで注目すべきはLogFormatとCustomLogの部分ですね。
LogFormatではapache_ltsvというログを自作していますが、これはltsv形式という解析しやすいログのフォーマットとなっています。
え?combined? fluentd先輩が読み込みに失敗することがあるので、使いません。
LTSVのすばらしさは、以下のサイトを見てみるといいでしょう。
LTSV FAQ - LTSV って何? どういうところが良いの?

最後にapacheで表示する内容ですが、なくてもいいです。
単純にapacheデフォルトの"It works!!"が出るだけでも十分ですが、一応用意してみましょう

apache/htdocs/index.html
<html>
<body>
    <h1>Hello World</h1>
</body>
</html>

docker-composeで立ち上げ

では、docker-composeを使ってapacheを立ててみましょう。
まず、docker-compose.ymlを以下のように作成します。

docker-compose.yml
version: '2'

services:

    apache:
        build: ./apache
        volumes:
            - ./apache/log:/var/log/apache2
            - ./apache/htdocs:/usr/local/apache2/htdocs
        ports:
            - 80:80

この状態で、docker-composeでapacheを立てます。
立てたらブラウザでアクセスしたり、curl打ったりしてみましょう。

$ docker-compose up -d
$ curl http://127.0.0.1/index.html
<html>
<body>
    <h1>Hello World</h1>
</body>
</html>

すると、ログ配下に

apache/log/access.log
domain:127.0.0.1    host:172.24.0.1 server:172.24.0.4   ident:- user:-  time:29/Sep/2016:**:**:** +0000 method:GET  path:/index.html    protocol:HTTP/1.1   status:200  size:55 referer:-   agent:curl/7.43.0   response_time:5715

こんな感じのログが吐き出されます。

トラブルシューティング

ここで、apacheサーバが起動しない場合、confの設定が間違っている場合があります。
docker psコマンドでapacheサーバが起動していない場合は

$ docker-compose up

でフォアグラウンドで実行して、エラーを確かめましょう。
エラー箇所が判明したら、修正し、

$ docker-compose build apache
$ docker-compose up

で起動するまで試行錯誤しましょう。
後のfluentdについても同じです。

fluentdでログを読む

次に、fluentdでログを読んで、とりあえずホストにファイルで出力してみましょう。
今回はapacheサーバにfluentdを仕込んだりせず、ボリューム共有を使って直接ファイルからログを拾ってみましょう。
真面目にやるのならばログの集約サーバを用意するといいですが、今回はそこまで必要ではないでしょう。

まず、プロジェクトルート上に設定用のディレクトリを作成します。

$ mkdir fluentd
$ cd fluentd
$ mkdir data plugins

そして、fluentd用のDockerfileを作ります。

FROM fluent/fluentd

RUN gem install fluent-plugin-elasticsearch
COPY fluent.conf /fluentd/etc/fluent.conf

もう予めelasticsearch用のプラグインが入ってますね。

次に、fluentdの設定ファイルを作ります。

fluentd/fluent.conf
<source>
  type tail
  path /var/log/apache2/access.log
  format ltsv
  tag apache.access_log
  pos_file /home/fluent/access.log.pos
  time_format %d/%b/%Y:%H:%M:%S %z
</source>

<match apache.**>
  type file
  path /var/log/fluent/access
  time_format %Y%m%dT%H%M%S%z
  flush_interval 60s
  #compress gzip
  timezone +09:00
</match>

docker-composeで、サーバ起動

それではdocker-composeを使って、再びサーバを再起動させてみましょう。

docker-compose.yml
version: '2'

services:

    apache:
        build: ./apache
        volumes:
            - ./apache/log:/var/log/apache2
            - ./apache/htdocs:/usr/local/apache2/htdocs
        ports:
            - 80:80

    fluentd:
        build: ./fluentd
        volumes:
            - ./apache/log:/var/log/apache2
            - ./fluentd/data:/var/log/fluent
        environment:
            FLUENTD_CONF: fluent.conf

このようにdocker-compose.ymlを書き換えた上で、

$ docker-compose stop
$ docker-compose up -d
$ curl http://127.0.0.1/index.html

すると、fluentd/data ディレクトリに、fluentdがファイルログを吐き出しているはずです。

動作の確認ができたら、一旦止めておきましょう。

$ docker-compose stop

Elasticsearchにログを入れるまで

さて、次にfluentdからログをelasticsearchに転送してみましょう。
ただ、とりあえず自分のローカルにもログは出し続けるようにしておきます。

elasticsearchの設定をdocker-compose.ymlに入れる

まず、elasticsearchとkibanaを使えるようにしておきましょう。
実際にこの二つは、すでにあるイメージをそのまま使ってしまうのが良いと思います。
docker-compose.ymlの設定は以下のとおりです。

docker-compose.yml
version: '2'

services:

    apache:
        build: ./apache
        volumes:
            - ./apache/log:/var/log/apache2
            - ./apache/htdocs:/usr/local/apache2/htdocs
        ports:
            - 80:80

    fluentd:
        build: ./fluentd
        volumes:
            - ./apache/log:/var/log/apache2
            - ./fluentd/data:/var/log/fluent
        environment:
            FLUENTD_CONF: fluent.conf

    elasticsearch:
        image: elasticsearch
        ports:
            - 9200

    kibana:
        image: kibana
        links:
            - elasticsearch:elasticsearch
        ports:
            - 5601:5601

docker-composeの設定はこれで終わりです。

fluentdの設定し直し

elasticsearchにログを転送できるように、fluent.confを書き直しましょう。

fluentd/fluent.conf
<source>
  type tail
  path /var/log/apache2/access.log
  format ltsv
  tag apache.access_log
  pos_file /home/fluent/access.log.pos
  time_format %d/%b/%Y:%H:%M:%S %z
</source>

<match apache.**>
  type copy
  <store>
    type elasticsearch
    include_tag_key true
    tag_key _tag
    host elasticsearch
    port 9200
    index_name access
    logstash_format true
    logstash_prefix log
  </store>
  <store>
    type file
    path /var/log/fluent/access
    time_format %Y%m%dT%H%M%S%z
    #compress gzip
    timezone +09:00
  </store>
</match>

こんな感じで、ログをfluentdに流しつつ、ローカルにもログを吐き出すように設定できます。
編集が完了したら、fluentdイメージを再生成しておきます。

docker-compose build fluentd

docker-composeで起動

準備が整ったので、docker-composeを使ってサーバ群を起動してみましょう。

$ docker-compose up -d
$ curl http://127.0.0.1/index.html

これで大丈夫のはずです。

kibanaを眺めてみる。

kibanaが既に起動していて、5601ポートが空いていますので、以下のURLでkibanaを見てみましょう。

すると、こんな画面になります。

まず、フォームの「logstash-*」と書いてある部分の「logstash」を、fluent.confの「logstash_prefix」に設定した文字列に書き直して、フォーカスアウトすると、ボタンができるので、これを押します。

これでkibanaを使う準備が整いましたので、ログを見てみましょう。
画面上部の「Discover」というボタンがあるので、これを押してみましょう。

こんな感じの画面になり、いつアクセスが有ったかがわかるようになります。
あとは色々といじってみましょう。

ハンズオン自体はこれで終りとなります。

まとめ

dockerを使うと、いろいろなシミュレーションが簡単にできます。
今回のシミュレーションを本番に適用する場合は、ログ集約用のfluentdを作ってもいいかと思います。

今回はこんなところです。

参考

elasticsearch
fluentd-elasticsearch-plugin
fluentd × Elasticsearch × kibanaによるアクセスログ解析