elasticsearch on Kubernetes(Docker for Mac)

## はじめに

[ [pires/kubernetes-elasticsearch-cluster - Github](https://github.com/pires/kubernetes-elasticsearch-cluster) ] を参考にelasticsearchクラスタを立てます。
Kubernetesを使用して拡張性を高く(スケールしやすい等)、
冗長化とx-pack無しのBasic認証導入とデータの永続化までやります。
デフォルトの設定を加えるためGCPのelasticsearchイメージに設定ファイルやプラグインのインストールコマンド、環境変数を加えたものを使用します。

## 動作確認
- Docker CE for Mac(Edge): Version 18.02.0
- Kubectl: Version 1.8
- elasticsearch: Version 5.6

現時点(2018年2月)ではkubernetesを使うためDocker for Mac edge版を使います。
インストールしていない場合は[Docker for Mac with Kubernetes - Qiita](https://qiita.com/taishin/items/920d62a641c9cd58f289)等を参考に

## elasticsearchのノードの概要
[Elasticsearch クラスタ概説](https://gist.github.com/yano3/3f5abc9eba0c1ad6a0508056961b273c) より  

> - client  
> Master (eligible) node でも data node でもないノード  
各種リクエストを受けるだけのロードバランサー的な何か
> - data  
> シャードを保管するノード  
データにまつわる操作 (CRUD, search, etc...) を扱う
> - master (eligible)  
> Master node はクラスタ全体の処理（シャードの配置等）を行うノード  
Master eligible node は master に候補になるノード  
Master node が死んだときには master eligible node から新たな master が選ばれる

この3つの役割ごとにpodを分けて作成します。

## イメージの準備

### 設定ファイルの用意
elasticsearchの設定ファイル等を用意します。

```
$ mkdir elasticsearch_k8s
$ cd elasticsearch_k8s
$ mkdir config; cd $_
```

作成したconfigディレクトリの中に`elasticsearch.yml`と`log4j2.properties`ファイルを作成します。

```yaml:elasticsearch.yml
http.host: 0.0.0.0
transport.host: 0.0.0.0

cluster:
  name: elasticsearch

node:
  master: ${NODE_MASTER}
  name: ${NODE_NAME}
  data: ${NODE_DATA}
  ingest: ${NODE_INGEST}
  max_local_storage_nodes: ${MAX_LOCAL_STORAGE_NODES}
network.host: ${NETWORK_HOST}

bootstrap:
  memory_lock: false

discovery:
  zen:
    ping.unicast.hosts: ${DISCOVERY_SERVICE}
    minimum_master_nodes: ${NUMBER_OF_MASTERS}
```

ノードの名前などはクラスタの中で一意である必要がありますが、ここで環境変数を使って設定することで全てのノードで同じファイルを使うことができます。

```properties:log4j2.properties
status = error
appender.console.type = Console
appender.console.name = console
appender.console.layout.type = PatternLayout
appender.console.layout.pattern = [%d{ISO8601}][%-5p][%-25c{1.}] %marker%m%n
rootLogger.level = info
rootLogger.appenderRef.console.ref = console
```

ログに関する設定で、これは[GCPのイメージ](https://github.com/GoogleCloudPlatform/elasticsearch-docker/blob/master/5/5.6/config/log4j2.properties)と同じです。

    $ tree
    config
    ├── elasticsearch.yml
    └── log4j2.propertie

configディレクトリのなかにこの2ファイルが無いとelasticsearchは動きませんでした…。
今回はビルドの中に入れてしまいましたが、開発時などこまめに変更して挙動を確認したい場合はKubernetesのconfigMapとして付けてしまうのも良いかもしれません。というかそちらのほうが正しいのかもしれません？

### Dockerイメージ作成
元々はGKEで立てていたため、GCPのランチャーからElasticsearch 5のイメージを使用しています。

```Dockerfile:Dockerfile
FROM launcher.gcr.io/google/elasticsearch5:5.6

MAINTAINER cmmmli

RUN yes | bin/elasticsearch-plugin install analysis-kuromoji
RUN yes | bin/elasticsearch-plugin install analysis-icu
RUN yes | bin/elasticsearch-plugin install org.codelibs:elasticsearch-analysis-kuromoji-neologd:5.6.1

ADD config /usr/share/elasticsearch/config/

ENV ES_JAVA_OPTS "-Xms256m -Xmx256m"
ENV CLUSTER_NAME elasticsearch
ENV NODE_MASTER true
ENV NODE_DATA true
ENV NODE_INGEST true
ENV HTTP_ENABLE true
ENV NETWORK_HOST _site_
ENV HTTP_CORS_ENABLE true
ENV HTTP_CORS_ALLOW_ORIGIN *
ENV NUMBER_OF_MASTERS 1
ENV MAX_LOCAL_STORAGE_NODES 1
ENV SHARD_ALLOCATION_AWARENESS ""
ENV SHARD_ALLOCATION_AWARENESS_ATTR ""
ENV MEMORY_LOCK false
ENV DISCOVERY_SERVICE elasticsearch-discovery
```
このDockerfileをconfigディレクトリと同じレベルに置いておきましょう。
プラグイン(kuromojiやneologd)のインストールとelasticsearchの設定ファイル等を加えて環境変数を設定しているだけです。プラグインは必要なければ消しましょう。
podの環境変数に同じキーを指定すると上書きすることが出来るので、`ES_JAVA_OPTS`や`NUMBER_OF_MASTERS`の値はここでは小さくしています。

    $ docker build -t elasticsearch:local .

    $ docker images
    => REPOSITORY          TAG         IMAGE ID            CREATED             SIZE
       elasticsearch       local       463f4c837920        2 hours ago         564MB

以上でイメージの作成は完了です！
ローカルでビルドしたイメージをDockerHubやGCRにプッシュせずそのまま使えるのは便利ですね！

## Kubernetes

#### namespace
```
$ kubectl create namespace elasticsearch
```

#### deployment
deploymentはpodのテンプレートとそのレプリカの数を指定して作成することで、podとreplicaSetを自動で作成します。
今回はelasticsearchノードの役割ごとに以下の3種類を作成します。

- master-deployment
- data-deployment
- client-deployment

##### master-node
```yaml:deployment-master.yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: elasticsearch-master
  namespace: elasticsearch
  labels:
    app: elasticsearch
    role: master
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: elasticsearch
        role: master
    spec:
      initContainers:
      - name: init-sysctl
        image: busybox
        imagePullPolicy: IfNotPresent
        command: ["sysctl", "-w", "vm.max_map_count=262144"]
        securityContext:
          privileged: true
      containers:
        - image: elasticsearch:local
          name: elasticsearch-master
          env:
          - name: NAMESPACE
            valueFrom:
              fieldRef:
                fieldPath: metadata.namespace
          - name: NODE_NAME
            valueFrom:
              fieldRef:
                fieldPath: metadata.name
          - name: "NUMBER_OF_MASTERS"
            value: "1"
          - name: NODE_MASTER
            value: "true"
          - name: NODE_DATA
            value: "false"
          - name: NODE_INGEST
            value: "false"
          - name: "ES_JAVA_OPTS"
            value: "-Xms256m -Xmx256m"
          ports:
          - containerPort: 9300
            name: transport
            protocol: TCP
          volumeMounts:
          - name: storage
            mountPath: /usr/share/elasticsearch/data
      volumes:
        - name: "storage"
          emptyDir:
            medium: ""
```

```
$ kubectl apply -f deployment-master.yaml
```

`spec.template.metadata.labels`で

- app: elasticsearch
- role: master

を指定しています。この2つのラベルを使うことで、elasticsearchのアプリケーションであることの判断、そのdeploymentが何の役割のものなのかを判断出来ることになります。

`initContainers` はelasticsearchが要求する`vm.max_map_count`を満たすために最初に走らせるコンテナです。以下コマンドを叩いています。

```
$ sysctl -w vm.max_map_count=262144
```

[Virtual memory | Elasticsearch Reference [5.5] | Elastic](https://www.elastic.co/guide/en/elasticsearch/reference/5.5/vm-max-map-count.html)

containersのelasticsearchの部分で環境変数をいくつか書いています。
`NUMBER_OF_MASTERS`はelasticsearch.ymlのminimum_master_nodesの部分に使われます。この値はマスター候補のノード数(ここではmaster-deploymentのレプリカ数)をnとすると、
( n/2 ) + 1 が適正値です。
ここで設定した値よりmaster候補のノードが少なくなるとelasticsearchは動かなくなります。
今回は1つしか動かさないので1にしています。

##### data-node
```yaml:deployment-data.yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: elasticsearch-data
  namespace: elasticsearch
  labels:
    app: elasticsearch
    role: data
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: elasticsearch
        role: data
    spec:
      initContainers:
      - name: init-sysctl
        image: busybox
        imagePullPolicy: IfNotPresent
        command: ["sysctl", "-w", "vm.max_map_count=262144"]
        securityContext:
          privileged: true
      containers:
        - image: elasticsearch:local
          name: elasticsearch-data
          env:
          - name: NAMESPACE
            valueFrom:
              fieldRef:
                fieldPath: metadata.namespace
          - name: NODE_NAME
            valueFrom:
              fieldRef:
                fieldPath: metadata.name
          - name: NODE_MASTER
            value: "false"
          - name: NODE_DATA
            value: "true"
          - name: NODE_INGEST
            value: "false"
          - name: "ES_JAVA_OPTS"
            value: "-Xms256m -Xmx256m"
          ports:
          - containerPort: 9300
            name: transport
            protocol: TCP
          volumeMounts:
          - name: storage
            mountPath: /usr/share/elasticsearch/data
      volumes:
        - name: "storage"
          emptyDir:
            medium: ""
```

環境変数で`NODE_DATA: true`にしている以外はmasterと同じです。

##### client-node

```yaml:deployment-client.yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: elasticsearch-nginx
  namespace: elasticsearch
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: elasticsearch
        role: client
    spec:
      initContainers:
      - name: init-sysctl
        image: busybox
        imagePullPolicy: IfNotPresent
        command: ["sysctl", "-w", "vm.max_map_count=262144"]
        securityContext:
          privileged: true
      containers:
        - image: launcher.gcr.io/google/nginx1
          name: nginx
          volumeMounts:
            - name: site-conf
              mountPath: /etc/nginx/conf.d
            - name: site-top-html
              mountPath: /usr/share/nginx/html
            - name: htpasswd
              mountPath: /etc/nginx/htpasswd
        - image: elasticsearch:local
          name: elasticsearch-client
          env:
          - name: NAMESPACE
            valueFrom:
              fieldRef:
                fieldPath: metadata.namespace
          - name: NODE_NAME
            valueFrom:
              fieldRef:
                fieldPath: metadata.name
          - name: NODE_MASTER
            value: "false"
          - name: NODE_DATA
            value: "false"
          - name: "ES_JAVA_OPTS"
            value: "-Xms256m -Xmx256m"
          ports:
          - containerPort: 9200
            name: http
            protocol: TCP
          - containerPort: 9300
            name: transport
            protocol: TCP
          volumeMounts:
          - name: storage
            mountPath: /usr/share/elasticsearch/data
      volumes:
        - name: site-conf
          configMap:
            name: site-conf
        - name: site-top-html
          configMap:
            name: site-top-html
        - name: "storage"
          emptyDir:
            medium: ""
        - name: htpasswd
          secret:
            secretName: htpasswd
```

clientのdeploymentはこれまでの２つと少し違っていて、
`containers`でelasticsearchとnginxのimageを指定しているため、このdeploymentによって作成されるpodではelasticsearchとnginxのコンテナが1つずつの計2つ動いています。
そしてnginxのコンテナではconfigMapとsecretを使って設定ファイル等を付けているので、それらを作成します！

```yaml:configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: site-conf
  namespace: elasticsearch
data:
  site.conf: |
    server_tokens off;
    server {
        listen       80;

        location / {
            root   /usr/share/nginx/html;
            index  index.html index.htm;
        }

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   /usr/share/nginx/html;
        }

    		location /search/ {
            auth_basic "Restricted";
            auth_basic_user_file /etc/nginx/htpasswd/.htpasswd;
            proxy_pass    http://localhost:9200/;
    		}
    }
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: site-top-html
  namespace: elasticsearch
data:
  index.html: |
    <!DOCTYPE html>
    <html>
    <head>
    <title>elasticsearch</title>
    <style>
        body {
            width: 35em;
            margin: 0 auto;
        }
    </style>
    </head>
    <body>
    <h1>HealthCheck OK!</h1>

    <p>elasticsearch is <a href="/search">here</a></p>

    </body>
    </html>
```

```
$ kubectl apply -f configmap.yaml
```

.htpasswdファイルを作成して、それを基にsecretを作ります。
.htpasswdファイルの作り方は[こちら](https://qiita.com/kotarella1110/items/be76b17cdbe61ff7b5ca)を参考にしました。

```
$ kubectl create secret generic htpasswd -n elasticsearch --from-file=.htpasswd
```

nginxコンテナは[GCPランチャーのイメージ](https://github.com/GoogleCloudPlatform/nginx-docker)を使っています。
このコンテナでは、`volumeMounts` でnginxの設定ファイルが入ったディレクトリとリダイレクト前のトップページのhtmlを読み込んでいます。このディレクトリとhtmlはconfigMapで作成したものです。

そもそもnginxを受け手として付けた理由ですが、今回はelasticsearchのプラグインであるx-packを入れていません。
そのため公開したelasticsearchにはどこからでもアクセスして編集可能な状態になってしまうため、一応認証としてbasic認証を付けています。

elasticsearchコンテナでもstorageを`emptyDir`のvolumeとしてマウントしています。  
これによって、podがリスタートしてもデータを保持出来ます(再生成はダメ)。

clientのpodではserviceからnginxコンテナでリクエストを受け取り、basic認証を通ったものを同podのelasticsearchコンテナにリダイレクトしています。

#### service

```yaml:services.yaml
kind: Service
apiVersion: v1
metadata:
  name: elasticsearch
  namespace: elasticsearch
  labels:
    app: elasticsearch
    role: client
spec:
  type: LoadBalancer
  selector:
    app: elasticsearch
    role: client
  ports:
  - protocol: TCP
    port: 80
---
apiVersion: v1
kind: Service
metadata:
  name: elasticsearch-discovery
  namespace: elasticsearch
  labels:
    component: elasticsearch
    role: master
spec:
  type: NodePort
  selector:
    app: elasticsearch
    role: master
  ports:
  - name: transport
    port: 9300
    protocol: TCP
```

外部からnginxのコンテナまで繋ぐためのサービスと、
elasticsearchが内部で通信に使うサービスを作成しています。

docker-for-macでは`type: LoadBalancer`とすることでブラウザからlocalhostで繋がるみたいです。
[Docker for Mac にKubernetesがやって来た！(ちょっと追記)](http://blue1st-tech.hateblo.jp/entry/2018/01/06/162306)

これまで作成した以下のファイルを作成すると、http://localhost:80 にアクセス出来ます！

- deployment-master.yaml
- deployment-data.yaml
- deployment-client.yaml
- configmap.yaml
- services.yaml

<img width="438" alt="ss1" src="https://qiita-image-store.s3.amazonaws.com/0/171180/665eee6b-d178-8ad1-3e3a-492b8a4e05f6.png">

elasticsearchにアクセスするにはhttp://localhost/search です。
basic認証としてユーザ名とパスワードを求められます。

user: elastic
pass: changeme

このパスワードなどはnginxコンテナにつけているconfigMapの.htpasswdを変更することで変更できます。

<img width="438" alt="ss2" src="https://qiita-image-store.s3.amazonaws.com/0/171180/55a3e874-881b-ab9d-3f04-a634ed23e76e.png">

```
$ kubectl get pod -n elasticsearch

NAME                                   READY     STATUS    RESTARTS   AGE
elasticsearch-data-db656cf4c-zpbc4     1/1       Running   0          3h
elasticsearch-master-f94b866f4-kjzlf   1/1       Running   0          2h
elasticsearch-nginx-c76c7cc6b-twrrl    2/2       Running   0          2h
```

```
$ curl -L http://localhost:80/search/_cat/nodes -u 'elastic:changeme'

10.1.0.9  27 97 12 0.79 0.65 0.67 d - elasticsearch-data-db656cf4c-zpbc4
10.1.0.11 49 97 12 0.79 0.65 0.67 i - elasticsearch-nginx-c76c7cc6b-twrrl
10.1.0.12 30 97 12 0.79 0.65 0.67 m * elasticsearch-master-f94b866f4-kjzlf
```

data, ingest, masterそれぞれのノードが動いていることが確認できます。

```
$ curl -L 'http://localhost:80/search/_cluster/health?pretty' -u 'elastic:changeme'

{
  "cluster_name" : "elasticsearch",
  "status" : "green",
  "timed_out" : false,
  "number_of_nodes" : 3,
  "number_of_data_nodes" : 1,
  "active_primary_shards" : 0,
  "active_shards" : 0,
  "relocating_shards" : 0,
  "initializing_shards" : 0,
  "unassigned_shards" : 0,
  "delayed_unassigned_shards" : 0,
  "number_of_pending_tasks" : 0,
  "number_of_in_flight_fetch" : 0,
  "task_max_waiting_in_queue_millis" : 0,
  "active_shards_percent_as_number" : 100.0
}
```

良さそうですね。
冗長化はそれぞれのdeploymentのレプリカ(`spec.replicas`)を増やせば出来ます！

## データの永続化

#### statefulSet

ここまででelasticsearchは動きましたが、データの永続化が出来ていません。
imageの変更などでdeploymentをapplyし直すとデータが飛んでしまう状態です。
これを解決するためにdeploymentではなくstatefulSetを使ってみようと思います。

[statefulSet](https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/)はdeploymentで作成されるpodに一意性と状態を持たせることが出来るようにしたものです。
podの名前が `***-0` のようになり順番に作成されます。
podは`volumeClaimTemplates`によってpvcを作り、そのpvcがpersistentVolumeを作る事によってデータの永続化が出来ています。storage-classを定義して適用すれば様々なストレージを使うことが出来ます。

deploymentではdata, master, clientとそれぞれの役割ごとに作成しましたが、statefulSetでは全部入りのpodを作成しようと思います。
必要なクラスタが小規模であれば全部入りを数台構成で運用可能であるため、それに対応するためです。

statefulSetを作成する前に先程作ったdeploymentを消してしまいます。

```
$ kubectl delete deployment -n elasticsearch --all
```

```yaml:statefulSet-full.yaml
apiVersion: apps/v1beta1
kind: StatefulSet
metadata:
  name: elasticsearch-full-sample
  namespace: elasticsearch
  labels:
    app: elasticsearch
spec:
  replicas: 1
  serviceName: elasticsearch-discovery
  updateStrategy:
    type: RollingUpdate
    rollingUpdate:
  podManagementPolicy: Parallel
  template:
    metadata:
      labels:
        app: elasticsearch
    spec:
      initContainers:
      - name: init-sysctl
        image: busybox
        imagePullPolicy: IfNotPresent
        command: ["sysctl", "-w", "vm.max_map_count=262144"]
        securityContext:
          privileged: true
      containers:
        - image: launcher.gcr.io/google/nginx1
          name: nginx
          volumeMounts:
            - name: site-conf
              mountPath: /etc/nginx/conf.d
            - name: site-top-html
              mountPath: /usr/share/nginx/html
            - name: htpasswd
              mountPath: /etc/nginx/htpasswd
        - image: elasticsearch:local
          name: elasticsearch-full
          env:
          - name: NAMESPACE
            valueFrom:
              fieldRef:
                fieldPath: metadata.namespace
          - name: NODE_NAME
            valueFrom:
              fieldRef:
                fieldPath: metadata.name
          - name: "NUMBER_OF_MASTERS"
            value: "1"
          - name: NODE_MASTER
            value: "true"
          - name: NODE_DATA
            value: "true"
          - name: "ES_JAVA_OPTS"
            value: "-Xms1024m -Xmx1024m"
          ports:
          - containerPort: 9200
            name: http
            protocol: TCP
          - containerPort: 9300
            name: transport
            protocol: TCP
          volumeMounts:
          - name: storage
            mountPath: /usr/share/elasticsearch/data
      volumes:
        - name: site-conf
          configMap:
            name: site-conf
        - name: site-top-html
          configMap:
            name: site-top-html
        - name: htpasswd
          secret:
            secretName: htpasswd
  volumeClaimTemplates:
  - metadata:
      name: storage
      annotations:
        volume.beta.kubernetes.io/storage-class: hostpath
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 5Gi
```

```
$ kubectl apply -f statefulSet-full.yaml

$ kubectl get pod,sts -n elasticsearch
NAME                             READY     STATUS    RESTARTS   AGE
po/elasticsearch-full-sample-0   2/2       Running   0          1m

NAME                                     DESIRED   CURRENT   AGE
statefulsets/elasticsearch-full-sample   1         1         1m
```

作成出来ましたがlocalhostにアクセスしてもコンテナまで繋がらないと思います。
これは作成したサービスがroleも見ているためです。そこだけ編集します。

```yaml:services-full.yaml
kind: Service
apiVersion: v1
metadata:
  name: elasticsearch
  namespace: elasticsearch
  labels:
    app: elasticsearch
spec:
  type: LoadBalancer
  selector:
    app: elasticsearch
  ports:
  - protocol: TCP
    port: 80
---
apiVersion: v1
kind: Service
metadata:
  name: elasticsearch-discovery
  namespace: elasticsearch
  labels:
    component: elasticsearch
spec:
  type: NodePort
  selector:
    app: elasticsearch
  ports:
  - name: transport
    port: 9300
    protocol: TCP
```

```
$ kubectl apply -f services-full.yaml
service "elasticsearch" configured
service "elasticsearch-discovery" configured
```
以上で繋がるようになっています。

statefulSetを使うことで、ノードが再起動する等してもデータを永続化することが出来ます。
image更新の際にもデータは引き継がれます。

## GKE(Google Kubernetes Engine)で動かすために
基本的にはそのまま動くと思います。バージョンによってapiが違っていたりするので、そこは使用しているバージョンの[リファレンス](https://kubernetes.io/docs/reference/)を見ましょう。

### GKEでのHTTPS化
https化はまた別の記事にでも書こうかなと思っていますが、
ingressと[kube-lego](https://github.com/jetstack/kube-lego)を使ってlet's encryptの証明書を取得することで出来ます。

[GKE でサービスを HTTPS と HTTP/2 に対応する(kube-lego 編) - Qiita](https://qiita.com/apstndb/items/2fef0a80d4510516cb1f)

更に、[helm](https://github.com/kubernetes/helm)を使ってhttps化をより簡単にすることが出来ます。
しかしkube-legoは開発終了してしまったみたいなので、引き継がれた[cert-manager](https://github.com/jetstack/cert-manager)を使ったほうがいいのかもしれません(試してないです)。

そしてelasticsearchの検索クエリでは、パラメータ付GETが使われているのですが、GCPのロードバランサがそれを弾いてしまうため、POSTで検索クエリを投げるようにする等結構面倒でした。

そしてGCEのingress controllerを使ってingressを作成するとBackend serviceのヘルスチェックが行われます。
ここでやっかいなのはデフォルトではserviceへのアクセスで200を返さなければ、そのサービスは停止しているとみなされingressが機能しません。
livenessProbe等を用いると解決できそうですが、elasticsearch単体でx-packを用いてbasic認証をかけると全てのアクセスで認証が必要になるため、ヘルスチェックが通らずingressを使うことが出来ませんでした。
今回nginxを通したのはそのヘルスチェックへの対応策でもあります。

## 片付け！
```
$ kubectl delete -n elasticsearch cm,svc,pv,pvc,sts --all
$ kubectl delete namespace elasticsearch
```

## おわりに
インフラとelasticsearchについてほぼ無知な状態から始まり、なんとか動かすことが出来ました。
elasticsearchのconfigディレクトリをイメージのビルド時に付けているので、辞書の更新時にイメージを更新してapplyする必要があり、その際にダウンタイムが発生してしまっているのでそこをどうにか出来れば良いなと思っています。
加えて、共通部分が多いyamlファイルが複数出来てしまっているので、管理の手間を何とかしたいです。

## 参考
- [elasticsearch-docker/5 at master · GoogleCloudPlatform/elasticsearch-docker](https://github.com/GoogleCloudPlatform/elasticsearch-docker/tree/master/5)
- [pires/docker-elasticsearch-kubernetes: Ready to use Elasticsearch + Kubernetes discovery plug-in Docker image.](https://github.com/pires/docker-elasticsearch-kubernetes)
- [pires/kubernetes-elasticsearch-cluster: Elasticsearch cluster on top of Kubernetes made easy.](https://github.com/pires/kubernetes-elasticsearch-cluster)