StatefulSetのノード障害時の挙動と対応
前回、Kubernetesのノード障害時の挙動について記事を書きました。このときはDeploymentを使って挙動を確かめていたですが、前回の記事を見た方から「StatefulSetだと挙動が違ったはず」とのご意見を頂戴しました。
https://twitter.com/tzkb/status/1116597587186814977
そこでStatefulSetだとノード障害時、どんな挙動になるか試してみました。
クラスタの状態は前回の状態からスタートします。
# kubectl get node NAME STATUS ROLES AGE VERSION test-cluster-7ajeznlcoium-master-0 Ready master 2d22h v1.14.0 test-cluster-7ajeznlcoium-minion-0 Ready <none> 2d22h v1.14.0 test-cluster-7ajeznlcoium-minion-1 Ready <none> 45h v1.14.0 test-cluster-7ajeznlcoium-minion-2 Ready <none> 2d16h v1.14.0
今回はElasticSearchのステートフルセットをデプロイしてみます。KubernetesのGitリポジトリをクローンします。
# cd ~ # git clone https://github.com/kubernetes/kubernetes
Cluster Add-Onからfluentd-elasticsearch
ディレクトリに入ります。
# cd kubernetes/cluster/addons/fluentd-elasticsearchds
デフォルトではElasticsearchのStatefulSetはvolumeClaimTemplates
を利用するように書かれていないので、以下のようにes-statefulset.yaml
を書き換えます。
# Elasticsearch deployment itself apiVersion: apps/v1 kind: StatefulSet metadata: name: elasticsearch-logging namespace: kube-system labels: k8s-app: elasticsearch-logging version: v6.6.1 kubernetes.io/cluster-service: "true" addonmanager.kubernetes.io/mode: Reconcile spec: serviceName: elasticsearch-logging replicas: 2 selector: matchLabels: k8s-app: elasticsearch-logging version: v6.6.1 template: metadata: labels: k8s-app: elasticsearch-logging version: v6.6.1 kubernetes.io/cluster-service: "true" spec: serviceAccountName: elasticsearch-logging containers: - image: gcr.io/fluentd-elasticsearch/elasticsearch:v6.6.1 name: elasticsearch-logging resources: # need more cpu upon initialization, therefore burstable class limits: cpu: 1000m requests: cpu: 100m ports: - containerPort: 9200 name: db protocol: TCP - containerPort: 9300 name: transport protocol: TCP volumeMounts: - name: elasticsearch-logging mountPath: /data env: - name: "NAMESPACE" valueFrom: fieldRef: fieldPath: metadata.namespace # Elasticsearch requires vm.max_map_count to be at least 262144. # If your OS already sets up this number to a higher value, feel free # to remove this init container. initContainers: - image: alpine:3.6 command: ["/sbin/sysctl", "-w", "vm.max_map_count=262144"] name: elasticsearch-logging-init securityContext: privileged: true volumeClaimTemplates: - metadata: name: elasticsearch-logging spec: accessModes: - ReadWriteOnce resources: requests: storage: 10Gi
ではおもむろにデプロイしてみます。
# kubectl apply -f es-statefulset.yaml # kubectl get pod -n kube-system -o wide -l k8s-app=elasticsearch-logging NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES elasticsearch-logging-0 1/1 Running 0 3m34s 10.100.2.221 test-cluster-7ajeznlcoium-minion-2 <none> <none> elasticsearch-logging-1 1/1 Running 0 99s 10.100.0.97 test-cluster-7ajeznlcoium-minion-0 <none> <none>
ここでtest-cluster-7ajeznlcoium-minion-2
のkubeletを停止します。
(test-cluster-7ajeznlcoium-minion-2) # sudo systemctl stop kubelet
すると。。。
# kubectl get pod -n kube-system -o wide -l k8s-app=elasticsearch-logging NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES elasticsearch-logging-0 1/1 Terminating 0 11m 10.100.2.221 test-cluster-7ajeznlcoium-minion-2 <none> <none> elasticsearch-logging-1 1/1 Running 0 9m56s 10.100.0.97 test-cluster-7ajeznlcoium-minion-0 <none> <none>
StatefulSetの場合は、PodのTerminate処理が完了してから新しいPodが上がるので、kubeletが反応しない以上Terminate処理が完了できず、stuckしてしまうみたいです。
ではどうするか。。。その際の解の一つがTwitter上でご指摘いただいた方から教わったkube-fencingです。
kube-fencingを使うと、NotReadyなノードが生じた場合、その上のk8sオブジェクトを全て削除してノードをフェンシング(強制再起動)してくれます。k8sオブジェクトの削除対象にはNodeオブジェクトも含まれるので、その上に乗ったPodはTerminateどころかetcdから消えます。したがって新しいPodが間違いなく作られるという仕組みで回避しようというのです。
では早速インストールしてみましょう。ソースコードをDLします。
# cd ~ # git clone https://github.com/kvaps/kube-fencing
manifest群が置いてあるディレクトリに移動します。
# cd kube-fencing/examples
私が管理しているノード群はOpenStack上に乗っているので、openstack
コマンドを使ってfencingを実現したいと思います。以下のようにfencing-script.yaml
を書き換えます。
apiVersion: v1 kind: ConfigMap metadata: name: fencing-scripts namespace: fencing data: fence.sh: | #!/bin/bash NODE="$1" source /openstack/openrc-file openstack server reboot --hard $1
fencing-scriptに与えるopenrcファイルを用意し、secretオブジェクトを作成します。Secretオブジェクトを作るために、fencing
Namespaceも一緒に作ります。
# kubectl create ns fencing # kubectl create secret generic openrc-file --from-file=openrc-file=openrc-file -n fencing
作ったSecretをマウントするように、またimageもopenstackコマンドがインストールされたものを指定するようにfencing-agents.yaml
を書き換えます。
apiVersion: apps/v1 kind: Deployment metadata: name: fencing-agents namespace: fencing spec: replicas: 1 selector: matchLabels: app: fencing-agents template: metadata: labels: app: fencing-agents spec: nodeSelector: node-role.kubernetes.io/master: "" tolerations: - key: node-role.kubernetes.io/master operator: Exists effect: NoSchedule hostNetwork: true containers: - name: fencing-agents image: bbrfkr0129/kube-fencing-agents command: [ "/usr/bin/tail", "-f", "/dev/null" ] volumeMounts: - name: scripts mountPath: /scripts - name: openrc-file mountPath: /openstack volumes: - name: scripts configMap: name: fencing-scripts defaultMode: 0744 - name: openrc-file secret: secretName: openrc-file defaultMode: 0400
manifestを適用します。
# kubectl apply -f .
ノードがNotReady時にfencingしてほしいノードはlabel付けをしておく必要があります。ワーカーノードをラベル付けしておきます。
# kubectl label node test-cluster-7ajeznlcoium-minion-0 fencing=enabled # kubectl label node test-cluster-7ajeznlcoium-minion-1 fencing=enabled # kubectl label node test-cluster-7ajeznlcoium-minion-2 fencing=enabled
これでインストール完了です! 早速kubeletを停止させてみましょう。
(test-cluster-7ajeznlcoium-minion-2) # sudo systemctl stop kubelet
どうなるかというと。。。今度は新しいPodが上がってきません。。。
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES elasticsearch-logging-0 1/1 Running 0 3m51s 10.100.0.98 test-cluster-7ajeznlcoium-minion-0 <none> <none> elasticsearch-logging-1 0/1 Init:0/1 0 53s <none> test-cluster-7ajeznlcoium-minion-0 <none> <none>
describeでログを見ると当然で、PVが違うインスタンスにアタッチされていて、移動できない旨のメッセージが表示されます。しかもたちの悪いことにノードオブジェクトは削除/再作成が行われているため、自分にPVがアタッチされていることを覚えていません。
ではやはりkube-fencingでもStatefulSetは救えないのか。。。というと、実は最後の砦があります。それは
もう諦めて、StatefulSetにはファイルシステムベース(NFSなど)のPVを使う、ということです。こうすれば、PVの取り合いが起きてもどのノードもPVをアタッチ/マウントできるので問題を回避できます。nfs-client
external-storage-provisionerなどを使って、興味がある方は試してみてください。
結論としては
- kube-fencingでもBlock DeviceベースのStatefulSetは救えない。FileSystemベースなら大丈夫。
ということでした。