0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

TL;DR

ある Kubernetes Node に障害が発生した際、Non-Graceful Node Shutdown により StatefulSet (Pod + RBD PV) が別の正常な Node に移される様子を確認する。

背景

Kubernetes では通常 、Node がダウンすると Pod は他の Node に再作成される。
しかし、StatefulSet の場合はそのような failover がうまく機能せず、Podが "Terminating" 状態のまま残り、新しい Pod が再作成されることがない。

この問題を解決するため、Kubernetes v1.28 からは Non-Graceful Node Shutdown 機能が安定版として実装された。この機能では、障害が発生した Node に out-of-service taints を付与することで、その Node から別の Node に StatefulSet を再作成して再稼働させることができるようになる。

Kubernetes の Non-Graceful Node Shutdown のフローについては外部リンクの記事が詳しい:

また、Rook-Ceph においてはこの機能を前提として、Node 上の out-of-service taint を検知し、Node の Network Fencing および RBD ボリュームのデタッチを自動で行う機能が実装されている。

仕様は下記のリンクを参照:

以上の機能により、Node が障害によってダウンした場合にも RBD PV + Pod の StatefulSet が別の正常なNode 上に移動される。

本記事では、out-of-service taint の付与によって Non-Graceful Node Shutdown をトリガーし、Rook-Ceph の RBD が failover される様子を確認する。

Failover の実験

前提条件

  • Kubernetes Cluster は、3-node 構成の OKD4 cluster を用いる。各 Node は master と worker 両方の role を持つ。
  • Rook-Ceph 環境は OpenShift Data Foundation に組み込まれたものを用いる。
  • Medik8s 系統の Operator は未導入である。
$ oc get node
NAME      STATUS   ROLES                         AGE   VERSION
master0   Ready    control-plane,master,worker   9h    v1.28.7+6e2789b
master1   Ready    control-plane,master,worker   10h   v1.28.7+6e2789b
master2   Ready    control-plane,master,worker   10h   v1.28.7+6e2789b

事前準備

StatefulSet の作成

まずは次の manifest より StatefulSet を作成する:

test-sts.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: test-sts
spec:
  serviceName: "nginx"
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      accessModes: ["ReadWriteOnce"]
      resources:
        requests:
          storage: 1Gi
      storageClassName: "ocs-storagecluster-ceph-rbd"

リソースを作成する:

$ oc apply -f test-sts.yaml 
statefulset.apps/test-sts created

StatefulSet が作成されたことを確認する:

$ oc get sts
NAME       READY   AGE
test-sts   1/1     31s

Pod が稼働していることを確認する:

$ oc get pod -o wide          
NAME         READY   STATUS    RESTARTS   AGE   IP            NODE      NOMINATED NODE   READINESS GATES          
test-sts-0   1/1     Running   0          36s   10.130.0.28   master0   <none>           <none>      

PVC がBound されていることを確認する:

$ oc get pvc
NAME             STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS                  AGE
www-test-sts-0   Bound    pvc-fb4be4f3-b0db-4db2-b981-bfb0a81262a1   1Gi        RWO            ocs-storagecluster-ceph-rbd   40m

作成された PV を確認する:

$ oc get pv
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                                                      STORAGECLASS                  REASON   AGE
pvc-fb4be4f3-b0db-4db2-b981-bfb0a81262a1   1Gi        RWO            Delete           Bound    default/www-test-sts-0                                     ocs-storagecluster-ceph-rbd            40m

RBD の確認

次に、PV に対応する RBD がどのNode 上に存在するか確認する。
これには、次のスクリプト:find_rbd.sh を作成すると便利である。
これは、PV 名を与えると RBD デバイスが存在する Node 名を表示するものである:

find_rbd.sh
#!/bin/bash

usage()
{
  echo "Usage:"
  echo "  ./find_rbd.sh <pvname>"
}

# Parse arguments
if [ "$#" -ne 1 ]; then
    usage
    exit 1
fi

