動作例からKubernetes PDBの挙動を理解する

 こんにちは!横浜国立大学理工学部情報工学EP3年の @shion1305 です。今年の10月から株式会社メルペイ Settlementチームにてバックエンドエンジニアのインターンとして所属しています。

この記事は、Merpay Advent Calendar 2023 の4日目の記事です。

Settlementチームでは、メルペイ加盟店で発生した売上金を加盟店に振り込む際に、指示を出したりデータの管理を行ったりするマイクロサービスの開発を行っています。Settlementのマイクロサービスについては こちらの記事 でも紹介されています。

私がJoinして早々担当したタスクの一つが、Settlementサービスに対するKubernetesのPod Distruption Budget(PDB)設定の見直しでした。Kubernetesについてはこれまで個人開発で少し調べたことがある程度でした。今回PDB見直しを行う中で仕様についてリサーチをしていたのですが、多くの記事がある中で実際の挙動について自分が思うような記事があまり見つかりませんでした。今回のAdvent Calendarを機に、自分なりにわかりやすくまとめてみることにしました。

本記事では、特に以下にフォーカスします。

  • PDBの挙動
  • パーセント指定を用いた場合のPDBの動き
  • minAvailable maxUnavailable それぞれのポイント

Pod Disruption Budgetの概要

 PDB(Pod Disruption Budget)は、 Voluntary Disruptions(システムの計画的な中断) からアプリケーションの可用性を保護するKubernetesの機能です。 Voluntary Disruption の具体例としては以下が挙げられます。

  • kubectl drain でのNodeからのPod退避
  • Nodeスケールダウン時のPod退避
  • Node間のPodの移動

(NodeからPodを他のNodeに退避させる操作を、以降Drain操作と表記します。)
 例えば、以下のように記述すると、 Voluntary Disruptions において、使用できないPodの割合を33%までとなるように指定できます。

apiVersion: policy/v1 
kind: PodDisruptionBudget 
metadata: 
  name: pdb-sample 
  namespace: namespace-sample 
spec: 
  maxUnavailable: 33%
  selector:
    matchLabels:
      app: pdb-tester

 PDBの設定においては、 minAvailable または maxUnavailable のいずれか一方を指定することが可能です。 minAvailable では、指定したPod群の中で常に稼働していなければならない最小限のPodの数または割合を指定でき、 maxUnavailable は、一度に中断または利用不可となっても良いPodの最大数または割合を指定できます。

自動スケールによってPod数が変動する場合において固定値を設定するのみではPod数に応じたPDBの挙動を設定することができないため、パーセント指定が用いられることがあります。実際、メルカリグループのマイクロサービスで設定されているPDBの多くでは、パーセント指定が用いられています。
 しかし、パーセント指定を行う場合、PDBがどのような挙動をとるのか、分かりにくくなってしまいがちです。

パーセント指定におけるPDBの挙動

公式ドキュメントでの記載

公式ドキュメントには以下のように、パーセント指定で中途半端な数になった時には数が繰り上げられることが明記されています。

When you specify the value as a percentage, it may not map to an exact number of Pods.
(中略..)
Kubernetes rounds up to the nearest integer, so in this case, 4 Pods must be available. When you specify the value maxUnavailable as a percentage, Kubernetes rounds up the number of Pods that may be disrupted. Thereby a disruption can exceed your defined maxUnavailable percentage.
https://kubernetes.io/docs/tasks/run-application/configure-pdb/#specifying-a-poddisruptionbudget

対応するソースコードを見てみる

 中途半端な数になった際の繰り上げの振る舞いについて、実際のソースコードを確認してみます。 minAvailablemaxUnavailable の読み取りに対応する部分は以下のようになっています。
/pkg/controller/disruption/disruption.go L797-L800 (2023/12/1時点)

maxUnavailable, err = intstr.GetScaledValueFromIntOrPercent(pdb.Spec.MaxUnavailable, int(expectedCount), true) 
if err != nil { 
    return 
} 
minAvailable, err = intstr.GetScaledValueFromIntOrPercent(pdb.Spec.MinAvailable, int(expectedCount), true) 
if err != nil { 
    return 
}

ここで用いられている GetScaledValueFromIntOrPercent の中身の実装はこのようになっていて、上記では パラメータとして roundUp: true が指定されているため、繰り上げ処理がされていることが確認できます。

