Docker + fig で Apache + fluentd + Elasticsearch + Kibana を試す

前から気になってたツールを試すシリーズその2。

今回は、複数のDockerコンテナを管理する [fig](http://www.fig.sh/index.html) がどんなものなのか試すために、これまた気になってた [fluentd](http://www.fluentd.org/) 、 [Elasticsearch](http://www.elasticsearch.org/overview/elasticsearch/) 、 [Kibana](http://www.elasticsearch.org/overview/kibana/) を試すための環境を fig を使って構築してみた。が、環境構築で満足して fluentd や Kibana の使用感はまだ確認できてない。ちょっと欲張りすぎたかな。。。

なお、環境は Mac OS X Mavericks + boot2docker。

## fig のインストール

手っ取り早く [Homebrew](http://brew.sh/) でインストール。

```console
% brew install fig
% fig --version
fig 1.0.1
```

ちなみに Docker の環境は以下の通り。

```console
% docker version
Client version: 1.3.0
Client API version: 1.15
Go version (client): go1.3.3
Git commit (client): c78088f
OS/Arch (client): darwin/amd64
Server version: 1.3.0
Server API version: 1.15
Go version (server): go1.3.3
Git commit (server): c78088f
```

## 構成

今回は fig を使って以下の3つのコンテナを作成して連携させる。

* コンテナ名： **web**
  - Apache
  - fluentd
* コンテナ名： **elasticsearch**
  - Elasticsearch
* コンテナ名： **kibana**
  - Apache
  - Kibana

webコンテナがフロントエンドのサーバという想定。このサーバのアクセスログを fluentd を使って Elasticsearch へ送り Kibana で可視化する。

実際にプロダクトで使用する際はいきなり Elasticsearch へ転送するのではなく、間に集約する fluentd を入れる構成にするのがいいらしい。

## Dockerfile

それぞれのコンテナを作るための Dockerfile を以下のように作成した。
いずれも CentOS 6 をベースにしている。

### web コンテナ


fluentd は公式の [ドキュメント](http://docs.fluentd.org/articles/install-by-rpm "Installing Fluentd Using rpm Package") の通りにインストールする。

が、yum で Apache をインストールすると ``/var/log/httpd`` が ``root:root`` で作られるのだが、パーミッションを変えてもなぜか td-agent ユーザーから参照することができなかった。色々試してもよくわからなかったので、 [こちら](http://qiita.com/mitzi2funk/items/8ef7e3c2707124adc739#2-5 "職場でよく耳にするfluentdを試してみました") を参考に root で起動するように起動スクリプトを書き換えた。

fluentd には Elasticsearch 用のプラグインもインストール。依存する ``libcurl-devel`` と ``gcc`` は yum で入れておく。

```text:web/Dockerfile
FROM centos:centos6

# Install core modules
RUN yum update -y &&\
    yum install -y sudo tar &&\
    yum clean -y all

# Edit sudoers file
RUN sed -i -e "s/Defaults    requiretty.*/ #Defaults    requiretty/g" /etc/sudoers

# Install fluentd
RUN curl -L http://toolbelt.treasuredata.com/sh/install-redhat-td-agent2.sh | sh
RUN sed -i -e "s/--user td-agent/--user root/g" /etc/init.d/td-agent &&\
    sed -i -e "s/--group td-agent/--user root --group td-agent/g" /etc/init.d/td-agent
RUN ulimit -n 65536
COPY td-agent.conf /etc/td-agent/td-agent.conf

# Install Fluent::Plugin::Elasticsearch
RUN yum install -y libcurl-devel gcc &&\
    yum clean -y all
RUN /opt/td-agent/embedded/bin/gem install fluent-plugin-elasticsearch

# Install httpd
RUN yum install -y httpd &&\
    yum clean -y all

COPY run.sh /tmp/
EXPOSE 80
ENTRYPOINT ["/bin/bash", "/tmp/run.sh"]
```

Apache と fluentd の両方のサービスを起動させるため、以下のようなシェルスクリプトを作成してプロセスがフォアグラウンドで動き続けるようにしている。

```bash:web/run.sh
#!/bin/bash

service httpd start
service td-agent start

tail -f /var/log/td-agent/td-agent.log
```

fluentd の設定ファイルである ``td-agent.conf`` は、とりあえず以下のように作ってみた。

```aconf:web/td-agent.conf
<source>
  type tail
  path /var/log/httpd/access_log
  tag apache.web
  pos_file /var/log/td-agent/httpd-access.log.pos
  format apache2
</source>

<match apache.**>
  type copy

  <store>
    type stdout
  </store>

  <store>
    type elasticsearch
    host elasticsearch
    port 9200
  </store>

</match>
```

### elasticsearch コンテナ

``java`` と ``which`` が必要なので、事前に yum で入れておく。また環境変数 ``JAVA_HOME`` を設定し、 ``PATH`` も通しておく。

Elasticsearch も [ドキュメント](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/_installation.html "Install") の通りにインストールする。インストール先は ``/opt/elasticsearch`` とした。

クロスドメイン対策のため、 ``elasticsearch.yml`` に設定を追加している。

```text:elasticsearch/Dockerfile
FROM centos:centos6

# Install core modules
RUN yum update -y &&\
    yum install -y java-1.7.0-openjdk sudo tar which &&\
    yum clean -y all

# Edit sudoers file
RUN sed -i -e "s/Defaults    requiretty.*/ #Defaults    requiretty/g" /etc/sudoers

# Set JAVA_HOME
ENV JAVA_HOME /usr/lib/jvm/java-1.7.0-openjdk.x86_64
ENV PATH $JAVA_HOME/bin:$PATH

# Install elasticsearch
ENV ES_PKG_NAME elasticsearch-1.4.0
ADD https://download.elasticsearch.org/elasticsearch/elasticsearch/$ES_PKG_NAME.tar.gz /tmp/$ES_PKG_NAME.tar.gz
RUN cd /tmp &&\
    tar xzvf $ES_PKG_NAME.tar.gz &&\
    mv $ES_PKG_NAME /opt/elasticsearch &&\
    rm -f $ES_PKG_NAME.tar.gz
RUN echo 'http.cors.enabled: true' >> /opt/elasticsearch/config/elasticsearch.yml &&\
    echo 'http.cors.allow-origin: "/.*/"' >> /opt/elasticsearch/config/elasticsearch.yml

EXPOSE 9200
EXPOSE 9300

ENTRYPOINT ["/opt/elasticsearch/bin/elasticsearch"]
```

### kibana コンテナ

Kibana 3 はダウンロードして Apache のドキュメントルート以下に突っ込むだけで使えるようになる（ Kibana 4 からはちょっと構成が変わるらしい）。簡単。今回は ``/var/www/html/kibana`` へ配置した。

```text:kibana/Dockerfile
FROM centos:centos6

# Install core modules
RUN yum update -y &&\
    yum install -y sudo tar &&\
    yum clean -y all

# Edit sudoers file
RUN sed -i -e "s/Defaults    requiretty.*/ #Defaults    requiretty/g" /etc/sudoers

# Install httpd
RUN yum install -y httpd &&\
    yum clean -y all

# Install kibana
ENV KIBANA_PKG_NAME kibana-3.1.2
ADD https://download.elasticsearch.org/kibana/kibana/$KIBANA_PKG_NAME.tar.gz /tmp/$KIBANA_PKG_NAME.tar.gz
RUN cd /var/www/html &&\
    tar xzvf /tmp/$KIBANA_PKG_NAME.tar.gz &&\
    mv $KIBANA_PKG_NAME kibana

COPY run.sh /tmp/run.sh

EXPOSE 80
ENTRYPOINT ["/bin/bash", "/tmp/run.sh"]
```

一応 web コンテナと同様にシェルスクリプトを用意したが、動かすサービスは1つだけなので直接 Apache を起動しても構わない。

```bash:kibana/run.sh
#!/bin/bash

service httpd start

tail -f /var/log/httpd/access_log
```

## fig.yml

Dockerfile ができたので、次に fig の設定ファイルを作成する。設定は YAML で書く。 [リファレンス](http://www.fig.sh/yml.html "fig.yml reference") を参考にして以下のような ``fig.yml`` を作成した。

```yaml:fig.yml
---
elasticsearch:
  build: elasticsearch
  ports:
    - "9200:9200"
    - "9300:9300"
kibana:
  build: kibana
  ports:
    - "50080:80"
  links:
    - elasticsearch
web:
  build: web
  ports:
    - "10080:80"
    - "18888:8888"
  links:
    - elasticsearch
```

``build`` で Dockerfile のあるディレクトリを指定する。エクスポートするポートは ``ports`` で、リンクしたいコンテナは ``links`` でそれぞれ指定できる。 fig を使わないで起動しようとすると、 ``docker run`` のオプションに自分で指定する必要があるが、 fig なら簡単に設定できてGood。

## コンテナの起動

Dockerfile をビルドしてイメージを作る。

```console
% fig build
Building elasticsearch...
 ---> 70441cac1ed5
Step 1 : RUN yum update -y &&    yum install -y java-1.7.0-openjdk sudo tar which &&    yum clean -y all

(...snip...)

Successfully built c9747896561b
```

これだけですべての Dockerfile がビルドされる。便利。

そして起動。こちらも簡単一発。

```console
% fig up
Recreating fluentdsample_elasticsearch_1...
Recreating fluentdsample_web_1...
Recreating fluentdsample_kibana_1...
Attaching to fluentdsample_elasticsearch_1, fluentdsample_web_1, fluentdsample_kibana_1
elasticsearch_1 | [2014-11-22 17:43:57,222][INFO ][node                     ] [Eros] version[1.4.0], pid[1], build[bc94bd8/2014-11-05T14:26:12Z]
elasticsearch_1 | [2014-11-22 17:43:57,223][INFO ][node                     ] [Eros] initializing ...
elasticsearch_1 | [2014-11-22 17:43:57,228][INFO ][plugins                  ] [Eros] loaded [], sites []
web_1           | httpd: Could not reliably determine the server's fully qualified domain name, using 172.17.0.198 for ServerName
kibana_1        | httpd: Could not reliably determine the server's fully qualified domain name, using 172.17.0.200 for ServerName
web_1           | Starting httpd: [  OK  ]
kibana_1        | Starting httpd: [  OK  ]
web_1           | Starting td-agent: [  OK  ]

(...snip...)
```

それぞれのコンテナの出力が標準出力に吐かれる。

## 動作確認

boot2docker を使用しているので、VMのIPアドレスを確認しておく。

```console
% boot2docker ip

The VM's Host only interface IP address is: 192.168.XX.YYY

```

それぞれのコンテナへアクセスしてみる。

Elasticsearch コンテナ：

```console
% curl -s "http://192.168.XX.YYY:9200/"
{
  "status" : 200,
  "name" : "Eros",
  "cluster_name" : "elasticsearch",
  "version" : {
    "number" : "1.4.0",
    "build_hash" : "bc94bd81298f81c656893ab1ddddd30a99356066",
    "build_timestamp" : "2014-11-05T14:26:12Z",
    "build_snapshot" : false,
    "lucene_version" : "4.10.2"
  },
  "tagline" : "You Know, for Search"
}
```

web コンテナ：

```console
% curl -s -I "http://192.168.XX.YYY:10080/"
HTTP/1.1 403 Forbidden
Date: Sat, 22 Nov 2014 17:52:14 GMT
Server: Apache/2.2.15 (CentOS)
Accept-Ranges: bytes
Content-Length: 4954
Connection: close
Content-Type: text/html; charset=UTF-8
```

kibana コンテナ：

```console
% curl -s -I "http://192.168.XX.YYY:50080/kibana/"
HTTP/1.1 200 OK
Date: Sat, 22 Nov 2014 17:53:52 GMT
Server: Apache/2.2.15 (CentOS)
Last-Modified: Fri, 07 Nov 2014 15:15:25 GMT
ETag: "cb-819-507464842dd40"
Accept-Ranges: bytes
Content-Length: 2073
Connection: close
Content-Type: text/html; charset=UTF-8
```

それぞれ動作しているようだ。

またwebブラウザから ``http://192.168.XX.YYY:50080/kibana/`` へアクセスすると、 Kibana の画面がきちんと表示された。 Elasticsearch との連携がうまくいっていないと、エラー画面が表示される。

## まとめ

複数の Dockerfile を組み合わせて使う場合に fig はとても便利。コンテナの連携がしやすくなるので、できるだけコンテナの役割を分けて1つ1つのコンテナの役割を明確にしていきたい。