HAProxyでHA化したElasticsearchクラスタを構築

この記事は[TUT CDSL Advent Calendar 2019 - Adventar](https://adventar.org/calendars/4281)の18日目です．

# はじめに

研究室内にあるESXi上にElasticsearchのクラスタを構築しました．
先生から冗長化を考慮した構築を提案されたので，SPOFの排除を目標としました．

設定ファイルは以下においてあります．

https://github.com/cdsl-research/elasticsearch-cluster

# VMのスペック

[ドキュメント](https://www.elastic.co/guide/en/elasticsearch/guide/current/hardware.html)を調べたところ以下のスペックが最小限でした．


- メモリ: 8GB以上(快適に使うなら16GB)
- CPU: 2-8コア
- ストレージ: SSDのような早いやつを使う(15k RPM)

上記を参考に以下を割り当てました．

- ロードバランサ: 2台
    - CPU: 2 [core]
    - RAM: 2 [GB]
    - Storage: 50[GB]
- Elasticsearchノード: 4台
    - CPU: 4 [core]
    - RAM: 12 [GB]
    - Storage: 120[GB]

![vm-spec](https://user-images.githubusercontent.com/2428176/71238351-c5ddbb00-22fb-11ea-99b2-215b635f8506.png)

# ロードバランサVM

## HAProxy

ロードバランサにはHAProxyを使用しました．HAProxyはヘルスチェックの条件を細かく設定可能であり，インターネット上に情報が多くあるため採用しました．また，MetricbeatによりHAProxyの統計を収集して，Elasticsearchへ送信しました．

以下はHAProxyのElasticsearch向け設定です．

```toml
##
# Elasticsearch
##

defaults
    timeout connect  5000
    timeout client  10000
    timeout server  10000

frontend elastic
    bind :9200
    mode http
    acl is_delete method DELETE
    http-request deny if is_delete
    default_backend elastic

backend elastic
    mode http
    option forwardfor
    balance roundrobin
    option httpclose
    server elasticsearch-j1 elasticsearch-j1.a910.tak-cslab.org:9200 weight 1 check inter 1000 rise 5 fall 1
    server elasticsearch-j2 elasticsearch-j2.a910.tak-cslab.org:9200 weight 1 check inter 1000 rise 5 fall 1
    server elasticsearch-m1 elasticsearch-m1.a910.tak-cslab.org:9200 weight 1 check inter 1000 rise 5 fall 1
    server elasticsearch-m2 elasticsearch-m2.a910.tak-cslab.org:9200 weight 1 check inter 1000 rise 5 fall 1
```

## keepalived

ロードバランサの冗長化のためにkeepalivedを導入しました．ホットスタンバイであるとリソース効率が悪いため，常に2台を稼働させています．以下の記事を参考にしました．

[keepalivedによるサーバ冗長化について | GMOアドパートナーズグループ TECH BLOG byGMO](https://techblog.gmo-ap.jp/2016/12/26/keepalived%E3%81%AB%E3%82%88%E3%82%8B%E3%82%B5%E3%83%BC%E3%83%90%E5%86%97%E9%95%B7%E5%8C%96%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6/)

```
! Configuration File for keepalived
vrrp_script chk_haproxy {
    script "systemctl is-active haproxy"
}

vrrp_instance VI_1 {
    state MASTER
    ! もう1台では以下を50に設定
    priority 100
    interface ens160
    garp_master_delay 10
    virtual_router_id 31
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass XXXXXXXXXX
    }
    virtual_ipaddress {
        192.168.100.10
    }
}

vrrp_instance VI_2 {
    state BACKUP
    ! もう1台では以下を100に設定
    priority 50
    interface ens160
    virtual_router_id 32
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass XXXXXXXXXX
    }
    virtual_ipaddress {
        192.168.100.11
    }
    track_script {
        chk_haproxy
    }
}
```

さらに，単一のFQDNで複数のリソースを参照可能なようDNSラウンドロビンを導入しました．

```shell
$ nslookup elasticsearch-edge
192.168.100.11
$ nslookup elasticsearch-edge
192.168.100.10
```

# ElasticsearchノードVM

各ノードごとにKibana, Elasticsearch, Logstashを導入しました．Logstashでは*BeatsからのデータとSyslogからのデータを許可しています．

![architecture](https://user-images.githubusercontent.com/2428176/71238360-c9714200-22fb-11ea-9205-f4e4b8e2898b.png)

## Elasticsearch

各ノードはMaster Eligible NodeとData Nodeを兼ねています．

```
cluster.name: logging-main
node.name: ${HOSTNAME}
node.master: true
node.data: true
path.data: /var/lib/elasticsearch
path.logs: /var/log/elasticsearch
bootstrap.memory_lock: true
network.host: 0.0.0.0
http.port: 9200
discovery.seed_hosts:
- elasticsearch-m1.a910.tak-cslab.org
- elasticsearch-m2.a910.tak-cslab.org
- elasticsearch-j1.a910.tak-cslab.org
- elasticsearch-j2.a910.tak-cslab.org
cluster.initial_master_nodes:
- elasticsearch-m1
- elasticsearch-m2
- elasticsearch-j1
- elasticsearch-j2

http.cors.enabled: true
http.cors.allow-origin: "*"
```

## Logstash

SyslogをLogstashで受けるよう以下のConfig(抜粋)を作成しました．

```
input {
  tcp {
    port => 5000
    type => syslog
  }
  udp {
    port => 5000
    type => syslog
  }
}

filter {
  if [type] == "syslog" {
    grok {
      patterns_dir => ["/etc/logstash/pattern.d"]
      match => { "message" => "%{MYSYSLOG}" }
      add_field => [ "received_at", "%{@timestamp}" ]
      add_field => [ "received_from", "%{host}" ]
    }
    date {
      match => [ "syslog_timestamp", "MMM  d HH:mm:ss", "MMM dd HH:mm:ss" ]
    }
  }
}

output {
  if [type] == "syslog" {
    elasticsearch {
      hosts => ["localhost:9200"]
      index => "syslog-%{+YYYY.MM.dd}"
    }
  }
}
```

grokパターンは以下です．

```
MYSYSLOG %{SYSLOGTIMESTAMP:syslog_timestamp} %{SYSLOGHOST:syslog_hostname} %{DATA:syslog_program}(?:\[%{POSINT:syslog_pid}\])?: %{GREEDYDATA:syslog_message}
```

# 参考リンク

- [Elasticsearch クラスタ概説](https://gist.github.com/yano3/3f5abc9eba0c1ad6a0508056961b273c)
- [Hardware | Elasticsearch: The Definitive Guide [2.x] | Elastic](https://www.elastic.co/guide/en/elasticsearch/guide/current/hardware.html)