はじめに
パッチ適用や障害時のメンテナンスなどKubernetesクラスタを構成するノードを停止する場面があると思います。
クラスタで稼働しているPod全てを停止していいなら悩むこともありませんが、実際には停止したいノードのみを停止させて、Podは稼働させた状態でサービスを継続することがほとんどだと思います。
今回はクラスタを構成するノードをメンテナンスする場面について確認したいと思います。
cordon/uncordon(スケジューリング対象からの除外/追加)
kubectl cordonコマンドでノードを指定することで、そのノードをスケジューラーからのスケジューリング対象から外すことができます。
以下の環境でその動作を確認したいと思います。
スケジューリング対象からの除外
replica数が4のDeploymentがデプロイされています。それぞれ2つのworkerノードにデプロイされています。
$ kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-dep-5589d85476-crlmg 1/1 Running 0 21s 192.168.69.235 k8s-worker02 <none> <none>
nginx-dep-5589d85476-flmth 1/1 Running 0 21s 192.168.79.77 k8s-worker01 <none> <none>
nginx-dep-5589d85476-fvkl4 1/1 Running 0 21s 192.168.79.76 k8s-worker01 <none> <none>
nginx-dep-5589d85476-hw5k4 1/1 Running 0 21s 192.168.69.200 k8s-worker02 <none> <none>
$ kubectl get node
NAME STATUS ROLES AGE VERSION
k8s-master Ready master 102d v1.17.3
k8s-worker01 Ready <none> 102d v1.17.3
k8s-worker02 Ready <none> 102d v1.17.3
このうち、k8s-worker02をスケジューリング対象から外します。
$ kubectl cordon k8s-worker02
node/k8s-worker02 cordoned
$ kubectl get node
NAME STATUS ROLES AGE VERSION
k8s-master Ready master 102d v1.17.3
k8s-worker01 Ready <none> 102d v1.17.3
k8s-worker02 Ready,SchedulingDisabled <none> 102d v1.17.3
STATUSに「SchedulingDisabled」が追加されていますね。
この状態でreplica数を8にします。
$ kubectl apply -f nginx-dep.yaml
deployment.apps/nginx-dep configured
$ kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-dep-5589d85476-47gxt 1/1 Running 0 115s 192.168.79.119 k8s-worker01 <none> <none>
nginx-dep-5589d85476-4hb62 1/1 Running 0 115s 192.168.79.66 k8s-worker01 <none> <none>
nginx-dep-5589d85476-bgqbb 1/1 Running 0 115s 192.168.79.124 k8s-worker01 <none> <none>
nginx-dep-5589d85476-crlmg 1/1 Running 0 3m16s 192.168.69.235 k8s-worker02 <none> <none>
nginx-dep-5589d85476-flmth 1/1 Running 0 3m16s 192.168.79.77 k8s-worker01 <none> <none>
nginx-dep-5589d85476-fvkl4 1/1 Running 0 3m16s 192.168.79.76 k8s-worker01 <none> <none>
nginx-dep-5589d85476-hw5k4 1/1 Running 0 3m16s 192.168.69.200 k8s-worker02 <none> <none>
nginx-dep-5589d85476-qtgh6 1/1 Running 0 115s 192.168.79.121 k8s-worker01 <none> <none>
新しくデプロイされたPodはすべてworker01にデプロイされていますね。
スケジューリング対象への追加
スケジューリング対象へ追加(元に戻す)には、kubectl uncordonコマンドでノードを指定します。
$ kubectl uncordon k8s-worker02
node/k8s-worker02 uncordoned
$ kubectl get node
NAME STATUS ROLES AGE VERSION
k8s-master Ready master 102d v1.17.3
k8s-worker01 Ready <none> 102d v1.17.3
k8s-worker02 Ready <none> 102d v1.17.3
STATUSの「SchedulingDisabled」がなくなりました。
cordon/uncodonだけですと、新しくデプロイされるPodはデプロイされなくなりますが、既にデプロイされているPodはそのままです。
例えば、今はまだ稼働させておくけど、近い将来停止予定のノードをスケジューリング対象から外す場合が考えられますね。
drain(Podの退避)
kubectl cordonだとノードをスケジューリング対象から外すだけでPodはそのままです。そのため、すぐにノードを停止させたい場合には「kubectl drain」コマンドを使用します。kubectl drainコマンドによって、Podを別ノードに退避させることができます。
Podの退避
以下の環境で確認します。
$ kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-dep-5589d85476-jj9gf 1/1 Running 0 25s 192.168.69.198 k8s-worker02 <none> <none>
nginx-dep-5589d85476-s5xl6 1/1 Running 0 25s 192.168.79.69 k8s-worker01 <none> <none>
kubectl drainコマンドで「k8s-worker02」ノードを指定します。
$ kubectl drain k8s-worker02
node/k8s-worker02 cordoned
evicting pod "nginx-dep-5589d85476-jj9gf"
pod/nginx-dep-5589d85476-jj9gf evicted
node/k8s-worker02 evicted
$ kubectl get node
NAME STATUS ROLES AGE VERSION
k8s-master Ready master 102d v1.17.3
k8s-worker01 Ready <none> 102d v1.17.3
k8s-worker02 Ready,SchedulingDisabled <none> 102d v1.17.3
kubectl drainコマンドを実行すると、cordonも実行されます。その次にk8s-worker02にあったPodがevicted(追い出された)とメッセージにあります。
Podの状態を確認します。
$ kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-dep-5589d85476-jt9pj 1/1 Running 0 4m2s 192.168.79.67 k8s-worker01 <none> <none>
nginx-dep-5589d85476-s5xl6 1/1 Running 0 4m44s 192.168.79.69 k8s-worker01 <none> <none>
k8s-worker02にあったPod(nginx-dep-5589d85476-jj9gf)は停止し、k8s-worker01で新たにPod(nginx-dep-5589d85476-jt9pj)がデプロイされたことがわかります。
別ターミナルでPodの状態変化を確認すると、以下のようになります。
$ kubectl get pod -w
NAME READY STATUS RESTARTS AGE
nginx-dep-5589d85476-jj9gf 1/1 Running 0 31s
nginx-dep-5589d85476-s5xl6 1/1 Running 0 31s
nginx-dep-5589d85476-jj9gf 1/1 Terminating 0 42s
nginx-dep-5589d85476-jt9pj 0/1 Pending 0 0s
nginx-dep-5589d85476-jt9pj 0/1 Pending 0 0s
nginx-dep-5589d85476-jt9pj 0/1 ContainerCreating 0 0s
nginx-dep-5589d85476-jj9gf 0/1 Terminating 0 43s
nginx-dep-5589d85476-jt9pj 0/1 ContainerCreating 0 1s
nginx-dep-5589d85476-jj9gf 0/1 Terminating 0 44s
nginx-dep-5589d85476-jj9gf 0/1 Terminating 0 44s
nginx-dep-5589d85476-jt9pj 1/1 Running 0 7s
workerノードをスケジューリング対象に追加するには、同様にkubectl uncordonを実行します。
$ kubectl uncordon k8s-worker02
node/k8s-worker02 uncordoned
$ kubectl get node
NAME STATUS ROLES AGE VERSION
k8s-master Ready master 102d v1.17.3
k8s-worker01 Ready <none> 102d v1.17.3
k8s-worker02 Ready <none> 102d v1.17.3
drainの注意事項
Kubernetsにはライブマイグレーション機能はありません。そのため、kubectl drainコマンドでは、指定したノードにデプロイされているPodは停止されたのち、Deploymentのセルフヒーリング機能で別のノードにデプロイされました。
そのため、DeploymentやReplicaSetではなく、Podでデプロイされている場合は、evictedされません。
以下の環境で確認します。
$ kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx 1/1 Running 0 14s 192.168.69.255 k8s-worker02 <none> <none>
$ kubectl get deployments.apps
No resources found in default namespace.
k8s-worker02をdrainで除外します。
$ kubectl drain k8s-worker02
node/k8s-worker02 cordoned
node/k8s-worker02 drained
cordonされてノードはスケジューリング対象からは除外されますが、Podはそのまま残っています。
$ kubectl get node
NAME STATUS ROLES AGE VERSION
k8s-master Ready master 102d v1.17.3
k8s-worker01 Ready <none> 102d v1.17.3
k8s-worker02 Ready,SchedulingDisabled <none> 102d v1.17.3
$ kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx 1/1 Running 0 96s 192.168.69.255 k8s-worker02 <none> <none>
なお、kubectl drainコマンドに--forceオプションを付与して実行すると、Podは削除されます。
$ kubectl drain k8s-worker02 --force
node/k8s-worker02 cordoned
WARNING: deleting Pods not managed by ReplicationController, ReplicaSet, Job, DaemonSet or StatefulSet: default/nginx
evicting pod "nginx"
pod/nginx evicted
node/k8s-worker02 evicted
$ kubectl get pod
No resources found in default namespace.
$ kubectl get node
NAME STATUS ROLES AGE VERSION
k8s-master Ready master 102d v1.17.3
k8s-worker01 Ready <none> 102d v1.17.3
k8s-worker02 Ready,SchedulingDisabled <none> 102d v1.17.3
1PodでもDeploymentでデプロイすることで、バージョン管理ができるメリットがありますが、このノードのメンテナンスを考えてもDeploymentにしておいた方がよいですね。
PodDisruptionBudget
PodDisruptionBudget(PDB)はkubectl drainでPodを停止できる最大数を設定するリソースです。設定のパラメータは以下のうち、どちらかを指定します。
- minAvailable:最小のPod起動数、またはパーセンテージで指定
- maxUnavailable:最大のPod停止数、またはパーセンテージで指定
また、対象となるPodをラベルで指定します。
apiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
name: pdb
spec:
# minAvailable: 1
maxUnavailable: 1
selector:
matchLabels:
app: app1
$ kubectl apply -f pdb.yaml
poddisruptionbudget.policy/pdb created
$ kubectl get poddisruptionbudgets.policy
NAME MIN AVAILABLE MAX UNAVAILABLE ALLOWED DISRUPTIONS AGE
pdb N/A 1 1 12s
確認
以下の環境で確認します。
nginxとredisがそれぞれ8Podずつデプロイされています。nginxのラベルが「app1」ですので、PDBの制御対象はnginxになります。
$ kubectl get pod -o wide -L app
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES APP
nginx-dep-5589d85476-crvmc 1/1 Running 0 8m 192.168.79.88 k8s-worker01 <none> <none> app1
nginx-dep-5589d85476-qcmlv 1/1 Running 0 8m 192.168.69.211 k8s-worker02 <none> <none> app1
nginx-dep-5589d85476-rn9tc 1/1 Running 0 8m 192.168.79.91 k8s-worker01 <none> <none> app1
nginx-dep-5589d85476-rxs7f 1/1 Running 0 8m 192.168.79.83 k8s-worker01 <none> <none> app1
nginx-dep-5589d85476-s6tr4 1/1 Running 0 8m 192.168.69.204 k8s-worker02 <none> <none> app1
nginx-dep-5589d85476-vs9bv 1/1 Running 0 8m 192.168.69.212 k8s-worker02 <none> <none> app1
nginx-dep-5589d85476-xzwr5 1/1 Running 0 8m 192.168.69.203 k8s-worker02 <none> <none> app1
nginx-dep-5589d85476-z9g8r 1/1 Running 0 8m 192.168.79.86 k8s-worker01 <none> <none> app1
redis-dep-79fd57c7b9-dwzh9 1/1 Running 0 8m27s 192.168.79.75 k8s-worker01 <none> <none> app2
redis-dep-79fd57c7b9-jsrbg 1/1 Running 0 8m27s 192.168.79.85 k8s-worker01 <none> <none> app2
redis-dep-79fd57c7b9-kg228 1/1 Running 0 8m27s 192.168.69.209 k8s-worker02 <none> <none> app2
redis-dep-79fd57c7b9-l2xlg 1/1 Running 0 8m27s 192.168.69.206 k8s-worker02 <none> <none> app2
redis-dep-79fd57c7b9-pfwmj 1/1 Running 0 8m27s 192.168.79.68 k8s-worker01 <none> <none> app2
redis-dep-79fd57c7b9-qkxwt 1/1 Running 0 8m27s 192.168.69.215 k8s-worker02 <none> <none> app2
redis-dep-79fd57c7b9-vqw55 1/1 Running 0 8m27s 192.168.79.89 k8s-worker01 <none> <none> app2
redis-dep-79fd57c7b9-wwcj2 1/1 Running 0 8m27s 192.168.69.207 k8s-worker02 <none> <none> app2
worker02をdrainします。
$ kubectl drain k8s-worker02
node/k8s-worker02 cordoned
evicting pod "nginx-dep-5589d85476-vs9bv"
evicting pod "nginx-dep-5589d85476-qcmlv"
evicting pod "nginx-dep-5589d85476-s6tr4"
evicting pod "redis-dep-79fd57c7b9-kg228"
evicting pod "nginx-dep-5589d85476-xzwr5"
evicting pod "redis-dep-79fd57c7b9-l2xlg"
evicting pod "redis-dep-79fd57c7b9-qkxwt"
evicting pod "redis-dep-79fd57c7b9-wwcj2"
error when evicting pod "nginx-dep-5589d85476-s6tr4" (will retry after 5s): Cannot evict pod as it would violate the pod's disruption budget.
error when evicting pod "nginx-dep-5589d85476-vs9bv" (will retry after 5s): Cannot evict pod as it would violate the pod's disruption budget.
error when evicting pod "nginx-dep-5589d85476-xzwr5" (will retry after 5s): Cannot evict pod as it would violate the pod's disruption budget.
pod/redis-dep-79fd57c7b9-wwcj2 evicted
evicting pod "nginx-dep-5589d85476-s6tr4"
error when evicting pod "nginx-dep-5589d85476-s6tr4" (will retry after 5s): Cannot evict pod as it would violate the pod's disruption budget.
evicting pod "nginx-dep-5589d85476-vs9bv"
error when evicting pod "nginx-dep-5589d85476-vs9bv" (will retry after 5s): Cannot evict pod as it would violate the pod's disruption budget.
evicting pod "nginx-dep-5589d85476-xzwr5"
error when evicting pod "nginx-dep-5589d85476-xzwr5" (will retry after 5s): Cannot evict pod as it would violate the pod's disruption budget.
pod/redis-dep-79fd57c7b9-kg228 evicted
pod/redis-dep-79fd57c7b9-qkxwt evicted
pod/nginx-dep-5589d85476-qcmlv evicted
pod/redis-dep-79fd57c7b9-l2xlg evicted
evicting pod "nginx-dep-5589d85476-s6tr4"
evicting pod "nginx-dep-5589d85476-vs9bv"
evicting pod "nginx-dep-5589d85476-xzwr5"
error when evicting pod "nginx-dep-5589d85476-vs9bv" (will retry after 5s): Cannot evict pod as it would violate the pod's disruption budget.
error when evicting pod "nginx-dep-5589d85476-xzwr5" (will retry after 5s): Cannot evict pod as it would violate the pod's disruption budget.
pod/nginx-dep-5589d85476-s6tr4 evicted
evicting pod "nginx-dep-5589d85476-vs9bv"
error when evicting pod "nginx-dep-5589d85476-vs9bv" (will retry after 5s): Cannot evict pod as it would violate the pod's disruption budget.
evicting pod "nginx-dep-5589d85476-xzwr5"
error when evicting pod "nginx-dep-5589d85476-xzwr5" (will retry after 5s): Cannot evict pod as it would violate the pod's disruption budget.
evicting pod "nginx-dep-5589d85476-vs9bv"
error when evicting pod "nginx-dep-5589d85476-vs9bv" (will retry after 5s): Cannot evict pod as it would violate the pod's disruption budget.
evicting pod "nginx-dep-5589d85476-xzwr5"
error when evicting pod "nginx-dep-5589d85476-xzwr5" (will retry after 5s): Cannot evict pod as it would violate the pod's disruption budget.
evicting pod "nginx-dep-5589d85476-vs9bv"
evicting pod "nginx-dep-5589d85476-xzwr5"
error when evicting pod "nginx-dep-5589d85476-xzwr5" (will retry after 5s): Cannot evict pod as it would violate the pod's disruption budget.
evicting pod "nginx-dep-5589d85476-xzwr5"
error when evicting pod "nginx-dep-5589d85476-xzwr5" (will retry after 5s): Cannot evict pod as it would violate the pod's disruption budget.
evicting pod "nginx-dep-5589d85476-xzwr5"
pod/nginx-dep-5589d85476-vs9bv evicted
pod/nginx-dep-5589d85476-xzwr5 evicted
node/k8s-worker02 evicted
nginxもredisと同様にすべての対象Pod(4つ)が最初にevicting されていますが、PDBで1つずつに制御されていますね。
でも、「error」が表示されるのはちょっと違和感があります。この機能はまだベータ版ですので、今後変わるのかも知れません。
まとめ
今回はworkerノードメンテナンス時のクラスタ、Podの操作について確認しました。
KubernetsはHypervisor型の仮想化機能と違って、ライブマイグレーション機能がありません。マイクロアーキテクチャの思想から考えて今後も実装されないと思いますので、Podそのものを停止させないのではなく、Podは止まるものとして、保守時を含めてサービスをいかに止めないかを考える必要がありますね。