pvname=$1
nodes=$(oc get nodes --no-headers)

while read -r line; do
  node=$(echo "$line" | awk '{print $1}')
  status=$(echo "$line" | awk '{print $2}')
  if [ "$status" != "Ready" ]; then
    continue
  fi

  lsblk_result=$(oc debug node/${node} -- chroot /host lsblk -d 2> /dev/null)
  echo "${lsblk_result}" | grep "/${pvname}/" > /dev/null
  if [[ $? -eq 0 ]]; then
    echo "An RBD found on $node:"
    # Output until the indentation ends
    echo "${lsblk_result}" | awk -v pvname="${pvname}" '
      {
        if (found) {
          if ($0 ~ /^ {32}/) {
            print $0
          } else {
            found = 0
          }
        }
        if ($0 ~ pvname) {
          print $0
          found = 1
        }
      }'
    exit 0
  fi
done <<< "$nodes"

echo RBD not found.
exit 1

これを利用して、PV に対応する RBD が存在する Node を特定する:

$ ./find_rbd.sh pvc-fb4be4f3-b0db-4db2-b981-bfb0a81262a1
An RBD found on master0:
rbd0  251:0    0     1G  0 disk /var/lib/kubelet/pods/5b14419a-3a70-405b-b3ab-acc9b4c3a741/volumes/kubernetes.io~csi/pvc-fb4be4f3-b0db-4db2-b981-bfb0a81262a1/mount
                                /var/lib/kubelet/plugins/kubernetes.io/csi/openshift-storage.rbd.csi.ceph.com/eb0b839f0a984f3d244bb6ecb4d6bf909cd134af1f0cfc3e3c1d3fb26baf750b/globalmount/0001-0011-openshift-storage-0000000000000001-62513b81-263e-486e-aed3-623d41d3c40d

Node master0 上に /dev/rbd0 として存在していることが確認できた。

Node に taints がないことの確認

すべての Nodes において、 taints : null になっていることを確認する。

$ oc get nodes -o json | jq '.items[] | {name: .metadata.name, taints: .spec.taints}'
{
  "name": "master0",
  "taints": null
}
{
  "name": "master1",
  "taints": null
}
{
  "name": "master2",
  "taints": null
}

実験1: Node を強制電源OFF + 手動で taints 付与

準備段階で対象の RBD が master0 上にあることがわかったので、この Node を強制的に電源OFFにする。

次に master0out-of-service Taint を付与する:

oc adm taint nodes master0 node.kubernetes.io/out-of-service=nodeshutdown:NoExecute
oc adm taint nodes master0 node.kubernetes.io/out-of-service=nodeshutdown:NoSchedule

master 0out-of-service taints が付与されたことを確認する:

$ oc get nodes -o json | jq '.items[] | {name: .metadata.name, taints: .spec.taints}'
{
  "name": "master0",
  "taints": [
    {
      "effect": "NoExecute",
      "key": "node.kubernetes.io/out-of-service",
      "value": "nodeshutdown"
    },
    {
      "effect": "NoSchedule",
      "key": "node.kubernetes.io/out-of-service",
      "value": "nodeshutdown"
    }
  ]
}
{
  "name": "master1",
  "taints": null
}
{
  "name": "master2",
  "taints": null
}

Node に障害が発生して 40 秒経つと、Kubernetes 側は Node の State を NotReady すると同時に、unreachable taints を付与してくる。そうすると出力結果は次のようになる。

{
  "name": "master0",
  "taints": [
    {
      "effect": "NoSchedule",
      "key": "node.kubernetes.io/out-of-service",
      "value": "nodeshutdown"
    },
    {
      "effect": "NoExecute",
      "key": "node.kubernetes.io/out-of-service",
      "value": "nodeshutdown"
    },
    {
      "effect": "NoSchedule",
      "key": "node.kubernetes.io/unreachable",
      "timeAdded": "2024-06-26T05:49:58Z"
    },
    {
      "effect": "NoExecute",
      "key": "node.kubernetes.io/unreachable",
      "timeAdded": "2024-06-26T05:50:04Z"
    }
  ]
}
{
  "name": "master1",
  "taints": null
}
{
  "name": "master2",
  "taints": null
}

