前回、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ベースなら大丈夫。
ということでした。