Ansible を使って EC2 に ELB アクセスログ分析（Fluentd + ElasticSearch + Kibana4 + Nginx）を立ち上げる

## バージョン＆記載ソースリポジトリ

| version | 
|:---|
| ansible 1.9.0.1 |

ポイントとなるソースのみ載せています。全てのソースが見たい場合は、以下のリポジトリを参照してください。

* https://github.com/katsuhiko/ec2-playbooks

Ansible で構築したサーバーのアプリの各バージョン

| version |
|:---|
| elasticsearch 1.6.0 |
| td-agent 0.12.7 |
| kibana 4.1.0 |
| nginx 1.6.2 |


## 概要

ELB のアクセスログを Kibana で分析するサーバーをすぐに構築できるように Ansible playbook を作成しました。
前回の phpMyAdmin サーバー playbook の教訓を生かして、今回はロールを細かく定義しました。

## ELBのアクセスログを S3 へ保存

* ELB のアクセスログを S3 へ保存する方法
    * http://dev.classmethod.jp/cloud/aws/elb-featured-accesslog/

ここを参考に ELB のアクセスログを S3 に保存するようにしました。
他にも参考はたくさんありました。


## ElasticSearch のインストール

yum モジュールで rpm を直接インストールすることができました。
これで elasticsearch を簡単にインストールできます。
設定は、デフォルトのままを使っているので、templates, defines は特に定義していません。
ここら辺は、実際に使って問題が発生したら調整する予定です。

```yaml:roles/ec2-elasticsearch/tasks/main.yml
---
- name: Install elasticsearch
  yum: name="https://download.elastic.co/elasticsearch/elasticsearch/elasticsearch-1.6.0.noarch.rpm" state="present"
  notify: restart elasticsearch

- name: Set elasticsearch service to start on boot
  service: name=elasticsearch enabled=true
```

## Fluentd のインストール

Ansible Galaxy を見てると、install-redhat-td-agent2.sh の中でやってる処理を Ansible で記述する（Fluentd のインストールはリポジトリを追加して、yum でインストールする）のが正しいやり方みたいです。

ただ、、、今回は、install-redhat-td-agent2.sh を使う方法で書きました。
最近、この curl で shell をとってきて実行してインストールするタイプが増えてる気がするので、この手順を Ansible で書く方法を理解しておきたかったからです。
今の手順での欠点は、playbook を実行するたびに Ansible に「changed」判定されてしまうことです。
良い方法は、、、思いついてません。

ELB のアクセスログを収集するプラグインは２つありますが、

* https://github.com/shinsaka/fluent-plugin-elb-log
* https://github.com/winebarrel/fluent-plugin-elb-access-log

今回は、 fluent-plugin-elb-access-log を利用しました。
強い思いがあったわけではなく、まずは、こちらで試してみようっという感じです。
この辺も使っていくうちに変えると思います。

fluent-plugin-elb-access-log 0.2.4 と AWS SDK 2.1.1 （2015/06/21 時点最新）組み合わせだとエラーが発生していたので、 AWS SDK 2.0.48 を入れるようにしています。

証明書に関するエラーが発生したので、以下サイトを参考にして、証明書を置き換える処理を追加しました。

* http://qiita.com/toshihirock/items/a90badb49e648af92d35

```yaml:roles/ec2-fluentd/tasks/main.yml
---
- name: Download fluentd
  get_url: url="https://td-toolbelt.herokuapp.com/sh/install-redhat-td-agent2.sh" dest="/tmp/install-redhat-td-agent2.sh" mode=0755

- name: Install fluentd
  shell: /tmp/install-redhat-td-agent2.sh

- name: Set fluentd service to start on boot
  service: name=td-agent enabled=true

# 2015.06.27 fluent-plugin-elb-access-log が aws-sdk 2.1系に対応したので、aws-sdk 2.0系をインストールする処理は入りません！！
#- name: Install the aws-sdk (In version specified because there is a bug)
#  gem: name={{ item }} version=2.0.48 executable="/opt/td-agent/embedded/bin/fluent-gem" user_install=false
#  with_items:
#    - aws-sdk
#    - aws-sdk-core
#    - aws-sdk-resources

- name: Install fluentd-plugins
  gem: name={{ item }} state=latest executable="/opt/td-agent/embedded/bin/fluent-gem" user_install=false
  with_items:
    - fluent-plugin-elasticsearch
    - fluent-plugin-elb-access-log
  notify: restart fluentd

- name: Put td-agent.conf
  template: src=td-agent.conf.j2 dest=/etc/td-agent/td-agent.conf backup=true mode=0644
  notify: restart fluentd

- name: Check replaced the certificate
  stat: path=/opt/td-agent/embedded/ssl/certs/cacert.pem.original
  register: cert_pem_original

- name: Backup the certificate
  command: cp /opt/td-agent/embedded/ssl/certs/cacert.pem /opt/td-agent/embedded/ssl/certs/cacert.pem.original
  when: not cert_pem_original.stat.exists

- name: Replace the certificate
  command: cp /etc/pki/tls/cert.pem /opt/td-agent/embedded/ssl/certs/cacert.pem
  when: not cert_pem_original.stat.exists
```

