はじめに
AWS EKS などの Kuberentes をアップデートする際に、PodDisruptionBudget(以下:PDB)の話を社内で聞くことがあります。
しかし、私自身 PodDisruptionBudget について知識がないのでローカルで Kubernetes を動かしながら、挙動を確認していきたいと思います。
この記事では k
から始まるコマンドを実行していますが、これはkubectl コマンドにk
のエイリアスをつけています。
バージョン
バージョン kind v0.19.0 kubectl gitVersion: v1.27.2 Kubernetes v1.27.1
PDBについて調べる
PDBとは
PDBは、レプリケートされたアプリケーションのうち、自発的な中断(voluntary disruptions)によって同時にダウンする Pod の数を制限するリソース。
PDB には、.status.currentHealthy
、 .status.desiredHealthy
という値が存在し、.status.currentHealthy
で指定された Pod の数が .status.destendedHealthy
で指定された数を下回らないようにアプリケーションを保護する。
$ k explain pdb.status.currentHealthy
KIND: PodDisruptionBudget
FIELD: currentHealthy <integer>
current number of healthy pods
$ k explain pdb.status.desiredHealthy
KIND: PodDisruptionBudget
FIELD: desiredHealthy <integer>
minimum desired number of healthy pods
Pod disruption budgets - kubernetes.io
spec
explain コマンドで Spec を確認してみます。
$ k explain pdb.spec --recursive
KIND: PodDisruptionBudget
FIELD: spec <PodDisruptionBudgetSpec>
Specification of the desired behavior of the PodDisruptionBudget.
PodDisruptionBudgetSpec is a description of a PodDisruptionBudget.
maxUnavailable <IntOrString>
minAvailable <IntOrString>
matchExpressions <[]LabelSelectorRequirement>
operator <string> -required-
matchLabels <map[string]string>
unhealthyPodEvictionPolicy <string>
PDB の設定には、minAvailable
、maxUnavailable
があります。まずこの2つの設定について調べてみます。
minAvailable
eviction が実行された後でも、最低限保持しておきたい Pod の数を設定するようです。
.spec.minAvailable which is a description of the number of pods from that set that must still be available after the eviction, even in the absence of the evicted pod. minAvailable can be either an absolute number or a percentage.
訳).spec.minAvailableは、eviction後に、evictionされたポッドがない場合でもまだ利用可能でなければならない、そのセットのポッドの数の説明です。
https://kubernetes.io/docs/tasks/run-application/configure-pdb/#specifying-a-poddisruptionbudget
maxUnavailable
eviction を実行するときに、eviction 可能な Pod の数を設定するようです。
.spec.maxUnavailable (available in Kubernetes 1.7 and higher) which is a description of the number of pods from that set that can be unavailable after the eviction. It can be either an absolute number or a percentage.
訳).spec.maxUnavailable(Kubernetes1.7以降で使用可能)は、そのセットから、eviction後に使用できなくなる可能性のあるPodの数を記述します。これは絶対数でもパーセンテージでもかまいません。
https://kubernetes.io/docs/tasks/run-application/configure-pdb/#specifying-a-poddisruptionbudget
unhealthyPodEvictionPolicy
explain コマンドで Spec を確認したとき、一番下にあった unhealthyPodEvictionPolicy
がありました。
気になるので、これもドキュメント を読みながら調べてみます。
$ k explain pdb.spec.unhealthyPodEvictionPolicy
$ k explain pdb.spec.unhealthyPodEvictionPolicy
KIND: PodDisruptionBudget
FIELD: unhealthyPodEvictionPolicy <string>
UnhealthyPodEvictionPolicy defines the criteria for when unhealthy pods
should be considered for eviction. Current implementation considers healthy
pods, as pods that have status.conditions item with
type = "Ready", status = "True".
Valid policies are IfHealthyBudget and AlwaysAllow. If no policy is
specified, the default behavior will be used, which corresponds to the
IfHealthyBudget policy means that running pods (status.phase= "Running" ), but
not yet healthy can be evicted only if the guarded application is not
disrupted (status.currentHealthy is at least equal to
status.desiredHealthy ). Healthy pods will be subject to the PDB for
AlwaysAllow policy means that all running pods (status.phase= "Running" ), but
not yet healthy are considered disrupted and can be evicted regardless of
whether the criteria in a PDB is met. This means perspective running pods of
a disrupted application might not get a chance to become healthy. Healthy
pods will be subject to the PDB for eviction.
Additional policies may be added in the future. Clients making eviction
decisions should disallow eviction of unhealthy pods if they encounter an
unrecognized policy in this field.
This field is beta-level. The eviction API uses this field when the feature
gate PDBUnhealthyPodEvictionPolicy is enabled (enabled by default ).
- ` "AlwaysAllow" ` policy means that all running pods
( status.phase = "Running" ) , but not yet healthy are considered disrupted and
can be evicted regardless of whether the criteria in a PDB is met. This
means perspective running pods of a disrupted application might not get a
chance to become healthy. Healthy pods will be subject to the PDB for
- ` "IfHealthyBudget" ` policy means that running pods
( status.phase = "Running" ) , but not yet healthy can be evicted only if the
guarded application is not disrupted (status.currentHealthy is at least
equal to status.desiredHealthy ). Healthy pods will be subject to the PDB for
v1.27 からデフォルトで使用できる機能です。
Feature Default Stage Since Until PDBUnhealthyPodEvictionPolicy false Alpha 1.26 1.26 PDBUnhealthyPodEvictionPolicy true Beta 1.27
引用:Feature Gates - kubernetes.io
予備知識: Pod が healthy である、とは?
.status.conditions.type
が Ready
であり、.status.conditions.status
が True
である Pod が healthy である定義です。
Healthiness of a Pod - kubernetes.io
$ k explain pod.status.conditions.type
Type is the type of the condition. More info:
https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-conditions
$ k explain pod.status.conditions.status
Status is the status of the condition. Can be True, False, Unknown. More
https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-conditions
IfHealthyBudget
unhealthyPodEvictionPolicy
を設定していない場合のデフォルト値です。
.status.phase="Running
であり、かつ healthy な Pod のみが eviction の対象です。
.status.phase="Running
だが、CrashLoopBackOff
や設定ファイルのミスなどによって healthy ではない Pod は evict できません。そのため PDB を設定している場合は、drain がブロックされる可能性があります。
(後ほど検証します)
Unhealthy Pod Eviction Policy - kubernetes.io
AlwaysAllow
.status.phase="Running
だが、healthy ではない Pod は中断されたとみなされ、PDB の条件に関係なく evict されます。
(後ほど検証します)
unhealthyPodEvictionPolicy
は(デフォルトは IfHealthyBudget
ですが)AlwaysAllow
を推奨されています。
It is recommended to set AlwaysAllow Unhealthy Pod Eviction Policy to your PodDisruptionBudgets to support eviction of misbehaving applications during a node drain. The default behavior is to wait for the application pods to become healthy before the drain can proceed.
https://kubernetes.io/docs/concepts/workloads/pods/disruptions/#pod-disruption-budgets
その他
その他ドキュメントを読んでわかったことを書きます。
minAvailable
と maxUnavailable
は数字またはパーセンテージ(文字列)を設定することができる
数字を設定した場合は、Podの数を表す
パーセンテージを設定した場合は、Podの総数を表す。
maxUnavailable
を50%に設定したとして、Podが7個ある場合、数字は切り上げられるので4つになる
minAvailable
、maxUnavailable
両方は設定できない
apiVersion: policy/v1beta1
での空のセレクタはどの Pod も対象にならない
1.25から policy/v1beta1
は使えないので、バージョンがそれ以上ならば気にしなくても良い
apiVersion: policy/v1
での空のセレクタは namespace すべての Pod が対象になる
参考:
minAvailable
と maxUnavailable
どっちを使う?
これらはどのように使い分けるのでしょうか?
ドキュメントを読んでみます。
どうやらオートスケーリングのことを考えると、maxUnavailable
の方が推奨されているようです。
The use of maxUnavailable is recommended as it automatically responds to changes in the number of replicas of the corresponding controller.
maxUnavailableの使用は、対応するコントローラのレプリカ数の変化に自動的に対応するため、推奨されます。
Specifying a PodDisruptionBudget - kubernetes.io
実際に動かしてみる
準備: ローカルでKubernetesクラスターを作成する
使用するツールは kind です。
apiVersion : kind.x-k8s.io/v1alpha4
image : kindest/node:v1.27.1
image : kindest/node:v1.27.1
image : kindest/node:v1.27.1
クラスターを作成し、kubectl がサンドボックス環境に向いていることを確認します。
$ kind create cluster --config kind-config.yaml
$ k config current-context
CURRENT NAME CLUSTER AUTHINFO NAMESPACE
* kind-sandbox kind-sandbox kind-sandbox
node が3つ作成されています。
NAME STATUS ROLES AGE VERSION
sandbox-control-plane Ready control-plane 35s v1.27.1
sandbox-worker Ready <none> 15s v1.27.1
sandbox-worker2 Ready <none> 11s v1.27.1
今回、検証に利用する Deployment の設定は以下の通りです。
PDB 無しで drain を実行する
$ k apply -f deployment.yaml
$ k get pod -o custom-columns=Name:metadata.name,STATUS:status.phase,NODE:spec.nodeName
my-app-866598c5b7-lc29w Running sandbox-worker
my-app-866598c5b7-pp2lz Running sandbox-worker2
my-app-866598c5b7-r2stx Running sandbox-worker
my-app-866598c5b7-zswbq Running sandbox-worker2
drain コマンドを実行します。
$ k drain sandbox-worker --ignore-daemonsets
node/sandbox-worker cordoned
Warning: ignoring DaemonSet-managed Pods: kube-system/kindnet-x4tz9, kube-system/kube-proxy-dlx6n
evicting pod default/my-app-866598c5b7-grf7k
evicting pod default/my-app-866598c5b7-xskp2
pod/my-app-866598c5b7-xskp2 evicted
pod/my-app-866598c5b7-grf7k evicted
node/sandbox-worker drained
PDB を設定せずに drain を実行した場合、 Running の Pod が一気に sandbox-worker2 に移動しているような挙動になりました。
node の status に SchedulingDisabled
がつきました。
NAME STATUS ROLES AGE VERSION
sandbox-control-plane Ready control-plane 9h v1.27.1
sandbox-worker Ready,SchedulingDisabled <none> 9h v1.27.1
sandbox-worker2 Ready <none> 9h v1.27.1
pod を確認すると、sandbox-worker2 に Pod が移動していることがわかります。
$ kubectl get pod -o custom-columns=Name:metadata.name,STATUS:status.phase,NODE:spec.nodeName
my-app-866598c5b7-9dx6f Running sandbox-worker2
my-app-866598c5b7-mjxvs Running sandbox-worker2
my-app-866598c5b7-phlds Running sandbox-worker2
my-app-866598c5b7-x4xz8 Running sandbox-worker2
node の status についた SchedulingDisabled
を消して、元に戻します。
$ k uncordon sandbox-worker
$ k delete -f deployment.yaml
PDB 有りで drain を実行する
次は PDB を設定して drain コマンドを実行してみます。
kind : PodDisruptionBudget
$ k apply -f deployment.yaml
NAME MIN AVAILABLE MAX UNAVAILABLE ALLOWED DISRUPTIONS AGE
$ k drain sandbox-worker --ignore-daemonsets
maxUnavailable: 1
を設定しているので、sandbox-worker からひとつずつ sandbox-worker2 に移動していることが確認できます。
node の status についた SchedulingDisabled
を消して、元に戻します。
$ k uncordon sandbox-worker
$ k delete -f deployment.yaml
minAvailable
、maxUnavailable
両方設定する
kind : PodDisruptionBudget
ドキュメント通り実行に失敗しました。
The PodDisruptionBudget "example-pdb" is invalid: spec: Invalid value: policy.PodDisruptionBudgetSpec{MinAvailable: ( * intstr.IntOrString)( 0x4009b75280 ) , Selector: ( * v1.LabelSelector)( 0x4009b752a0 ) , MaxUnavailable: ( * intstr.IntOrString)( 0x4009b75260 ) , UnhealthyPodEvictionPolicy: ( * policy.UnhealthyPodEvictionPolicyType)( nil ) }: minAvailable and maxUnavailable cannot be both set
policy/v1でselectorを空にする
Kubernetes のバージョンは 1.27.1 です。
NAME STATUS ROLES AGE VERSION
sandbox-control-plane Ready control-plane 11d v1.27.1
sandbox-worker Ready <none> 11d v1.27.1
sandbox-worker2 Ready <none> 11d v1.27.1
apiVersion: policy/v1
の PDB を作成します。
kind : PodDisruptionBudget
apiVersion: policy/v1
での空のセレクタは namespace すべての Pod が対象になるので、.status.currentHealthy
を確認してみます。
Pod が 4つある状態です。
$ k apply -f deployment.yaml
$ k get pod -o custom-columns=Name:metadata.name,STATUS:status.phase,NODE:spec.nodeName
my-app-866598c5b7-bjlq4 Running sandbox-worker2
my-app-866598c5b7-d2jwq Running sandbox-worker
my-app-866598c5b7-l696j Running sandbox-worker
my-app-866598c5b7-sxqx8 Running sandbox-worker2
.status.currentHealthy
が 4 になっている、ということは Pod 4つが対象に入っていることがわかります。
$ k get pdb example-pdb -o yaml | tail -5
drain コマンドを実行して挙動を確認してみます。
$ k drain sandbox-worker --ignore-daemonsets
node/sandbox-worker cordoned
Warning: ignoring DaemonSet-managed Pods: kube-system/kindnet-zb9vb, kube-system/kube-proxy-z79pc
evicting pod default/my-app-866598c5b7-fm6xs
evicting pod default/my-app-866598c5b7-6vrqt
pod/my-app-866598c5b7-fm6xs evicted
pod/my-app-866598c5b7-6vrqt evicted
node/sandbox-worker drained
eviction は実行されましたが、挙動は PDB を設定していない時と変わらないように見えました。
node の status についた SchedulingDisabled
を消して、元に戻します。
$ k uncordon sandbox-worker
$ k delete -f deployment.yaml
policy/v1beta1でselectorを空にする
今度は Kubernetes のバージョンを 1.24.13 にして確認します。
NAME STATUS ROLES AGE VERSION
sandbox-control-plane Ready control-plane 40s v1.24.13
sandbox-worker Ready <none> 20s v1.24.13
sandbox-worker2 Ready <none> 20s v1.24.13
Deployment は先ほどと同じです。
$ k apply -f deployment.yaml
$ k get pod -o custom-columns=Name:metadata.name,STATUS:status.phase,NODE:spec.nodeName
my-app-585c9c5558-2s989 Running sandbox-worker
my-app-585c9c5558-8cjzj Running sandbox-worker
my-app-585c9c5558-g8vl5 Running sandbox-worker2
my-app-585c9c5558-w79pj Running sandbox-worker2
apiVersion を policy/v1beta1
に変更しました。
apiVersion : policy/v1beta1
kind : PodDisruptionBudget
Warning: policy/v1beta1 PodDisruptionBudget is deprecated in v1.21+, unavailable in v1.25+ ; use policy/v1 PodDisruptionBudget
poddisruptionbudget.policy/example-pdb created
.status.currentHealthy
を確認してみると、 policy/v1beta1
の場合、どの Pod も対象に入っていないことがわかりました。
$ k get pdb example-pdb -o yaml | tail -5
drain コマンドを実行して挙動を確認してみます。
$ k drain sandbox-worker --ignore-daemonsets
node/sandbox-worker cordoned
Warning: ignoring DaemonSet-managed Pods: kube-system/kindnet-rwqzs, kube-system/kube-proxy-gskwk
evicting pod default/my-app-585c9c5558-4vlcl
evicting pod default/my-app-585c9c5558-mggjt
pod/my-app-585c9c5558-4vlcl evicted
pod/my-app-585c9c5558-mggjt evicted
node/sandbox-worker drained
eviction は実行されましたが、挙動は PDB を設定していない時と変わらないように見えました。
Kubernetes のバージョンを v1.27.1 に戻します。
$ kind delete cluster --name sandbox
# v1.27.1 に kind-config.yaml を編集
$ kind create cluster --config kind-config.yaml
unhealthyPodEvictionPolicy を設定してみる
Kubernetes 1.27 からデフォルトで使えるようになった unhealthyPodEvictionPolicy
について調べてみます。
deployment.yaml を以下のように編集します。
nginx コンテナのコマンドに exit 1
を入れ、Pod の起動に失敗させる
Pod が sandbox-worker ノードにスケジュールされるように affinity
を追加する
command : [ 'sh' , '-c' , "sleep 2 && exit 1" ]
preferredDuringSchedulingIgnoredDuringExecution :
- key : kubernetes.io/hostname
IfHealthyBudget
デフォルトでは unhealthyPodEvictionPolicy: IfHealthyBudget
になります。
The default behavior when no policy is specified corresponds to the IfHealthyBudget policy.
Unhealthy Pod Eviction Policy - kubernetes.io
PDB は以下の通りです。
kind : PodDisruptionBudget
$ k apply -f deployment.yaml
意図した通り Podが CrashLoopBackOff
になりました。
$ watch -t kubectl get pod -o custom-columns=Name:metadata.name,STATUS:status.phase,REASON:status.containerStatuses[0].state.waiting.reason,NODE:spec.nodeName
my-app-d66699f7f-mjf2z Running CrashLoopBackOff sandbox-worker
my-app-d66699f7f-rs2cs Running CrashLoopBackOff sandbox-worker
PDB の status は以下のようになっています。
$ watch -t "kubectl get pdb example-pdb -o yaml | tail -5"
drain を実行してみると、eviction に失敗しました。sandbox-worker ノードのステータスには SchedulingDisabled
が付きましたが、Pod は evict されず、 sandbox-worker ノードに残ったままでした。
$ k drain sandbox-worker --ignore-daemonsets
node/sandbox-worker cordoned
Warning: ignoring DaemonSet-managed Pods: kube-system/kindnet-zvxtb, kube-system/kube-proxy-5r2b2
evicting pod default/my-app-d66699f7f-rs2cs
evicting pod default/my-app-d66699f7f-mjf2z
error when evicting pods/"my-app-d66699f7f-mjf2z" -n "default" (will retry after 5s ): Cannot evict pod as it would violate the pod 's disruption budget.
error when evicting pods/"my-app-d66699f7f-rs2cs" -n "default" (will retry after 5s): Cannot evict pod as it would violate the pod' s disruption budget.
node の status についた SchedulingDisabled
を消して、元に戻します。
$ k uncordon sandbox-worker
AlwaysAllow
次に PDB の設定を unhealthyPodEvictionPolicy: AlwaysAllow
にします。
kind : PodDisruptionBudget
unhealthyPodEvictionPolicy : AlwaysAllow
Pod は引き続き CrashLoopBackOff
です。
$ watch -t kubectl get pod -o custom-columns=Name:metadata.name,STATUS:status.phase,REASON:status.containerStatuses[0].state.waiting.reason,NODE:spec.nodeName
my-app-d66699f7f-lkrq4 Running CrashLoopBackOff sandbox-worker
my-app-d66699f7f-v676t Running CrashLoopBackOff sandbox-worker
PDB の status も変わっていません。
$ watch -t "kubectl get pdb example-pdb -o yaml | tail -5"
PDB に unhealthyPodEvictionPolicy: AlwaysAllow
を設定したので drain を実行してみると、無事に drain できました。
この設定があると、PDB によってブロックされる Pod がなくなります。
$ k drain sandbox-worker --ignore-daemonsets
node/sandbox-worker cordoned
Warning: ignoring DaemonSet-managed Pods: kube-system/kindnet-zvxtb, kube-system/kube-proxy-5r2b2
evicting pod default/my-app-d66699f7f-v676t
evicting pod default/my-app-d66699f7f-lkrq4
pod/my-app-d66699f7f-v676t evicted
pod/my-app-d66699f7f-lkrq4 evicted
node/sandbox-worker drained
クラスターの削除
$ kind delete cluster --name sandbox
~/.kube/config
から不要なコンテキストを削除する
$ for x in {cluster,context,user} ; do kubectl config delete- $x <コンテキスト名> ; done
まとめ
healthy である Pod とは、.status.conditions.type
が Ready
であり、.status.conditions.status
が True
である Pod のことを指す。
minAvailable
、maxUnavailable
どちらを設定すればいいか迷ったときは、maxUnavailable
を設定しておくと良い
Kuberentes v1.27 からは unhealthyPodEvictionPolicy: AlwaysAllow
の設定が推奨される。
apiVersion: policy/v1
での空のセレクタは namespace すべての Pod が対象になる
今回使用したコードは以下にあります。
https://github.com/kntks/blog-code/tree/main/2023/06/pod-disruption-budget
参考