Docker初心者が、Nginxのログを fluentd + elasticsearch + kibana で可視化してみた

Dockerは仮想技術の一つ程度しか知らいない状態から、JJUG CCC 2018 Spring で「JavaエンジニアのためのDocker入門 ～ 仮想開発・テスト環境構築 ～ 」を聞いて少し勉強してみたのでDockerで環境を作ってみました。「[今更Docker入門 - コンテナ化することで何が嬉しいか](https://qiita.com/zgmf_mbfp03/items/9e515124217e4f081b6f)」の延長線で、Nginxのログを fluentd + elasticsearch + kibana で可視化してみました。ちなみに私は、[fluentd](https://www.fluentd.org/) も [elasticsearch](https://www.elastic.co/products/elasticsearch) も [kibana](https://www.elastic.co/products/kibana) も聞いたことがあるだけで全く使ったことも無く予備知識もほぼないまま、環境を構築しているので、誤りがあるかもです。

# 環境
* macOS
* Docker for Mac Version 2.0.0.0-mac81 ([Docker](https://docs.docker.com/))

# 作るもの
nginxのログを収集して、kibanaでいつどれくらいのアクセスがあったのかを可視化します。今回は、細かい設定はなしで、可視化します。やり方として、nginxのログをfluentdで収集してelasticsearchに蓄積させます。蓄積したデータをもとにkibanaで可視化していきます。

# 作ったもの
[git](https://github.com/kmiura-1002/nginx-monitoring)を見てください。
cloneした後に、``docker-compose build``して、``docker-compose up -d``で起動できます。
コマンドを叩くとこんな感じになると思います。初回のbuildは、時間が少しかかります。

```
$ docker-compose build
Building elasticsearch
Step 1/1 : FROM elasticsearch:5.6
 ---> 671bb2d7da44

Successfully built 671bb2d7da44
Successfully tagged nginx-monitoring_elasticsearch:latest
Building fluentd
Step 1/2 : FROM fluent/fluentd:v1.3.2-1.0
 ---> 2b942cd70a7f
Step 2/2 : RUN gem install fluent-plugin-elasticsearch
 ---> Using cache
 ---> fb7ab6e052b3

Successfully built fb7ab6e052b3
Successfully tagged nginx-monitoring_fluentd:latest
Building nginx
Step 1/1 : FROM nginx:1.15.8
 ---> 02256cfb0e4b

Successfully built 02256cfb0e4b
Successfully tagged nginx-monitoring_nginx:latest
Building kibana
Step 1/1 : FROM kibana:5.6.14
 ---> 4e429ed92aeb

Successfully built 4e429ed92aeb
Successfully tagged nginx-monitoring_kibana:latest
$ docker-compose up -d
Creating network "nginx-monitoring_default" with the default driver
Creating nginx-monitoring_elasticsearch_1 ... done
Creating nginx-monitoring_fluentd_1       ... done
Creating nginx-monitoring_kibana_1        ... done
Creating nginx-monitoring_nginx_1         ... done
$ docker ps
CONTAINER ID        IMAGE                            COMMAND                  CREATED             STATUS              PORTS                                NAMES
576dd2bce127        nginx-monitoring_nginx           "nginx -g 'daemon of…"   9 seconds ago       Up 14 seconds       0.0.0.0:8090->80/tcp                 nginx-monitoring_nginx_1
ab084c4416a3        nginx-monitoring_kibana          "/bin/sh -c /usr/loc…"   10 seconds ago      Up 15 seconds       0.0.0.0:5601->5601/tcp               nginx-monitoring_kibana_1
ef28ec21fe2b        nginx-monitoring_fluentd         "/bin/entrypoint.sh …"   10 seconds ago      Up 15 seconds       5140/tcp, 0.0.0.0:24224->24224/tcp   nginx-monitoring_fluentd_1
2550dc320be2        nginx-monitoring_elasticsearch   "/bin/bash bin/es-do…"   11 seconds ago      Up 16 seconds       0.0.0.0:9200->9200/tcp, 9300/tcp     nginx-monitoring_elasticsearch_1
```
nginx、fluentd、elasticsearch、kibanaが立ち上がったら、[http://localhost:8090/](http://localhost:8090/)にアクセスしてみます。
その後、kibanaにアクセス([localhost:5601](http://localhost:5601))して、設定後（あとで説明）Timelineから下図のようにアクセス数がグラフで見れます。
<img width="1105" alt="スクリーンショット 2018-06-04 22.18.45.png" src="https://qiita-image-store.s3.amazonaws.com/0/101378/760880e5-e54e-f836-9daf-f1d8bc0d8939.png">

そもそもnginx、fluentd、elasticsearch、kibanaを連携させていくために、それぞれについてある程度の知識が必要なので、簡単に次から説明していきます。

# nginx
一言で言ってしまうとApacheと同じWebサーバです。が、中身はApacheとは大きく異なります。Apacheは、マルチプロセスモデルで同時アクセス数が増えると起動プロセス数×ヒープサイズの掛け算でメモリを食べにいくためメモリが枯渇しやすくなり、レスポンスが悪くなるます。世にいう、C10K（クライアント1万台)問題と呼ばれているやつです。これに比べ、nginxはC10K問題耐えうるように、イベンドドリブンモデルで同時アクセスに対する処理に特化しているので、Apacheのようにレスポンスが悪くなることはないです。シングルスレッドでプロセス数が増えないように処理しているからです。これだけだと、nginx一択に思えますが、Apacheにもメリットがあり、nginxにもデメリットはあります。nginxに比べApacheは、Webサーバとしての機能が豊富にあり、動的コンテンツの処理速度が速いようです。

スマホの登場でサイトへのアクセス数が増加しているので、nginxは知っておいて損はないですね。（むしろ知らないほうがやばい・・・？）

# fluentd
OSSのデータ収集管理ツールです。fluentdの特徴は、データの収集とそのデータの記録先をカスタマイズできることです。データファイルをどこかに転送したり、収集・蓄積をリアルタイムに処理したい場合にとてもいいツールになります。ここでいうデータは主に、ログです。プラグインを使えば、Tweetデータなども収集できます。

<img width="903" alt="スクリーンショット 2018-06-07 23.59.38.png" src="https://qiita-image-store.s3.amazonaws.com/0/101378/642f7847-4bba-1884-834a-86af407fd103.png">

イメージとして、上図のような感じです。DBのログやLinuxのログ、Twitterのデータなどをfluentdが収集して、kafkaや、hadoop、elasticsearch、S３などに渡すことができます。

fluentdには、以下のような機能を持ち合わせています。

* input(fluentdに入れる・収集するデータ)
    * 必要に応じてパースできる
    * タイムスタンプを管理が可能
* output(fluentdが吐き出すデータ)
    * 必要に応じてフォーマットできる
* buffer(収集するデータを紛失しない仕組み)
    * データのやり取りでなにかエラーがあればリトライしてくれる
    * バッファはmemory bufferとfile bufferの２種類がある  

|  | memory buffer | file buffer |
|:-:|:--|:--|
|バッファの場所|メモリ|ファイル|
|処理速度|早い|遅い|
|使用容量|少ない（メモリ枯渇の可能性有）|大きい（ディスク容量に依存）|
|運用面|運用しにくい|運用しやすい|

bufferは上の表のような特徴がある。基本的には、file bufferがおすすめ。なんらかの理由で、fluentd自体が終了した場合にファイルとしてバッファが残るので確認が行いやすい。処理速度は、memory bufferに比べれば遅いというかだけでI/Oの時間を厳密にきにする状況で無ければメモリ枯渇を危険性を背負いながらmemory bufferを選ぶ理由は無いと思います。一つ注意するとすればデフォルトの設定の場合、memory bufferになっている事です。なので必要に応じてバッファの設定を書き換える必要があります。

# elasticsearch
elasticsearchは、elastic社が開発しているオープンソースの全文検索エンジン(分散型RESTful検索/分析エンジン)です。RDBのようにSQLでのデータ操作は行いません。そのかわり、RESTful APIを使って操作します。そのため、様々な言語での操作が可能です。elasticsearchは、主にデータの保存と検索を担当するため、可視化やセキュリティといった部分は、[Elastic Stack](https://www.elastic.co/jp/products)と呼ばれている関連システム群を使います。Elastic Stackには、elasticsearch、kinaba、Logstash、Beats、X-Pack、ECEといったものがあります。今回は、そこまで難しいことをしないので、あまり説明しません。

# Kibana
elasticsearchに入っているデータを様々な形式で可視化することができます。また、elasticsearchに入っているデータの検索もできます。

# ログの可視化まで
## 構成

```
.
├── docker-compose.yml
├── elasticsearch
│   └── Dockerfile
├── fluentd
│   ├── Dockerfile
│   └── config
│       └── fluent.conf
├── kibana
│   └── Dockerfile
└── nginx
    ├── Dockerfile
    ├── config
    │   └── nginx.conf
    └── html
        ├── index.html
        └── test.html
```

```yml:docker-compose.yml
version: "3.3"

services:
  nginx:
    build: ./nginx
    volumes:
      - ./nginx/config/nginx.conf:/etc/nginx/nginx.conf
      - ./nginx/log:/var/log/nginx
      - ./nginx/html:/usr/share/nginx/html
    ports:
      - 8090:80
    links:
      - fluentd

  fluentd:
    build: ./fluentd
    volumes:
      - ./fluentd/config:/fluentd/etc
      - ./nginx/log:/var/log/nginx
    links:
      - elasticsearch
    ports:
      - "24224:24224"
    depends_on:
      - elasticsearch

  elasticsearch:
    build: elasticsearch
    environment:
      - discovery.type=single-node
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    ports:
      - "9200:9200"
    ulimits:
      memlock:
        soft: -1
        hard: -1
    volumes:
      - esdata:/usr/share/elasticsearch/data

  kibana:
    build: kibana
    ports:
      - "5601:5601"
    links:
      - elasticsearch:elasticsearch

volumes:
  esdata:
    driver: local
```

```Dockerfile:./elasticsearch/Dockerfile
FROM elasticsearch:5.6.14

# RUN elasticsearch-plugin remove x-pack
```

elasticsearch:5.6.4の頃はelasticsearchのイメージをそのまま利用するとx-packが入っているため、削除が必要でした。x-packは、有料ツールのため、ライセンスキーが必要になるため、削除または無効化が必要です。5.6.14ではremoveが不要になったようです。

```Dockerfile:./fluentd/Dockerfile
FROM fluent/fluentd:v1.3.2-1.0

RUN gem install fluent-plugin-elasticsearch
```
今回は、fluentdでelasticsearchにデータを送るために、fluent-plugin-elasticsearchをインストールします。


```conf:./fluentd/config/fluent.conf
<source>
  type tail
  format ltsv
  path /var/log/nginx/access.log
  tag nginx
  pos_file /var/log/nginx/access.log.pos
</source>

<match nginx>
  type elasticsearch
  host elasticsearch
  buffer_type memory
  port 9200
  index_name fluentd
  type_name nginx
  logstash_format true
  logstash_prefix nginx.access
</match>
```

fluentdの設定で``<source>``の部分が入力で``<match>``が出力に当たります。  
今回指定した設定  

| souece | 設定値 | 概要 |
|:-:|:--|:--|
| type |  tail | 入力をtailで読む  |
|  format |  ltsv | ltsv形式としてファイル読み込み（加工するわけではない）  |
|  path | /var/log/nginx/access.log  |  取り込むデータの場所 |
|  tag | nginx  | tailしたデータの吐き出す場所の指定  |
| pos_file  |  /var/log/nginx/access.log.pos |  どこまで読み込んだか記録しているposファイルを指定 |


| match | 設定値 | 概要 |
|:-:|:--|:--|
| type  | elasticsearch  |  fluent-plugin-elasticsearchプラグインで追加されたもので、elasticsearchに転送します |
|   host| elasticsearch  |  転送先のホスト |
| buffer_type  | memory  | メモリバッファを指定  |
| port  |  9200 | 転送先のポート  |
| index_name  |  fluentd | elasticsearchでのindex名、logstash_formatがtrueの場合は、利用されない  |
| type_name  | nginx  | elasticsearchのタイプ名  |
|logstash_format   | true  | kibanaにlogstashフォーマットで出力するオプション  |
| logstash_prefix  | nginx.access  |  kibanaに転送するデータのプレフィックス |

``source``の``tag``に書かれているタグによって出口のmatchを選択する。``mathc``は``<match nginx>``と設定している。なので``source``の``tag nginx``で収集したデータは``<match nginx>``で転送先を仕分けることになります。以下のように複数の出口を設定することも可能です。

```conf:fluent.conf
<source>
  tag hoge.foo
  ・・・
</source>

<source>
  tag hoge.huga
  ・・・
</source>

# tag hoge.fooの出口
<match hoge.foo>
  ・・・
</match>

# tag hoge.fooとtag hoge.hugaの両方の出口
<match hoge.**>
  ・・・
</match>


```


```Dockerfile:./kibana/Dockerfile
FROM kibana:5.6.14

# RUN kibana-plugin remove x-pack
```

kibanaも5.6.4の頃はx-packの削除が必要でしたが、5.6.14で不要のようです。

```conf:./nginx/config/nginx.conf
user  nginx;

worker_processes  auto;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;

events {
    worker_connections  1024;
}

http {
    # 他のファイルから設定読み込み
    # mime.types : MIMEタイプと拡張子の関連付けを定義したファイル
    include /etc/nginx/mime.types;

    # レスポンスのデフォルトMIMEタイプ設定
    default_type application/octet-stream;

    # アクセスログの書式設定(ltsv)
log_format ltsv 'time:$time_iso8601\t'
                'remote_addr:$remote_addr\t'
                'request_method:$request_method\t'
                'request_length:$request_length\t'
                'request_uri:$request_uri\t'
                'https:$https\t'
                'uri:$uri\t'
                'query_string:$query_string\t'
                'status:$status\t'
                'bytes_sent:$bytes_sent\t'
                'body_bytes_sent:$body_bytes_sent\t'
                'referer:$http_referer\t'
                'useragent:$http_user_agent\t'
                'forwardedfor:$http_x_forwarded_for\t'
                'request_time:$request_time\t'
                'upstream_response_time:$upstream_response_time\t'
                'host:$host';

    # アクセスログの出力先
    access_log /var/log/nginx/access.log  ltsv;

    # コンテンツのファイルの読み込みとクライアントへのレスポンスの送信にsendfile() APIを使うかを設定
    # デフォルトは off
    sendfile on;

    include /etc/nginx/conf.d/*.conf;
}
```

```Dockerfile:./nginx/Dockerfile
FROM nginx:1.15.8
```

これで下準備完了。あとは``docker-compose build``して``docker-compose up -d``で起動します。起動したらnginxにアクセスしてログを吐かせます。次にkibanaにアクセスします。kibanaは、127.0.0.1:5601でアクセスできます。
![127.0.0.1_5601_app_kibana(Laptop with HiDPI screen).png](https://qiita-image-store.s3.amazonaws.com/0/101378/b258445f-7170-ed28-3b96-fc409fc19301.png)

アクセスするとこんな感じになると思います。この画面から、elasticsearchのインデックスを指定します。fluentdの設定でlogstash_prefixに指定したnginx.accessをindex patterに記述し、Createします。
![127.0.0.1_5601_app_kibana(Laptop with HiDPI screen) (1).png](https://qiita-image-store.s3.amazonaws.com/0/101378/88c91df5-bd7d-a402-7b21-afdc9863e9b3.png)

Timelineに移動するとグラフでアクセス数が見れます
![127.0.0.1_5601_app_timelion(Laptop with HiDPI screen).png](https://qiita-image-store.s3.amazonaws.com/0/101378/482a9bc5-20b6-a604-0dab-edf2bc7f0bde.png)

次に、Visualizeから簡単な集計を行って見ます。ログからuseragentを見てブラウザ毎のアクセス数を集計してみます。VisualizeからPieを選択。Split Slicesを追加、AggregationにTermを指定、Fieldにuseragent.keywordを指定、Order ByにCustom Metricを指定してAggregationをCountに
![127.0.0.1_5601_app_kibana(Laptop with HiDPI screen) (2).png](https://qiita-image-store.s3.amazonaws.com/0/101378/bcf66ee1-ad32-05d0-fda1-14ffdbb50194.png)

あとは、各種ブラウザで適当にアクセスしてログを作り、Dashboardにグラフ登録したりすれば、可視化完了です。ログファイルを見るより、明らかに可視化した方が見やすく情報も読み取りやすいですね。