概要
前回 docker-compose で構築しました
今回は k8s 上に構築するのにチャレンジしました
StatefulSet + Headless Service で構築しています
シングル構成から複数台構成まで順を追って説明します
環境
- macOS 10.14.5
- minikube 1.1.0
- ElasticSearch 7.1
とりあえず run (Deployment)
まずは kubctl run
でシングル構成で立ち上げます
kubectl run elasticsearch --image=docker.elastic.co/elasticsearch/elasticsearch:7.1.1 --env="discovery.type=single-node" --port=9200
kubectl run
は Deployment を使っています
YAML 定義を確認するには -o yaml --dry-run
オプションを付与します
作成されたリソースを確認するには Deployment, ReplicaSet, Pod
を確認します
kubectl get deploy,rs,po
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.extensions/elasticsearch 1/1 1 1 12s
NAME DESIRED CURRENT READY AGE
replicaset.extensions/elasticsearch-775d564d6c 1 1 1 12s
NAME READY STATUS RESTARTS AGE
pod/elasticsearch-775d564d6c-h4hxs 1/1 Running 0 12s
動作確認は exec を使って直接 Pod の localhost にアクセスしてみましょう
kubectl exec elasticsearch-775d564d6c-h4hxs curl localhost:9200/_cat/health
kubectl exec elasticsearch-775d564d6c-h4hxs curl localhost:9200/_cat/nodes
リソースの削除は Deployment を削除すれば OK です
kubectl delete deploy elasticsearch
シングル構成 (Pod)
次に YAML ファイルを定義してシングル構成を構築してみます
先程 run で Deployment 経由の構築はしたので今回は Pod リソースを使った YAML ファイルを定義してみます
※先程の run に --restart=Never
を付与すれば Pod のみになりますが勉強のため自分で YAML ファイルを作成しています
vim es_master_pod.yml
apiVersion: v1
kind: Pod
metadata:
name: es-pod
labels:
app: es
spec:
containers:
- name: es
image: docker.elastic.co/elasticsearch/elasticsearch:7.1.1
env:
- name: discovery.type
value: single-node
ports:
- containerPort: 9200
name: api
- containerPort: 9300
name: gossip
restartPolicy: Never
ポイントは env
で discovery.type
を渡している点です
これを apply
しましょう
kubectl apply -f es_master_pod.yml
作成されるリソースは Pod になります
kubectl get po
NAME READY STATUS RESTARTS AGE
es-pod 1/1 Running 0 4s
動作確認します
kubectl exec es-pod curl localhost:9200/_cat/health
リソースの削除は Pod を削除すれば OK です
kubectl delete po es-pod
シングル構成 (StatefulSet)
次にシングル構成を StatefulSet を使って構築します
今回は 3 台構成のクラスタを StatefulSet を使って構築します
そのための準備としてまずはシングル構成を構築します
vim kubectl apply -f es_master_sts1.yml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: es-cluster
spec:
selector:
matchLabels:
app: es
serviceName: "es-cluster"
replicas: 1
template:
metadata:
labels:
app: es
spec:
containers:
- name: es
image: docker.elastic.co/elasticsearch/elasticsearch:7.1.1
env:
- name: discovery.type
value: single-node
ports:
- containerPort: 9200
name: api
- containerPort: 9300
name: gossip
volumeMounts:
- name: data
mountPath: /data
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 1Gi
PVC (PersistentVolumeClaim) を追加しています
template.spec.containers
は先程の Pod の定義とほぼ同じです
selector.matchLabels
と template.metadata.labels
は同じになるようにします
これで起動してみましょう
kubectl apply -f es_master_sts1.yml
作成されるリソースは StatefulSet, Pod, PVC になります
kubectl get sts,po,pvc
NAME READY AGE
statefulset.apps/es-cluster 1/1 49s
NAME READY STATUS RESTARTS AGE
pod/es-cluster-0 1/1 Running 0 48s
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
persistentvolumeclaim/data-es-cluster-0 Bound pvc-ae9aa9e8-8e3a-11e9-8372-080027d5c49e 1Gi RWO standard 48s
動作確認しましょう
kubectl exec es-cluster-0 curl localhost:9200/_cat/health
ホスト名も確認してみましょう
StatefulSet を使っているのでホスト名が固定されているのが確認できると思います
kubectl exec es-cluster-0 hostname
削除は StatefulSet と PVC を削除すれば OK です
kubectl delete sts es-cluster
kubectl delete pvc data-es-cluster-0
3 台クラスタ構成 (StatefulSet + Headless Service)
さてここからが本番です
3 台構成のクラスタを StatefulSet を使って構築します
また Headless Service も使います
Headless Service を使うとホスト名+サブドメイン (サービス名) で他の Pod にアクセスすることができます
クラスタを構成するにあたり IP ではなくホスト名を使いたいので Headless Service を使います
vim es_master_svc.yml
apiVersion: v1
kind: Service
metadata:
name: es-cluster
labels:
app: es
spec:
clusterIP: None
ports:
- port: 9200
name: api
- port: 9300
name: gossip
selector:
app: es
まずは Headless Service を作成します
kubectl apply -f es_master_svc.yml
これで同じ namespace 配下の Pod 間で名前解決ができるようになります
次に StatefulSet を作成します
vim kubectl apply -f es_master_sts3.yml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: es-cluster
spec:
selector:
matchLabels:
app: es
serviceName: "es-cluster"
replicas: 3
template:
metadata:
labels:
app: es
spec:
containers:
- name: es
image: docker.elastic.co/elasticsearch/elasticsearch:7.1.1
env:
- name: HOSTNAME_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: node.name
value: $(HOSTNAME_NAME).es-cluster
- name: cluster.name
value: k8s-cluster
- name: discovery.seed_hosts
value: es-cluster-0.es-cluster,es-cluster-1.es-cluster,es-cluster-2.es-cluster
- name: cluster.initial_master_nodes
value: es-cluster-0.es-cluster,es-cluster-1.es-cluster,es-cluster-2.es-cluster
- name: ES_JAVA_OPTS
value: "-Xms512m -Xmx512m"
ports:
- containerPort: 9200
name: api
- containerPort: 9300
name: gossip
volumeMounts:
- name: data
mountPath: /data
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 1Gi
だいぶ長くなりました
が一つずつ見ればそこまで複雑ではありません
serviceName: "es-cluster"
は Headless Service の metadata.name
と同じにします
3 台構成にするので replicas: 3
にしましょう
一番のポイントは env
になります
ElasticSearch のクラスタを構成する場合 node.name
と discovery.seed_hosts
, cluster.initial_master_nodes
そしてマシンの hostname
はすべて合わせたほうが無難です
IP でもできるようですが個人的にはハマりやすいのでできれば名前でアクセスできるようにしましょう
今回はそのために Headless Service を使っています
まず HOSTNAME_NAME
に fieldPath: metadata.name
でホスト名を環境変数に設定しています
今回の 3 台構成であれば es-cluster-0, es-cluster-1, es-cluster-2 がそれぞれ動的に代入されます
そしてそれを使って node.name
を $(HOSTNAME_NAME).es-cluster
にしています
なぜわざわざマシンのホスト名にサブドメインの .es-cluster
を付与しているのかというと Headless Service の場合他の Pod にアクセスするにはホスト名だけではなくサブドメインも必要になります
なので discovery.seed_hosts
と cluster.initial_master_nodes
は es-cluster-0.es-cluster
という感じにしなければクラスタに所属させるホストを見つけることができません
でここに指定した値と node.name
は同じにする必要があるためわざわざこのような方法にしています
(もしかするともっと簡単な方法があるかもしれませんが)
kubectl apply -f es_master_sts3.yml
あとはこれを適用して確認してみます
kubectl get sts,po,pvc,svc
NAME READY AGE
statefulset.apps/es-cluster 3/3 45s
NAME READY STATUS RESTARTS AGE
pod/es-cluster-0 1/1 Running 0 45s
pod/es-cluster-1 1/1 Running 0 40s
pod/es-cluster-2 1/1 Running 0 35s
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
persistentvolumeclaim/data-es-cluster-0 Bound pvc-f581cf6c-8e3c-11e9-8372-080027d5c49e 1Gi RWO standard 45s
persistentvolumeclaim/data-es-cluster-1 Bound pvc-f8371d19-8e3c-11e9-8372-080027d5c49e 1Gi RWO standard 40s
persistentvolumeclaim/data-es-cluster-2 Bound pvc-fae468fa-8e3c-11e9-8372-080027d5c49e 1Gi RWO standard 36s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/es-cluster ClusterIP None <none> 9200/TCP,9300/TCP 17h
service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 17h
クラスタを構築するまでに少し時間がかかるので待ちましょう
kubectl exec es-cluster-0 curl -- -s localhost:9200/_cat/health
1560473021 00:43:41 k8s-cluster green 3 3 0 0 0 0 0 0 - 100.0%
kubectl exec es-cluster-0 curl localhost:9200/_cat/nodes
172.17.0.7 26 98 36 1.61 2.13 1.32 mdi * es-cluster-1.es-cluster
172.17.0.6 14 98 41 1.61 2.13 1.32 mdi - es-cluster-0.es-cluster
172.17.0.8 16 98 33 1.61 2.13 1.32 mdi - es-cluster-2.es-cluster
こんな感じで取得できるようになれば OK です
minikube を使っている場合
minikube の場合初期リソースが少ないため以下を実施しないとクラスタ構成時に Pod が起動しませんでした
メモリ増設
minikube config set memory 4096
minikube delete
minikube start
max_map_count 増設
minikube ssh
sudo sysctl -w vm.max_map_count=262144
failover を確認する
master になっている Pod を 1 台削除してみましょう
kubectl delete po es-cluster1
StatefulSet なので Pod が削除されても自動で復活します
再作成中はしばらく 2 台構成のクラスタ状態になります
復活したら再度 health と nodes を確認してみましょう
kubectl exec es-cluster-0 curl -- -s localhost:9200/_cat/health
1560473297 00:48:17 k8s-cluster green 3 3 0 0 0 0 0 0 - 100.0%
kubectl exec es-cluster-0 curl -- -s localhost:9200/_cat/nodes
172.17.0.6 19 98 32 1.05 1.43 1.20 mdi - es-cluster-0.es-cluster
172.17.0.8 23 98 32 1.05 1.43 1.20 mdi * es-cluster-2.es-cluster
172.17.0.7 14 98 26 1.05 1.43 1.20 mdi - es-cluster-1.es-cluster
failover し master が es-cluster-1 から es-cluster-2 になっているのがわかります
また再作成された es-clsuter-1 も自動的にクラスタに再ジョインいしていることがわかります
リソースの削除は以下の通りです
kubectl delete sts es-cluster
kubectl delete pvc data-es-cluster-0 data-es-cluster-1 data-es-cluster-2
kubectl delete svc es-clsuter
トラブルシューティング
今回の 3 台構成にたどり着くまでに何度もトライアンドエラーしました
今回のポイントは Headless Service を使ってホスト名+サブドメインでクラスタ間のアクセスをする点かなと思います
あとは minikube のリソース周りもはまりポイントかなと思います
これらのミスに気づいた方法としては minikube ssh
してログインし直接 docker logs
で ElasticSearch コンテナのログを確認していました
StatefulSet は Pod の作成に失敗すると自動でコンテナを再作成します
なのでコンテナが削除される前に docker logs -f f93400366819
でログを眺めるのが一番良いと思います
リソースが足りない場合にエラーは大抵これで解決できます
StatefulSet の場合 restartPolicy を Never に設定できません
なので今回のように restartPolicy=Never にできる Pod リソースだけを使って原因調査するのもありかなと思います
最後に
ElasticSearch7.1 のクラスタ構成を k8s 上に構築してみました
記事内では StatefulSet + Headless Service を使って構築しています
調べると他にも CRD (Custom Resource Definition) を使ったり Deployment を使って構築しているサンプルもありました
個人的に試した感じだとクラスタ間はホスト名を使ってアクセスできるようにしたほうが運用も楽になる感じがするのが自分は StatefulSet + Headless Service のリソースを使って構築した感じです
この辺りのリソースやコントローラのチョイスは経験が大きく影響するので自分にあったものを選択するのが良いかなと思います
この記事のおかげでうまくいきました。本当に助かりました。
返信削除