fluent-plugin-elb-access-log の設定で、AWSに関連した情報が必要になります。

```py:roles/ec2-fluentd/templates/td-agent.conf.j2
...
{% for source in fluentd_sources %}
<source>
  type elb_access_log
  aws_key_id {{ source.aws_key_id }}
  aws_sec_key {{ source.aws_sec_key }}
  account_id {{ source.account_id }}
  region {{ source.region }}
  s3_bucket {{ source.s3_bucket }}
  s3_prefix {{ source.s3_prefix }}
  tag elb.access_log
  #tsfile_path /var/tmp/fluent-plugin-elb-access-log.ts
  #histfile_path /var/tmp/fluent-plugin-elb-access-log.history
  #interval 300
  start_datetime {{ source.start_datetime }}
  #buffer_sec 600
  #history_length 100
  #sampling_interval 1
  debug true
</source>
{% endfor %}

<match elb.access_log>
  type elasticsearch
  type_name access_log
  host localhost
  port 9200
  logstash_format true
  include_tag_key true
  tag_key @log_name
</match>
...
```

ぼくは アクセスキー／シークレットキーのために IAM でユーザーを作りました。
適用したポリシーは以下です。
Action は参照系の処理のみに絞るのが適切だと思いますが、どの Action のみにすればよいかがわからくて、 S3:* にしてしまいました。。。

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "Stmt999999999999",
            "Effect": "Allow",
            "Action": [
                "s3:*"
            ],
            "Resource": [
                "arn:aws:s3:::バケット名",
                "arn:aws:s3:::バケット名/*"
            ]
        }
    ]
}
```


## Nginx のインストール

Kibana4 は単体でも動作しますが、HTTPS にして、Basic 認証をかけたかったので Nginx でリバースプロキシさせます。
そうすることで、AWS のセキュリティグループは、HTTP(80), HTTPS(443)の２つのみのポートを解放すれば良くなります。
Kibana4 のデフォルトポート 5601 を解放する必要はありません。
（ちなみに、、、Nginx を利用しなくても、ElasticSearch のデフォルトポート 9200 を解放する必要はありません。）

Nginx は yum でインストールできるので簡単です。
SSL を利用するための自己証明書（オレオレ証明書）も以下を参考に作っています。

* http://qiita.com/hnakamur/items/d6c93442ce28fb2e9aad

```yaml:roles/ec2-nginx/tasks/main.yml

---
- name: Install nginx
  yum: name=nginx state=latest

- name: Set nginx service to start on boot
  service: name=nginx enabled=true

- name: Put nginx.conf
  template: src=nginx.conf.j2 dest=/etc/nginx/nginx.conf backup=true mode=0644
  notify: restart nginx

- name: Create self signed certificate
  shell: |
    mkdir -p /etc/nginx/certs &&
    crt_file="/etc/nginx/certs/local-ssl.crt" &&
    key_file="/etc/nginx/certs/local-ssl.key" &&
    crt_and_key_file="/etc/nginx/certs/local-ssl.crt_and_key" &&
    subject="/C=JP/ST=Osaka/L=Osaka City/CN=local-ssl" &&
    openssl req -new -newkey rsa:2048 -sha1 -x509 -nodes \
      -set_serial 1 \
      -days 3650 \
      -subj "$subject" \
      -out "$crt_file" \
      -keyout "$key_file" &&
    cat "$crt_file" "$key_file" >> "$crt_and_key_file" &&
    chmod 400 "$key_file" "$crt_and_key_file"
    creates="/etc/nginx/certs/local-ssl.crt"
```


## Kibana4 のインストール

Kibana4 が出ているので、3 ではなく 4 を使います。
Kibana のインストールは、tar.gz をダウンロードして解凍するタイプです。
command や shell モジュールを使いたくなってきましたが、ぐっとこらえて、

* ダウンロードは get_url モジュール
* 解凍は unarchive モジュール (copy=no でリモート内のファイルを解凍するように指定するのがポイント)
* シンボリックリックは file モジュール

の各モジュールを使うようにしました。急いでいるときには command, shell を使って良いと思うけど、時間があるときにはなるべく各モジュールを使ったほうが、ベキトウセイの考慮が楽になるので良いと思います。

init script については、以下サイトを参考にしました。
これで、service コマンドを使ってスタート／ストップ／リスタートが行えます。
どのサービスでも同じ手順で操作できることは重要だと思います。

* http://orih.io/2015/03/kibana-4-init-script-for-amazon-linux/

Basic 認証をするための htpasswd 作成は、htpasswd モジュールを使っています。
ちょくちょく Basic 認証をかけたいことがあるので、htpasswd が簡単に作れることがわかったのはかなりの収穫です。

ec2-kibana のロールは、ec2-nginx に依存していますが、meta は書いていません。書いたほうがよいことはわかってますが、なくても動いているので、後回しにしています。

```yaml:roles/ec2-kibana/tasks/main.yml
---
- name: Download kibana
  get_url: url="https://download.elastic.co/kibana/kibana/kibana-4.1.0-linux-x64.tar.gz" dest="/tmp/kibana-4.1.0-linux-x64.tar.gz" mode=0644