func GetScaledValueFromIntOrPercent(intOrPercent *IntOrString, total int, roundUp bool) (int, error) { 
    (中略...) 
    if isPercent { 
        if roundUp { 
            value = int(math.Ceil(float64(value) * (float64(total)) / 100)) 
        } else { 
            value = int(math.Floor(float64(value) * (float64(total)) / 100)) 
        } 
    } 
    return value, nil 
}

PDB設定時の動作例

前述で繰り上げ処理が行われることはわかりましたが、受け付けた値に対してPDBがどのような動作をするのかについて、いくつかケースを想定して検証しました。

今回想定した流れは以下の通りです。

  1. Nodeが2つ存在している
  2. 片方にPodが入っていて、PDBが設定されている
  3. Podが存在しているNodeに対して kubectl drain が実行され、Podの退避処理が実行される

ケース1: Pod数:2, maxUnavailable:50%

maxUnavailableのPod数は1Podとして計算されます。その結果、図のように可用性を担保した状態で Drain操作ができることが確認できました。

ケース1 Pod数:2 maxUnavailable:50%の時の挙動説明図

ケース2: Pod数:1, maxUnavailable:50%

 maxUnavailableのPod数は1となります。そのためDrain操作が可能となりますが、退避するPodの中断処理と新しいPodの起動処理は同時に実行されるため、Drain操作中に利用可能なPod数が0となる可能性があります。
 このようにmaxUnavailableを用いる場合、最小pod数を考慮する必要が出てきます。(Pod数が1の時にあえてDrainできないようにする手段もあるようです。 公式ドキュメント)

ケース2 Pod数:2 maxUnavailable:50%の時の挙動説明図

ケース3: Pod数:2, maxUnavailable: 80%

 maxUnavailableのPod数は2となります。そのため2Podに対して同時に退避処理が実行されます。そのため、drain操作中に利用可能なPod数が0となる可能性があり、可用性に問題が出る可能性があります。

ケース3 Pod数:2 maxUnavailable:80%の時の挙動説明図

ケース4: Pod数:1, minAvailable: 50%

 minAvailableのPod数は1となります。PDBはdrain操作のためにPod数の調節を行うことはしないため、PDBの条件を満たすDrain操作ができず、Drain操作は止まったままの状態となります。
 このように、minAvailableを用いる場合でも最小pod数を意識する必要が出てきます。

ケース4 Pod数:1 minAvailable:50%の時の挙動説明図

minAvailableとmaxUnavailableの整理

 これまで具体的なケースにおけるPDBの影響を確認しました。
 PDBではminAvailableとmaxUnavailableどちらかのみしか設定することができません。それぞれを採用することによるメリット、考慮すべき点について以下にまとめます。

minAvailable

メリット

  • 0より大きい数を指定しておけば、Drain操作時において必ず1Pod以上動作させて可用性を持たせることができる。

考慮が必要な点

  • (パーセント指定ではなくPod数で指定した場合) スケールアップして多くのPodがPDBの管理対象となっている場合、Drain操作で一度に多くのPodが利用できない状態となり、パフォーマンスに影響が出る可能性がある。
  • 適切に値を設定しないとDrain操作ができなくなってしまう。(特に管理対象のPodが少数である場合は注意が必要)

Drain操作ができなくなる条件

  • パーセント指定の場合、現存のPod数に対して計算を行い繰り上げ処理が行われた結果、minAvailableのPod数がPDBが対象とするPod数と同数になる時。
  • Pod数指定の場合、minAvailableのPod数がPDBが対象とするPod数より多くなってしまう場合。

maxUnavailable

メリット

  • Drain操作ができなくなる状態は基本発生しない。

    考慮が必要な点

  • 適切に数値を設定しないとDrain操作中に全てのPodが中断してしまう可能性がある。

    Drain操作ができなくなる条件

  • maxUnavailableを0に設定する。
  • Pod数が1の状態のままでは可用性を持ってDrain操作をすることができないため、あえて maxUnavailable: 0 を設定して警告を出すために設定する方法もあるようです。
    公式ドキュメント

まとめ

 PDBの設定に関しては、minAvailable と maxUnavailable のどちらが良いという一般的な答えは存在しません。システムによって要件は異なるのでPodの最小稼働数や中断許容範囲を理解し、それに基づいて適切な設定をすることが重要です。今回の記事が、皆さんのPDBに関する理解を深め、より効果的なKubernetesの運用に役立つ一助となれば幸いです。

明日の記事はmikichinさんです。引き続きお楽しみください!

  • X
  • Facebook
  • linkedin
  • このエントリーをはてなブックマークに追加