ELK(ElasticSearch, Logstash, Kibana)+fluentdの環境をDocker Composeで構築しつつ、試しにCloudWatchの統計データを収集してみた

前々から気になっていたELK(ElasticSearch, Logstash, Kibana)の環境をDocker Composeを使ってどこにでも簡単に構築できるようにしてみました。

また、Logstashやfluentdのプラグインを使ったデータ収集と可視化も試してみようと思い、試しにAWSのCloudWatchの統計データを収集してみました。

# 環境

今回試した環境と、ツール類のバージョンは下記のとおりです。

| 環境            |         バージョンなど|
|:---------------|--------------------:|
| OS             | EI Capitan(10.11.1) | 
| ElasticSearch  |               2.0.0 |
| Logstash       |               2.0.0 |
| Kibana         |               4.2.0 |
| td-agent       |             0.12.12 |
| Docker Engine  |               1.8.3 |
| Docker Machine |               0.4.1 |
| Docker Compose |               1.4.2 |

表のDockerについて。。。

* Engineはいわゆる普通のDocker。Dockerの実行環境です。
* MachineはローカルやAWSなどの別環境にDockerのホストマシンを構築するツール。  
通常、MacやWindowsでDockerを使う場合は、このMachineを用いて、ローカルのVirtualBoxにDockerホストのVMを作って、それに対して、dockerコマンドを実行していきます。
* Composeは複数のコンテナのパラメータをYAMLファイルに記述しておいて、ワンボタンで環境構築するものです。


# はじめにdockerの環境を整えておく
Dockerで構築しておくと何度も同じインストール作業をしなくてよくなります。

MacだとDocker Toolboxをインストールすれば、Docker Engine, Machine, Composeの準備がすべて整います。
https://docs.docker.com/mac/step_one/

homebrew caskが使える場合は、下記でもインストールできます。
(私はこちらでやりました)

`$ brew cask install dockertoolbox`

インストールが終わったら、Dockerのホストが動作するVMをVirtualBoxに作成しておきます。
今回devという名前で作成しました。
`$ docker-machine create -d virtualbox dev`

Windows10でもDocker Toolboxをインストールして試してみましたが、Macと同じようにできました。
(ターミナルにはcygwinを使用しました)

# それぞれのコンテナを作る
## それぞれのDockerfile
今回作ったものは、下記になります。GitHubのリポジトリとして登録しています。
ELKに合わせてfluentdも試してみようと思い追加しています。
それぞれのビルド後のイメージサイズも書いてみました。