- name: Unarchive kibana
  unarchive: src=/tmp/kibana-4.1.0-linux-x64.tar.gz dest=/opt/ copy=no owner=root group=root

- name: Install kibana
  file: src=/opt/kibana-4.1.0-linux-x64 dest=/opt/kibana state=link

- name: Put init script for kibana
  template: src=kibana.j2 dest=/etc/init.d/kibana mode=0755
  notify: restart kibana

- name: Set kibana service to start on boot
  service: name=kibana enabled=true

- name: Install passlib for creating htpasswd
  pip: name=passlib state=latest
  when: kibana_basic_enabled

- name: Create htpasswd
  htpasswd: path=/etc/nginx/conf.d/kibana.htpasswd name={{ kibana_basic_user_name }} password={{ kibana_basic_user_password }} create=yes state=present
  when: kibana_basic_enabled

- name: Put nginx.kibana.conf
  template: src=nginx.kibana.conf.j2 dest=/etc/nginx/conf.d/kibana.conf mode=0644
  notify: restart nginx
```

Kibana 用の Nginx の設定です。
HTTP を HTTPS へリダイレクトして、Basic 認証を行えるようにしました。
これで最低限のセキュリティを保ちますが、できれば、さらにセキュリティグループで IP制限をかけたほうが良いと思います。

```py:roles/ec2-kibana/templates/nginx.kibana.conf.js
# {{ ansible_managed }}

# redirect https from http
server {
  listen 80;

  location ~ ^/kibana/.* {
    return 301 https://$host$request_uri;
  }
}

# proxy kibana
server {
  listen 443;

  access_log /var/log/nginx/kibana.access.log;
  error_log /var/log/nginx/kibana.error.log;

  ssl on;
  ssl_certificate /etc/nginx/certs/local-ssl.crt;
  ssl_certificate_key /etc/nginx/certs/local-ssl.key;

  location ~ ^/kibana/.* {
    proxy_pass http://localhost:5601;
    rewrite ^/kibana/(.*) /$1 break;
    proxy_set_header Host $host;
    proxy_set_header  X-Forwarded-For $proxy_add_x_forwarded_for;

{% if kibana_basic_enabled %}
    auth_basic "Restricted";
    auth_basic_user_file /etc/nginx/conf.d/kibana.htpasswd;
{% endif %}
  }
}
```

各ロールの作成は以上です。

## Playbook サンプルと実行

必要な role を読み込みます。
meta で依存関係を定義していないので、roles: の記述順を変えると動かないと思います。

```yaml:ec2-kibana.example.yml
- hosts: all
  sudo: yes
  vars:
    fluentd_sources:
      - aws_key_id: XXXXXXXXXXXX
        aws_sec_key: XXXXXXXXXXXXXXXXXXXXXXXX
        region: ap-northeast-1
        account_id: 999999999999
        s3_bucket: bucketname
        s3_prefix: prefix
        start_datetime: 2015/06/01 00:00
    kibana_basic_enabled: true
    kibana_basic_user_name: kibana
    kibana_basic_user_password: kibanapassword
  roles:
    - ec2-elasticsearch
    - ec2-fluentd
    - ec2-nginx
    - ec2-kibana
```

以下で inventory ファイルなしで実行できます。
ぼくは inventory ファイルなしで実行してばかりですが、本格的な運用をしてないからでしょうね。

```
$ ansible-playbook -i "xxx.xxx.xxx.xxx," --user=ec2-user --private-key=xxx.pem ec2-kibana.example.yml
```

playbook の適用が完了したら、以下の URL でアクセスできます。
```
https://xxx.xxx.xxx.xxx/kibana/
```

ここまでで環境はできますが、これからがログ解析の本番です。如何に Kibana4 を使いこなすか、です。
使いこなすことのほうが時間がかかりそうです。


## 感想

いろんなインストール方法を記述することになりました。あとはコンパイルが必要なタイプぐらいだと思います。
このことは、今後も Ansible を使うときに役に立ちそうです。

この Ansible の書き方だと今は動きますが、来年でも問題なく動くかはわかりません。
急ぎで構築したいときに動かないっといのは辛いです。
ServerSpec 等のテストに着手して、この点をどこまでカバーできるかを調べたいです。