log 用の elasticsearch cluster の構築

kibana4-BETA2とelasticsearch-1.4をさわってみたくてよくある構成をaws上で構築したときのメモ。
とりあえずインストールとちょっと設定くらいまで。環境はubuntu 12.04

インスタンス選択
---

+ elasticsearchはHEAPにメモリの50%以下割当推奨
+ kibana4になってからaggregationを使ったリクエストになっているのでcpuよりメモリの制約のほうが大きそう
+ application logを流す予定で実測80Mbpsくらいしか出ないネットワークパフォーマンス中のインスタンスはきつそう

といわけで、様子見でm3.xlargeを横に並べる構成に。

disk周り
---

+ EBS上に構築。diskioを考えるとそれなりに大きくしたほうが良さそう
+ あとでdisk を非停止で追加したい（この時点ではdisk性能とか容量が問題になるかもしれないと考えていたので）

lvmの下に1GのEBSを束ねる形に。
ちなみにkernel 3.2.54辺りにxfsを拡張できない系のバグがあるので、xfs使う場合はkernelのversionを少し気にしたほうがいいかも。


```
> sudo apt-get install lvm2
> sudo pvcreate /dev/xvdf
> sudo vgcreate vg1 /dev/xvdf
> sudo lvcreate -L 999.99G -n lv1 vg1
> sudo apt-get install xfsprogs
> sudo mkfs.xfs /dev/vg1/lv1
> sudo mount -o "discard,noatime,nodiratime,nobarrier" /dev/vg1/lv1 /mnt
> sudo sh -c "echo '/dev/mapper/vg1-lv1 /mnt xfs discard,noatime,nodiratime,nobarrier 0 0' >> /etc/fstab"
```

elasticsearchのインストール
---

+ java7が必須
+ あとはdebで1.4をインストール `sudo dpkg -i elasticsearch-1.4.0.deb`
+ 定番のpluginをいくつか
  + `sudo /usr/share/elasticsearch/bin/plugin --install lukas-vlcek/bigdesk`
  + `sudo /usr/share/elasticsearch/bin/plugin --install mobz/elasticsearch-head`
  + `sudo /usr/share/elasticsearch/bin/plugin --install elasticsearch/elasticsearch-analysis-kuromoji/2.4.1`
  +  `sudo /usr/share/elasticsearch/bin/plugin --install river-csv -url https://github.com/AgileWorksOrg/elasticsearch-river-csv/releases/download/2.1.1/elasticsearch-river-csv-2.1.1.zip`

configの修正
---

`/etc/elasticsearch/elasticsearch.yml`の修正箇所は

+ cluster.name
  + なんか名前つけておくとよろし
+ node.name
  + awsならホスト名とかで問題なし
+ index.number_of_shards
  + 一つのインデックスのシャードの数のデフォルト値。
  + 一度作ったインデックスは変更できないので今回は10に
  + logstash形式のインデックスにするなら翌日分から変更は可能
+ index.number_of_replicas
  + 最初0にしていたけど、問題が発生して後1に。
  + 0のままだとインスタンスを退役させるときにshardを非停止で移動する方法が見つからなかった...
  + _cluster/reroute の moveは移動後、リバランシングが走って他のshardが戻ってきてしまった
+ path.data
+ discovery.zen.ping.multicast.enabled: false
  + 指定してないclusterに勝手にjoinするのはあまり好みではなかったため...
+ discovery.zen.ping.unicast.hosts: ["xxx.xxx.xxx.xx"]
  + elasticsearch間の通信は9300
+ init.dの修正
  + debでいれると、`ES_HEAP_SIZE`がべた書きしてあるので、そこを/etc/defaultから読むとか直接書き換えるとか好きに修正
  + 利用可能なメモリ量の50%以下で31GB以下らしい

これでいちお起動したら勝手に通信開始

security group
----

+ 9200と9300が疎通できればOK
+ 9300はnode間の通信で、9200は外からAPIやweb plugin表示するときに使う。
+ 9200はELBの下にぶら下げることにした

fluentd
----