Pod の稼働状況を確認する。以下は、10秒おきに oc get pod を実行したログのうち、failover が起こった部分を抜粋したものである。
master0 上の pod が削除され、master2 に再作成される様子が確認できる。

$ date
Wed Jun 26 03:14:12 PM JST 2024
$ oc get pod
NAME         READY   STATUS    RESTARTS   AGE     IP            NODE      NOMINATED NODE   READINESS GATES
test-sts-0   1/1     Running   0          3m48s   10.130.0.28   master0   <none>           <none>

$ date
Wed Jun 26 03:14:22 PM JST 2024
$ oc get pod
NAME         READY   STATUS        RESTARTS   AGE     IP            NODE      NOMINATED 
NODE   READINESS GATES
test-sts-0   1/1     Terminating   0          3m58s   10.130.0.28   master0   <none>           <none>

$ date
Wed Jun 26 03:14:32 PM JST 2024
$ oc get pod
NAME         READY   STATUS              RESTARTS   AGE   IP       NODE      NOMINATED NODE   READINESS GATES
test-sts-0   0/1     ContainerCreating   0          8s    <none>   master2   <none>           <none>

$ date
Wed Jun 26 03:14:43 PM JST 2024
$ oc get pod
NAME         READY   STATUS    RESTARTS   AGE   IP            NODE      NOMINATED NODE   READINESS GATES 
test-sts-0   1/1     Running   0          18s   10.129.0.32   master2   <none>           <none>

RBD を調べてみると、Node master2 に移動していることが確認できる。

$ ./find_rbd.sh pvc-fb4be4f3-b0db-4db2-b981-bfb0a81262a1
An RBD found on master2:
rbd1  251:16   0     1G  0 disk /var/lib/kubelet/pods/37784274-29f0-43df-9a3e-4cd08da3a738/volumes/kubernetes.io~csi/pvc-fb4be4f3-b0db-4db2-b981-bfb0a81262a1/mount
                                /var/lib/kubelet/plugins/kubernetes.io/csi/openshift-storage.rbd.csi.ceph.com/eb0b839f0a984f3d244bb6ecb4d6bf909cd134af1f0cfc3e3c1d3fb26baf750b/globalmount/0001-0011-openshift-storage-0000000000000001-62513b81-263e-486e-aed3-623d41d3c40d

これで、Node 障害に対して Pod + RBD PV の StatefulSet が failover できていることを確認できた。

後始末として StatefulSet を削除する。

$ oc delete -f test-sts.yaml 
statefulset.apps "test-sts" deleted

Node master0 の電源を ON にし、taints を削除する:

oc adm taint nodes master0 node.kubernetes.io/out-of-service-

実験2: Node を稼働させたまま taints だけ付与

今度は Node をシャットダウンせず、out-of-service taints だけ付与した場合はどうなるのだろうか。

再度、事前準備を行い、master0 上に RBD が作成されている状況を作ってみる。

そして、Node の電源を落とさず、master0out-of-service taints を付与してみる:

oc adm taint nodes master0 node.kubernetes.io/out-of-service=nodeshutdown:NoExecute
oc adm taint nodes master0 node.kubernetes.io/out-of-service=nodeshutdown:NoSchedule

以下に Pod の状態を 10秒おきに取得したログを貼る。
あるタイミングで Terminating に変化した後は、膠着してしまい Pod が再作成される気配はない。

$date
Wed Jun 26 04:05:09 PM JST 2024
$ oc get pods -o wide
NAME         READY   STATUS    RESTARTS   AGE    IP            NODE      NOMINATED NODE   READINESS GATES
test-sts-0   1/1     Running   0          3m2s   10.130.0.33   master0   <none>           <none>