* [ElasticSearch](https://github.com/khiraiwa/docker-elasticsearch/blob/d1d76fcf8696919d34d5d4032e04b8c89fa61c39/Dockerfile)  314 MB
* [Logstash](https://github.com/khiraiwa/docker-logstash/blob/c166c5c40fc525ae83292a8e931fb09b7a8858f4/Dockerfile)  422 MB
* [Kibana](https://github.com/khiraiwa/docker-kibana/blob/55381dc9ac2c3edf8aafa43887e6cb7616fb7f70/Dockerfile)  321 MB
* [fluentd](https://github.com/khiraiwa/docker-tdagent/blob/24ce5ab67e2b445d8464fbc0df55b2ee728dc1d7/Dockerfile)  125 MB

今回は勉強のためにubuntuのイメージをベースに構築してみましたが、イメージのサイズが大きくなってしまいました。。。:sweat:
ちなみに、下記まっさらな公式イメージ。Kibanaとか全然違いますね。

* [ElasticSearch公式](https://hub.docker.com/_/elasticsearch/)  
153MB
* [Logstash公式](https://hub.docker.com/_/logstash/)  
206MB
* [Kibana公式](https://hub.docker.com/_/kibana/)  
89MB
* [fluentd公式](https://hub.docker.com/r/fluent/fluentd/)  
v0.12.16で178 MB

## DockerHubのAutomated Buildに登録しておく
Docker Composeを実行した際にDockerHubから自動的にイメージを取ってくるようにするため、GitHubのリポジトリをDockerHubに登録しておきます。  
これをやっておくと、GitHubの特定のリポジトリの特定のブランチにPushしたタイミングで自動的にDockerHubでイメージがビルドされ保存されるようになります。

方法は、とても簡単で、

0. まず、DockerHubにログインします。
0. 下記のように"Create"ボタンの右の▼を押して、
![dockerhub1.png](https://qiita-image-store.s3.amazonaws.com/0/45959/f442cba1-6d01-2414-84c3-091e022616ff.png)
0. "Create Automated Build"を選択します。
![dockerhub2.png](https://qiita-image-store.s3.amazonaws.com/0/45959/dfc11505-e43d-ee11-4675-4dd870f30575.png)
0. すでにGitHubアカウントでログインしていたり、ひも付けをおこなっていると、下記のようにリポジトリ一覧が表示されます。  
Automated Buildしたいリポジトリを選択します。
![dockerhub3.png](https://qiita-image-store.s3.amazonaws.com/0/45959/15b2f804-f704-4a7c-90bb-cca48865ebbc.png)
0. 必要内容を記述して、"Create"ボタンを押します。
![dockerhub4.png](https://qiita-image-store.s3.amazonaws.com/0/45959/2dc69810-0615-bcd8-7b54-bbebea4a73d4.png)

これだけで完了です。

## Docker Composeで構築する

次にDocker Composeで構築するためのYAMLファイルを作成します。

今回のコンテナの関係は下記の図のようになっています。

![Pasted_Image_11_7_15__9_54_PM.png](https://qiita-image-store.s3.amazonaws.com/0/45959/d44ed0e2-1793-7b89-c98d-fa126525a7c5.png)

赤い矢印についてはコンテナからコンテナへの通信なので、相手のIPアドレスがわからないと通信できません。
そのため、下記のようにlinkオプションで指定してやる必要があります。

```yaml
  links:
    - elasticsearch:docker-elasticsearch
```
これを書いておけば、docker-elasticsearchというホスト名で、elasticsearchのコンテナのIPアドレスが引けるようになります。
(コンテナ内の/etc/hostsにelasticsearchのコンテナのIPアドレスが書かれていました。)

上をふまえて、最終的にDocker Composeに渡すYAMLファイル(docker-compose.yml)は下記のようにしました。

```yaml:docker-compose.yml
# elasticsearch
elasticsearch:
  image: khiraiwa/docker-elasticsearch:1.0.0
  volumes:
    - /data_elasticsearch:/data_elasticsearch
  ports:
    - "9200:9200"
    - "9300:9300"

# kibana
kibana:
  image: khiraiwa/docker-kibana:1.0.0
  volumes:
    - /data_kibana:/data_kibana
  ports:
    - "5601:5601"
  links:
    - elasticsearch:docker-elasticsearch

# logstash
logstash:
  image: khiraiwa/docker-logstash:1.0.0
  volumes:
    - /data_logstash:/data_logstash
  links:
    - elasticsearch:docker-elasticsearch
  environment:
    - AWS_ACCESS_KEY_ID=dummy
    - AWS_SECRET_ACCESS_KEY=dummy

# tdagent
tdagent:
  image: khiraiwa/docker-tdagent:1.0.0
  volumes:
    - /data_tdagent:/etc/td-agent
  links:
    - elasticsearch:docker-elasticsearch
  environment:
    - AWS_ACCESS_KEY_ID=dummy
    - AWS_SECRET_ACCESS_KEY=dummy
```


(GitHubにあげておきました。
https://github.com/khiraiwa/compose-elk/blob/5694e5c09b9e679b674d4a916582802f24d6e855/docker-compose.yml)

# CloudWatchの統計データを取得してみる
動作確認のため、LogstashやfluentdのコンテナでCloudWatchの統計データをInputして、ElasiticSearchのコンテナにOutputするようにしてみました。

## Logstash

検索すると、下記のInputプラグインが見つかりました。
[logstash-input-cloudwatch](https://github.com/EagerELK/logstash-input-cloudwatch)

今回はこれを使ってみることに。

しかし、問題があり、logstash-input-cloudwatch.gemspecに書かれているlogstashの対応バージョンは1.4.0以上2.0.0未満でした。

そこで、とりあえず、これを2.0.0に対応させてビルドしてみることに。
下の方の一行を下記のように修正しました。

```:logstash-input-cloudwatch.gemspec
  s.add_runtime_dependency "logstash-core", '>= 1.4.0', '<= 2.0.0'
```
そして下記のコマンドでビルド。

```bash

$ [logstashへのパス]/vendor/jruby/bin/gem build logstash-filter-awesome.gemspec
```

すると、logstash-input-cloudwatch-0.2.2.gemが出来上がったので、下記でインストール。

```bash

$ [logstashへのパス]/bin/plugin install /your/local/plugin/logstash-filter-awesome.gem
```

今回のDockerイメージでは、Dockerfileにてビルド済みファイルをADDしてインストールするようにしています。

Outputのプラグインの追加インストールは必要なく、デフォルトでElasticSearchにOutputできます。

logstash.confは下記のようにしてみました。

```rb:logstash.conf

input {
  cloudwatch {
    tag_name => "Name"
    tag_values => "*"
    region => 'us-west-2'
    interval => 900
    period => 60
    namespace => 'AWS/EC2'
    metrics => [ 'CPUCreditUsage','CPUCreditBalance','CPUUtilization','DiskReadOps','DiskWriteOps','DiskReadBytes','DiskWriteBytes','NetworkIn','NetworkOut','StatusCheckFailed','StatusCheckFailed_Instance','StatusCheckFailed_System' ]
    statistics => [ 'SampleCount', 'Average', 'Minimum', 'Maximum', 'Sum' ]
  }
}

output {
  elasticsearch { hosts => 'docker-elasticsearch:9200' }
  stdout { codec => rubydebug }
}
```

inputのcloudwathではnamespaceがAWS/EC2の統計データを取得するようにしています。
outputではelasticsearchへの出力と標準出力を行っています。
elasticsearchのhostsにはdocker-elasticsearchという値を設定しているので、/etc/hostsにより名前解決されます。

実行してみたところ正しく動作し、ElasticSearchには下記のような形でInputされていました。(CPUUtilizationのもの)

```json
{
  "_index": "logstash-2015.11.04",
  "_type": "logs",
  "_id": "AVDSjQcXo0IJOX5JTEN8",
  "_score": null,
  "_source": {
    "timestamp": "2015-11-04 12:43:00 +0000",
    "sample_count": 5,
    "unit": "Percent",
    "minimum": 0,
    "maximum": 0.17,
    "sum": 0.17,
    "average": 0.034,
    "@version": "1",
    "@timestamp": "2015-11-04T12:43:00.000Z",
    "metric": "CPUUtilization",
    "instance": "i-fdb70939",
    "Name": "Test"
  },
  "fields": {
    "@timestamp": [
      1446640980000
    ]
  },
  "sort": [
    "43"
  ]
}
```

このプラグイン、試してみたところ、tag_valuesのところにワイルドカードを指定可能なようで、なかなか便利でした。

※本来、Logstashの2.0.0には対応していなかったはずなのでちょっと心配ですが。。。

## fluentd

こちらも探してみるとInputのプラグインとして下記が見つかりました。
[fluent-plugin-cloudwatch](https://github.com/yunomu/fluent-plugin-cloudwatch)

また、ElasticSearchへのOutputにも別途プラグインの追加が必要です。
[fluent-plugin-elasticsearch](https://github.com/uken/fluent-plugin-elasticsearch)

これらはDockerfile中でインストールするようにしています。

tdagent.confは下記のようにしました。

```xml:tdagent.conf

<source>
  type cloudwatch
  tag  cloudwatch.ec2

  aws_key_id  YOUR_AWS_KEY_ID
  aws_sec_key YOUR_AWS_SECRET_KEY

  cw_endpoint monitoring.us-west-2.amazonaws.com

  namespace AWS/EC2
  metric_name CPUCreditUsage,CPUCreditBalance,CPUUtilization,DiskReadOps,DiskWriteOps,DiskReadBytes,DiskWriteBytes,NetworkIn,NetworkOut,StatusCheckFailed,StatusCheckFailed_Instance,StatusCheckFailed_System,
  dimensions_name InstanceId
  dimensions_value i-XXXXXXXX
  statistics Average
  period 300
  interval 300
</source>

<source>
  type cloudwatch
  tag  cloudwatch.billing

  aws_key_id  YOUR_AWS_KEY_ID
  aws_sec_key YOUR_AWS_SECRET_KEY

  cw_endpoint monitoring.us-east-1.amazonaws.com

  namespace AWS/Billing
  metric_name EstimatedCharges
  dimensions_name Currency
  dimensions_value USD
  statistics Average
  interval 7200
  period 21600
</source>

<match cloudwatch.**>
  type elasticsearch
  host docker-elasticsearch
  port 9200
  index_name fluentd
  type_name fluentd
  logstash_format true
  logstash_prefix tdagent
  request_timeout 5s # defaults to 5s
  reload_connections true # defaults to true

  # Buffered output options
  buffer_type memory
  flush_interval 60
  retry_limit 17
  retry_wait 1.0
  num_threads 1
</match>
```

まず、一つ目のsourceではlogstashの時と同じくAWS/EC2のものを、二つ目のsourceではAWS/Billingを取るようにしています。
AWS/Billingは現時点の請求額です。
AWS/Billingのnamespaceの統計データを取るには、あらかじめ下記の設定が必要です。
https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/DeveloperGuide/monitor_estimated_charges_with_cloudwatch.html

Outputについて、Logstashのときと同様にelasticsearchのhostにはdocker-elasticsearchを設定しているので、/etc/hostsにより名前解決されます。

実行してみたところ正しく動作し、ElasticSearchには下記のようにインプットされていました。

```json
{
  "_index": "tdagent-2015.11.04",
  "_type": "fluentd",
  "_id": "AVDSmNVAo0IJOX5JTEYl",
  "_score": null,
  "_source": {
    "CPUUtilization": 0.032,
    "@timestamp": "2015-11-04T12:53:00+00:00"
  },
  "fields": {
    "@timestamp": [
      1446641580000
    ]
  },
  "sort": [
    1446641580000
  ]
}
```

ちなみに、Logstashで使えたようなワイルドカードは使えないようだったので、とりあえずtdagent.confのdimensions_valueをi-XXXXXXXXとしています。
(実行時に修正して、td-agentのコンテナを再起動する必要あり。。。)

# 実際に構築してみる
一連の手順を詳しく書くと長いように感じますが、実際に入力するdocker-composeのコマンドはとてもシンプルです。

## dockerの準備
0. docker machineの起動  
```$ docker-machine start dev ```
0. docker machineの環境変数をホストマシンに登録  
```$ eval "$(docker-machine env dev)"```
0. docker machineにポートフォワーディング  
コンテナがたちあがるのは、devというVM上なので、  
devというVMのKibanaの5601/tcpポートをVMのホストのMacから繋げられるようにしておきます。  
別のターミナルを開いて、下記を入力。    
```$ ssh docker@$(docker-machine ip dev) -L 5601:localhost:5601```  
このとき聞かれるパスワードはデフォルトだとtcuserです。
0. docker composeのリポジトリをclone  
```$ git clone -b 1.0.0 git@github.com:khiraiwa/compose-elk.git ```  
```$ cd compose-elk ```  
docker-compose.ymlの```AWS_ACCESS_KEY_ID```と```AWS_SECRET_ACCESS_KEY```を正しいものに書き換えます。(二か所)
0. docker composeで起動    
```$ docker-compose up -d ```  
すると、DockerHubからのイメージのPullが始まり、それが完了するとコンテナが立ち上がります。

## tg-agentのconfを書き換えて再起動
自動でtd-agent.confのインスタンスIDを書き換えるようにしていないので、手作業が発生してしまいました :sweat: 

0. 先ほどポートフォワーディングしたターミナルはdevのVMにログインしたままになっているので、これで/data_tdagent/td-agent.confを開きます。(viなどで)
0. dimensions_valueがi-XXXXXXXXになっている箇所が一つあるので、これを正しいインスタンスIDに書き換えて、保存します。(下記、例)  
```
  dimensions_value i-fdb70939
```
0. コンテナを再起動します。Docker Composeを実行したマシンで、下記を実行して、fluentdのコンテナを再起動します。  
```
$ docker-compose restart tdagent
```

## Kibanaで見てみる
0. Webブラウザから下記のURLにアクセスします。  
http://localhost:5601/  
すると、Kibanaが開きます。  
しかし、初回だと、まだindexパターンの設定を行っていないので、データをみることはできません。
0. indexパターンの設定を行う  
まず、"Settings"のメニューを選択します。
![kibana1.png](https://qiita-image-store.s3.amazonaws.com/0/45959/9cd76835-283f-c8fc-11aa-91993124449f.png)
続いて、indexのパターンを作成します。  
今回Logstashはlogstash-\*、fluentdはtdagent-\*のindexのフォーマットでデータをInputするように設定しているため、それを入力して作成します。
"Create"ボタンが緑色にならず、押せなかった場合はまだデータがInputされていません。
![kibana2.png](https://qiita-image-store.s3.amazonaws.com/0/45959/6175d630-559c-c00b-af95-9de5a9519f07.png)
0. データを確認する  
"Discover"を選択し、先ほど作成したindexパターンを選択します。  
すると、図のようにデータのInputのグラフと、実際のパラメータが表示されるので確認できます。
![kibana3.png](https://qiita-image-store.s3.amazonaws.com/0/45959/3b1c70b6-1d45-631e-a0f7-e8bb9cffbcfa.png)
0. グラフ化  
Visualizeでグラフの作成が可能です。そして、それをDashboardで自由に表示できます。
(ここについては割愛します)

## dockerの終了
0. docker composeで終了  
```$ docker-compose stop```
0. docker machineの終了  
```$ docker-machine stop dev ```
0. dockerホストのVMも削除する場合は  
```$ docker-machine rm dev ```

# 最後に

これで、どこでもワンボタンで構築できるELK(+fluentd)の環境を作ることができました。
(ちなみに、まだProxy環境下ではテストしていないので動かないかもです。)

ちなみに、今回はCloudWatchの統計データ取得を試しにやりましたが、AWSのACCESS_KEYを登録するだけでいい、SaaSのサービスの方が相当楽ですね:sweat:
またGrafanaだと、CloudWatchが正式にサポートされているそうです。
[GrafanaでCloudWatch見る（接続設定編）](http://qiita.com/gacha-ru/items/0ccebe4f489c3f1a6e43)

まあ、そもそも最大表示間隔が2週間でよければ、2015年10月に発表された[CloudWatch Dashboards](http://aws.typepad.com/aws_japan/2015/10/cloudwatch-dashboards-create-use-customized-metrics-views.html)で十分なのですが。