+ logstash形式でlogをelasticsearchに
+ 既存のログの構造を変えられないとかよくある制約のおかげで、アプリケーション内でobject dumpした物をfluentdのpluginでparseして構造化

この形で何も考えずにログを流すとobjectが非常に深い構造だったためインデキシングのコストが高くすぐにelasticsearchがつまってしまった。
そのため、必要な要素だけインデキシング対象にしておき、必要に応じてインデキシング対象を増やす形に...

template (elasticsearchの機能)
---

+ データをインサートするタイミングで適応するインデックスのマッピングを設定する機能
  + インデックスが期間で切り替わるlogstash形式と非常に相性が良さそうだった
+ インサートするインデックスの名前が正規表現で書けるtemplate判別文字列と一致したらそのtemplateが適応される
  + `logstash-*`とかにしておくと、`logstash-2014.12.19`のようなlogstash形式のインデックスとマッチする

```template.json
{
  "template" : "logstash-*", # ここにマッチしたら適応される
  "order" : 10, # orderは正規表現の適応優先順位
  "settings" : {
    "index" : {
      "number_of_replicas" : 1 # あとで変更したくなったらここにも書いておくことも可能
    }
  },
  "mappings" : {
    "production_log" : { # 自分で設定したtype名
      "properties" : {
        "@timestamp" : { # fluent-plugin-elasticsearchのdefalutだと時刻はこの形
          "type" : "date",
          "format" : "dateOptionalTime"
        },
        ### --- ここから先はそれぞれの構造 --- ###
        ### とりあえずよくありそうな形で埋めておく
        "ua" : {
          "type" : "string"
          "index" : "not_analyzed"
          # 検索ではなくkibanaを使ったVisuarizationが主目的の場合はnot_analyzedのほうが無難
          # term aggragationを使ったりしたら分かち書きしたものごとに重複して描画されたりする
        },
        "result" : {
          "type" : "integer"
        },
        "path" : {
          "type" : "string",
          "index" : "not_analyzed"
        },
        "params" : {
          "dynamic": false, #これでここより下の階層は一致する要素だけインデックスできる模様
          "properties" : {
            "user_id" : {
              "type" : "integer"
            },
            "firneds" : {
              "dynamic": false, # さらに深い階層も面倒だけどこんな感じで...
              "properties" : {
                "count" : {
                  "type" : "integer"
                }
              }
            },
            "map" : {
              # 緯度経度を格納するgeo_pointというインデックスタイプもある
              # kibana4からgeo_pointインデックスは地図上にマッピングすることも可能
              # valueは{ "lat" : "35.7056396", "lon" : "139.7518913"} の決まった形でいれる必要がある
              # ここ気づくのに時間がかかった...
              "type" : "geo_point",
              "geohash" : true,
              "geohash_prefix" : true
            }
          }
        },
        "error" : {
          "type" : "string"
          ## エラーのように検索したいものは検索対象に
          ## 日本語を入れる場合はanalyzerにkuromojiとか指定する
        }
        ### --- ここまで --- ###
      }
    }
  }
}
```

mappingを入れると、各要素が存在しないのはOKで、指定していないものがきたらOUTなので、上の階層はある程度fixして
objectをdumpしたようなどんな値がくるかわからない物はparamsの下なんかに入れておくと便利だった

これをelasticsearchにいれるのは

```
curl -XPUT http://localhost:9200/_template/logstash -d @template
```

これでとりあえず準備完了。
ログが流れてきだすとインデックスが指定されたテンプレートの形で格納されました。

パフォーマンス
---

+ テンプレートを使ったインデックス運用にした結果、例より複雑なマッピングを指定しても1台で秒間1000行とかは問題なく処理できている模様
+ ただログの保存期間が長くなってくると、今度はkibana上から飛んでくるrangeの広かったり、aggregationのインターバルが細かいsearchリクエストでelasticsearchが殺される問題が...
+ とりあえずは横に並べて問題回避
  + できればHEAPを食い尽くすようなリクエストをkillする仕組みがないか検討したい