$ date
Wed Jun 26 04:05:19 PM JST 2024
$ oc get pods -o wide
NAME         READY   STATUS        RESTARTS   AGE     IP            NODE      NOMINATED NODE   READINESS GATES
test-sts-0   0/1     Terminating   0          3m12s   10.130.0.33   master0   <none>           <none>

$ date
Wed Jun 26 04:05:29 PM JST 2024
$ oc get pods -o wide
NAME         READY   STATUS        RESTARTS   AGE     IP            NODE      NOMINATED NODE   READINESS GATES
test-sts-0   0/1     Terminating   0          3m22s   10.130.0.33   master0   <none>           <none>

$ date
Wed Jun 26 04:05:39 PM JST 2024
$ oc get pods -o wide
NAME         READY   STATUS        RESTARTS   AGE     IP            NODE      NOMINATED NODE   READINESS GATES
test-sts-0   0/1     Terminating   0          3m32s   10.130.0.33   master0   <none>           <none>

...
...

Rook 側の処理はどうなったのだろうか?Pod rook-ceph-operator から ceph-cluster-controller に関するログを見てみる。

oc logs -f -n openshift-storage $(oc get pods -n openshift-storage -o custom-columns=:metadata.name | grep rook-ceph-operator) | grep "| ceph-cluster-controller:"

次のようなログが断続的に吐かれている:

2024-06-26 08:08:28.042556 I | ceph-cluster-controller: node "master0" require fencing, found rbd volumes in use
2024-06-26 08:08:28.513866 W | ceph-cluster-controller: Blocking node IP [100.64.0.4/32]
2024-06-26 08:08:28.526738 I | ceph-cluster-controller: successfully created network fence CR for node "master0"
2024-06-26 08:13:12.613655 I | ceph-cluster-controller: Found taint: Key=node.kubernetes.io/out-of-service, Value=nodeshutdown on node master0
2024-06-26 08:13:12.613676 I | ceph-cluster-controller: volumeInUse after split based on '^' [openshift-storage.rbd.csi.ceph.com 0001-0011-openshift-storage-0000000000000001-62513b81-263e-486e-aed3-623d41d3c40d]

該当箇所は次のリンク先のあたりで、 node fencing を実行する処理が書かれている。

ログを見る限り、 out-of-service taints は認識されていており、Network Fence も作成されているようだ。そして、 RBD を探してみると、どの Node 上にも RBD デバイスが存在していない。

$ ./find_rbd.sh pvc-fb4be4f3-b0db-4db2-b981-bfb0a81262a1
RBD not found.

結果を見る限り Rook 側がやるべき RBD の処理は終わっているように見える。
単に Kubernetes 側が detach 処理をしていないだけならば、 pod を強制的に削除してみる。

kubectl delete pod test-sts-0 --force --grace-period=0
Warning: Immediate deletion does not wait for confirmation that the running resource has been terminated. The resource may continue to run on the cluster indefinitely.
pod "test-sts-0" force deleted

直後、動きがあり Pod が別 Node master2 に再作成された。

...
...
$ date
Wed Jun 26 06:01:12 PM JST 2024
$ oc get pods -o wide
NAME         READY   STATUS        RESTARTS   AGE   IP             NODE      NOMINATED NODE   READINESS GATES
test-sts-0   0/1     Terminating   0          53m   10.130.1.188   master0   <none>           <none>

$ date
Wed Jun 26 06:01:32 PM JST 2024
$ oc get pods -o wide
NAME         READY   STATUS              RESTARTS   AGE   IP       NODE      NOMINATED NODE   READINESS GATES
test-sts-0   0/1     ContainerCreating   0          1s    <none>   master2   <none>           <none>

$ date
Wed Jun 26 06:01:42 PM JST 2024
$ oc get pods -o wide
NAME         READY   STATUS    RESTARTS   AGE   IP            NODE      NOMINATED NODE   READINESS GATES 
test-sts-0   1/1     Running   0          11s   10.129.0.83   master2   <none>           <none>

RBD も master2 に再作成されている。

