ドメインを考慮した柔軟なPodの配置を実現する「Balancer」
はじめに
こんにちは。3-shakeのSreake事業部に所属する長澤翼(@toversus26)です。第2回目の今回は、ドメインを考慮した柔軟なPodの配置を実現する「Balancer」について紹介します。
KubernetesにおけるPodの配置の課題
Kubernetesにはノード上へのPodのスケジューリングを制御するための機能がいくつかあります。NodeSelector、AffinityとAntiAffinity、TaintとToleration、Pod Topology Spread Constraintsなどです。この中でもPod Topology Spread Constraintsの機能は強力で、ドメインを考慮して均等にPodを配置できます。例えば、Podを複数のゾーンのノードになるべく均等に分散して耐障害性を高めるといった使い方ができるでしょう。
しかし、Pod Topology Spread Constraintsには問題があります。Pod Topology Spread Constraintsがドメインを考慮してPodを配置するのはスケジューリング時のみです。Podがノードにスケジュールされた後、Podの再配置は行われません。平常時とピーク時でPodの台数が異なるサービスでは、HPAによりピーク時間帯に増えたPodの台数が平常時に減ることになります。Podがスケールインする際にはドメインを考慮してPodを削除しないため、Podの配置が特定のドメインに偏る可能性が出てきます。
この問題を解決するためにDeschedulerを導入し、RemovePodsViolatingTopologySpreadConstraintのルールを設定する方法が広く使われています。Pod Topology Spread Constraintsに違反したPodを定期的にEvictして、スケジューリングを強制することでPodの配置を整える戦略です。
Pod Topology Spread ConstraintsはあくまでドメインにPodを均等に配置する機能です。maxSkewの設定次第では不均一なPodの配置を表現できますが、表現力は弱くなります。例えば、クラウドサービスで通常より低価格なSpotインスタンスを通常のOn-Demandインスタンスと併用したい場合を考えます。Spotインスタンスはクラウドサービス内の余剰インスタンスを低価格で提供する仕組みです。需要によって変動するため在庫の保証はなく、インスタンスがいつ停止してもおかしくありません。そのため、On-Demandインスタンス上で動作するPodを20%、Spotインスタンス上で動作するPodを80%の割合で配置したいといったユースケースが出てきます。また、Spotインスタンスを確保できる保証がないため、確保できない場合はOn-DemandインスタンスでPodを起動するようにフォールバックすることが望ましいでしょう。しかし、Kubernetes 1.28時点の機能でこれらの要件を実現することは困難です。
この問題はKubernetesのupstreamでも改善できないか議論されています。興味のある方はKEP-3990やuneven Pods spreading across domainsを確認してみると良いでしょう。今回で取り上げるBalancerも、この問題を解決するために開発されたツールの1つになります。
Balancerの概要
Balancerはkubernetes/autoscalerのリポジトリ配下でSIG-Autoscalingによって開発されています。基本的な機能の実装は終わっていますが、2023年11月時点で0.1.1が最新バージョンです。本番環境で利用する場合は注意してください。
Balancerはカスタムリソースとそのコントローラーで構成されています。Balancer controllerがBalancerのカスタムリソースをreconcileし、ポリシーに従って複数のDeploymentのreplicasを増減します。
ここで注意したいのは、ユーザーがDeploymentを作成する必要がある点です。Balancerのカスタムリソースでは既存のDeploymentを参照します。Balancer controllerがBalancerのカスタムリソースを元にDeploymentを作成するわけではありません。ユーザーがリージョン、ゾーン、ラック、ノード、Spot/On-Demandなどのドメイン毎にDeploymentを作成する戦略をあえてとることで柔軟性を高めています。しかし、Deploymentを複数管理するのは煩雑で、その分管理コストが増加してしまいます。Deploymentを複数管理するために、Helm chartのテンプレート機能を活用するなど抽象化すると良いでしょう。
ユーザーがドメイン毎にDeploymentを作成し、BalancerのカスタムリソースでDeploymentを参照しつつ、配置数の決定に関わる全体のreplicasやポリシーを指定します。Balancer controllerはポリシーに従ってDeploymentのreplicasを増減することで、各ドメインのPodの配置数を調整していきます。Balancerのポリシーには割合と優先順位の2種類あります。各ドメインの割合で配置数を決定するか、各ドメインを指定した順番で配置数を埋めていくかの違いです。ここでは割合のポリシーを前提に話を進めます。
Balancerは複数のドメインにDeploymentを配置する調整役です。Balancerにはscaleサブリソースが実装されており、HPAと連携可能です。HPAはscaleサブリソースを持つリソースのreplicasを増減できるため、Balancerのreplicasを調整できます。結果として、BalancerはDeploymentとHPAの仲介役と見ることもできます。
Balancerのreplicasが変更された際に、Balancer controllerが各ドメインに対応するDeploymentのreplicasを増減することで、Podのスケールイン時にもドメイン毎の割合を考慮してPodの台数を減らすことができます。Deschedulerを導入して定期的にPodをEvictすることで、ドメイン毎の割合を調整する必要はありません。
Balancerを介したPodの配置数の調整の流れは以下の通りです。
- HPA controllerがワークロードの負荷を検知してBalancerのreplicasを変更
- Balancer controllerがBalancerのポリシーを考慮しつつPodの配置数を決定
- Balancerはドント式というアルゴリズムでPodの配置数を決定する。選挙における議席配分の計算で使われる方法
- 対応するドメインのDeploymentのreplicasを変更
- 一定時間経過してもDeploymentのreplicasの台数のPodが起動しない場合、そのドメインでの配置に失敗したと判断して別のドメインにフォールバック
- 失敗したドメインを除外してPodの配置数を再計算する
Balancerは複数のドメインに渡ってPodの配置数を調整し、Podのスケールイン時にもPodの配置を維持できる点と別のドメインにフォールバックする仕組みが組み込まれている点が大きな特徴です。
Balancerのインストール
BalancerのCRDとコントローラーをインストールします。Helm chartは用意されていないため、マニフェストを直接参照して反映しています。リリースタグが切られていないので、コミットハッシュ値で固定するなど工夫が必要です。
kubectl apply -f https://raw.githubusercontent.com/kubernetes/autoscaler/cc888a18aae490063ce45eb4be41858169f60762/balancer/deploy/crd.yaml kubectl apply -n kube-system -f https://raw.githubusercontent.com/kubernetes/autoscaler/cc888a18aae490063ce45eb4be41858169f60762/balancer/deploy/controller.yaml
Spotインスタンスの併用
ゾーン毎の分散を維持しつつ、On-Demand/Spotインスタンスを2:8の割合で利用するケースを考えてみましょう。
- On-DemandとSpotインスタンスの併用
- On-DemandとSpotインスタンスの割合は2:8
- Spotインスタンスを起動できない場合は、On-Demandインスタンスを使ってPodを起動できるようにフォールバックしたい
- Spotインスタンスはゾーンによって在庫状況が異なる
- Topology Spread Constraintsでゾーン毎に均一にPodを配置(ベースライン)
- 在庫不足などの問題が起きた場合に別のゾーンにPodを配置可能に
図の青色のDeploymentに相当するOn-Demandインスタンス用のDeploymentを用意します。こちらのDeploymentを基本形として、ラベルやノードへのスケジューリングに関わるフィールドのみを変更したDeploymentを追加していきます。
apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: selector: matchLabels: app: nginx mode: on-demand spread: random template: metadata: labels: app: nginx mode: on-demand spread: random spec: containers: - name: nginx image: nginx:1.24.0 ports: - containerPort: 80 resources: requests: cpu: 100m
次に、図の赤色に相当するDeploymentを作成します。GKEのノード自動プロビジョニングでSpotインスタンスを利用するためのノードセレクタやTolerationsを追加しています。
apiVersion: apps/v1 kind: Deployment metadata: name: nginx-spot spec: selector: matchLabels: app: nginx mode: spot spread: random template: metadata: labels: app: nginx mode: spot spread: random spec: containers: - name: nginx image: nginx:1.24.0 ports: - containerPort: 80 resources: requests: cpu: 100m nodeSelector: cloud.google.com/gke-spot: "true" tolerations: - key: cloud.google.com/gke-spot operator: Equal value: "true" effect: NoSchedule
最後に、図の緑色に相当するDeploymentを作成します。赤色のDeploymentにPod Topology Spread Constraintsの設定を追加しています。緑色のDeploymentがベースラインのDeploymentで、赤色のDeploymentが特定のゾーンでSpotインスタンスの在庫が枯渇した場合のフォールバック用のDeploymentになります。
apiVersion: apps/v1 kind: Deployment metadata: name: nginx-spot-even-spread spec: selector: matchLabels: app: nginx mode: spot spread: even template: metadata: labels: app: nginx mode: spot spread: even spec: containers: - name: nginx image: nginx:1.24.0 ports: - containerPort: 80 resources: requests: cpu: 100m nodeSelector: cloud.google.com/gke-spot: "true" tolerations: - key: cloud.google.com/gke-spot operator: Equal value: "true" effect: NoSchedule topologySpreadConstraints: - maxSkew: 1 topologyKey: topology.kubernetes.io/zone whenUnsatisfiable: DoNotSchedule labelSelector: matchLabels: app: nginx mode: spot spread: even
Balancerのポリシーとして、proportional(割合)を指定したマニフェストを用意します。
- On-Demandインスタンスで起動するPodとSpotインスタンスで起動するPodの割合は全体で2:8となるように指定
- ベースラインはSpotインスタンスで起動かつPod Topology Spread Constraintsによりゾーン毎に均等に配置
- 最小台数として6つのPodを確保する
- 特定のゾーンでのSpotインスタンスの在庫不足に対応できるようにTopology Spread ConstraintsなしのDeploymentをフォールバック先として用意
- 最小台数はあえて指定せず、ベースラインが確保できなかった場合の補助として動作するようにしている
- Spotインスタンスを確保できない場合があるので、一定時間経過してもPodが起動しない場合は別のドメインにフォールバック
spec.policy.fallback.startupTimeoutSeconds
に180秒を指定
apiVersion: balancer.x-k8s.io/v1alpha1 kind: Balancer metadata: name: nginx spec: replicas: 10 selector: matchLabels: app: nginx policy: policyName: proportional proportions: targetProportions: nginx: 20 nginx-spot: 10 nginx-spot-even-spread: 70 fallback: startupTimeoutSeconds: 180 targets: - name: nginx scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: nginx minReplicas: 2 maxReplicas: 4 - name: nginx-spot scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: nginx-spot maxReplicas: 10 - name: nginx-spot-even-spread scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: nginx-spot-even-spread minReplicas: 6 maxReplicas: 10
HPAと連携する場合は、scaleTargetRefでBalancerを参照します。BalancerとHPAの両方でminReplicas/maxReplicasを設定できるため、ドメイン毎とクラスタ全体でそれぞれ柔軟に台数を調整できます。
apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: nginx spec: minReplicas: 2 maxReplicas: 30 metrics: - resource: name: cpu target: averageUtilization: 40 type: Utilization type: Resource scaleTargetRef: apiVersion: balancer.x-k8s.io/v1alpha1 kind: Balancer name: nginx
マニフェストを反映するとPodが起動します。Balancerの各ドメインのminReplicasで指定したnginxのPodが2台とnginx-spot-even-spreadのPodが6台は尊重されます。それ以外に各ドメインの割合を満たすよう配置数を計算して、nginx-spotのPodが1台とnginx-spot-even-spreadのPodが1台起動しました。
❯ kubectl get pods NAME READY STATUS RESTARTS AGE nginx-769f7c6844-76k98 1/1 Running 0 29m nginx-769f7c6844-fmmqc 1/1 Running 0 29m nginx-spot-794f599d87-w6q8s 1/1 Running 0 3m nginx-spot-even-spread-b4c96c456-4z8sr 1/1 Running 0 29m nginx-spot-even-spread-b4c96c456-5r85t 1/1 Running 0 29m nginx-spot-even-spread-b4c96c456-9zsb4 1/1 Running 0 29m nginx-spot-even-spread-b4c96c456-h7pmv 1/1 Running 0 29m nginx-spot-even-spread-b4c96c456-nwwgj 1/1 Running 0 29m nginx-spot-even-spread-b4c96c456-ttt6b 1/1 Running 0 2m59s nginx-spot-even-spread-b4c96c456-xsdp5 1/1 Running 0 29m
負荷を掛けていないため、しばらくするとクラスタ全体のPod数がHPAのminReplicasの数に戻ります。minReplicasを指定したドメインのPodの台数は保証されるので、nginx-spotのPodが1台とnginx-spot-even-spreadのPodが1台がそれぞれ停止しました。今回は各ゾーンにDeploymentを作成していないため、停止するnginx-spot-even-spreadのPodはゾーンのドメインを考慮せずに削除される点に注意してください。各ゾーンで厳密に割合ベースでPodを配置したい場合は、On-Demand/Spotインスタンスと3ゾーンの計6つのDeploymentを作成する必要があります。
❯ kubectl get pods NAME READY STATUS RESTARTS AGE nginx-769f7c6844-76k98 1/1 Running 0 31m nginx-769f7c6844-fmmqc 1/1 Running 0 31m nginx-spot-even-spread-b4c96c456-5nkzj 1/1 Running 0 66s nginx-spot-even-spread-b4c96c456-76ssn 1/1 Running 0 65s nginx-spot-even-spread-b4c96c456-g2tlg 1/1 Running 0 66s nginx-spot-even-spread-b4c96c456-t5jdr 1/1 Running 0 66s nginx-spot-even-spread-b4c96c456-tpkls 1/1 Running 0 66s nginx-spot-even-spread-b4c96c456-ttt6b 1/1 Running 0 4m48s
Balancerはscaleサブリソースを実装しているので、kubectlで簡単にスケールできます。試しに、HPAのmaxReplicasで指定している30に更新してみましょう。HPAの設定では最大で30台までPodを起動可能ですが、Balancerの各ドメインのmaxReplicasを合計すると24台です。差分の6台はどのように扱われるでしょうか。
❯ kubectl scale balancer nginx --replicas 30 balancer.balancer.x-k8s.io/nginx scaled
24台のPodのみ起動することが分かります。Balancerのtargetsで設定したmaxReplicasの合計値がHPAで設定したmaxReplicasの値よりも優先される点は注意が必要です。
❯ kubectl get pods --no-headers | wc -l 24 ❯ kubectl get pods NAME READY STATUS RESTARTS AGE nginx-769f7c6844-76k98 1/1 Running 0 7h19m nginx-769f7c6844-fmmqc 1/1 Running 0 7h19m nginx-769f7c6844-qb7kl 1/1 Running 0 6m22s nginx-769f7c6844-vv97s 1/1 Running 0 6m21s nginx-spot-794f599d87-4m4js 1/1 Running 0 4m56s nginx-spot-794f599d87-4zlk2 1/1 Running 0 2m2s nginx-spot-794f599d87-87x4q 1/1 Running 0 2m2s nginx-spot-794f599d87-fv7kx 1/1 Running 0 4m55s nginx-spot-794f599d87-g29m8 1/1 Running 0 6m21s nginx-spot-794f599d87-gd7b5 1/1 Running 0 2m2s nginx-spot-794f599d87-sc782 1/1 Running 0 2m2s nginx-spot-794f599d87-sxl8m 1/1 Running 0 6m22s nginx-spot-794f599d87-t6s6f 1/1 Running 0 4m56s nginx-spot-794f599d87-wt9pn 1/1 Running 0 4m55s nginx-spot-even-spread-b4c96c456-2lnqm 1/1 Running 0 6h36m nginx-spot-even-spread-b4c96c456-5dhtv 1/1 Running 0 4m55s nginx-spot-even-spread-b4c96c456-bfwkt 1/1 Running 0 4m55s nginx-spot-even-spread-b4c96c456-d45xf 1/1 Running 0 4m56s nginx-spot-even-spread-b4c96c456-fq44k 1/1 Running 0 4m56s nginx-spot-even-spread-b4c96c456-g2tlg 1/1 Running 0 6h49m nginx-spot-even-spread-b4c96c456-qd7ph 1/1 Running 0 4m55s nginx-spot-even-spread-b4c96c456-s7t7s 1/1 Running 0 4m56s nginx-spot-even-spread-b4c96c456-tpkls 1/1 Running 0 6h49m nginx-spot-even-spread-b4c96c456-ttt6b 1/1 Running 0 6h53m
おわりに
今回は、KubernetesのSIG-Autoscalingが開発しているBalancerについて紹介しました。Podをゾーンやリージョンなどのドメインを考慮して配置する機能はKubernetesにもありますが、Balancerはより柔軟にPodの配置を制御・維持できます。既存のDeploymentやHPAと連携できる点も嬉しいです。Deploymentをドメイン毎に作成する必要がある点が導入の障壁になりますが、そのおかげで他の方法に比べて柔軟性が高くなっています。特にOn-DemandインスタンスとSpotインスタンスを併用しており、それぞれのインスタンスに割合でPodを配置したい場合に導入を検討しても良いかもしれません。SIG-Autoscalingの方のコメントにあるように、Balancerの需要によっては今後開発リソースを投資するそうなので、ぜひ触ってみてフィードバックをお願いします。
連載バックナンバー
Think ITメルマガ会員登録受付中
全文検索エンジンによるおすすめ記事
- Oracle Cloud Hangout Cafe Season 4 #5「Kubernetesのオートスケーリング」(2021年8月4日開催)
- KubernetesのWorkloadsリソース(その1)
- Oracle Cloud Hangout Cafe Season7 #1「Kubnernetes 超入門」(2023年6月7日開催)
- Kubernetes上のコンテナをIngressでインターネットに公開するまで
- 「Kyverno Chainsaw」で宣言的なE2Eテストを実施する
- KubernetesのマニフェストをMagnumで実行する
- Kubernetesにおけるオートスケーリングの概要
- OpenShift:アプリケーションの構成と運用
- CNDT2020シリーズ:サイボウズのSREが語る分散ストレージの配置問題を解決するTopoLVMとは
- KubernetesのDiscovery&LBリソース(その2)