./find_rbd.sh pvc-fb4be4f3-b0db-4db2-b981-bfb0a81262a1 
An RBD found on master2:
rbd1  251:16   0     1G  0 disk /var/lib/kubelet/pods/e67dad97-bfdb-4edc-8acb-23ecf639a7f5/volumes/kubernetes.io~csi/pvc-fb4be4f3-b0db-4db2-b981-bfb0a81262a1/mount
                                /var/lib/kubelet/plugins/kubernetes.io/csi/openshift-storage.rbd.csi.ceph.com/eb0b839f0a984f3d244bb6ecb4d6bf909cd134af1f0cfc3e3c1d3fb26baf750b/globalmount/0001-0011-openshift-storage-0000000000000001-62513b81-263e-486e-aed3-623d41d3c40d

out-of-service を taints を削除してみる:

oc adm taint nodes master0 node.kubernetes.io/out-of-service-

すると Ceph Rook Operator の ログが動き、unFence でエラーが吐かれている。

2024-06-26 09:37:51.688020 I | ceph-cluster-controller: waiting for network fence CR "master0-rbd-openshift-storage" status to get result "unfencing operation successful"
2024-06-26 09:37:53.688976 I | ceph-cluster-controller: successfully unfenced "rbd" network fence cr "master0-rbd-openshift-storage", proceeding with deletion
2024-06-26 09:37:53.714553 I | ceph-cluster-controller: successfully deleted network fence CR master0-rbd-openshift-storage
2024-06-26 09:37:53.721795 I | ceph-cluster-controller: node master0 does not have taint node.kubernetes.io/out-of-service, unfencing networkFence CR
2024-06-26 09:37:53.727081 E | ceph-cluster-controller: failed to unFence network fence CR. Operation cannot be fulfilled on networkfences.csiaddons.openshift.io "master0-rbd-openshift-storage": StorageError: invalid object, Code: 4, Key: /kubernetes.io/csiaddons.openshift.io/networkfences/master0-rbd-openshift-storage, ResourceVersion: 0, AdditionalErrorMsg: Precondition failed: UID in precondition: 17773a8b-85b9-4263-be02-953df54bb4a5, UID in object meta: 
2024-06-26 09:37:53.727117 E | ceph-cluster-controller: failed to handle node failure. failed to delete rbd network fence for node "master0".: Operation cannot be fulfilled on networkfences.csiaddons.openshift.io "master0-rbd-openshift-storage": StorageError: invalid object, Code: 4, Key: /kubernetes.io/csiaddons.openshift.io/networkfences/master0-rbd-openshift-storage, ResourceVersion: 0, AdditionalErrorMsg: Precondition failed: UID in precondition: 17773a8b-85b9-4263-be02-953df54bb4a5, UID in object meta: 

しかし、NetworkFence の CR は削除されているようだ。

$ oc get networkfence
No resources found           

実験の意図としては、Node が稼働中でも Pod と RBD の failover を意図的にトリガーできるか試したかった。結果的にはできていそうだが、Rook 側が想定した操作ではない気がする。

最後に

Rook-Ceph における failover の issue は 2018 年から issue が上がってきて、その対処 core: faster recovery from rbd rwo node loss by subhamkrai · Pull Request #12286 · rook/rook がマージされたのが 2023年5月末。

Kubernetes の Non-Graceful Node Shutdown が stable 版としてとして GA されたのが v1.28 1(2023年08月15日2)。Kubernetes や Rook-Ceph は万能なイメージがあったが、実はこの類の Failover ができるようになってまだ1年ほどなのか。

Medik8s を用いれば障害発生時の Taints の付与も自動化できるようで、検証した結果は次の記事を参照:

これらの機能をうまく使うことでアプリケーション側の failover 実装の負担が軽くなりそう。

  1. Kubernetes 1.28: Non-Graceful Node Shutdown Moves to GA | Kubernetes

  2. Kubernetes v1.28: Planternetes | Kubernetes

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?