ドメインを考慮した柔軟なPodの配置を実現する「Balancer」

2023年12月6日(水)
長澤 翼
第2回目の今回は、ドメインを考慮した柔軟なPodの配置を実現する「Balancer」について紹介します。

はじめに

こんにちは。3-shakeのSreake事業部に所属する長澤翼(@toversus26)です。第2回目の今回は、ドメインを考慮した柔軟なPodの配置を実現する「Balancer」について紹介します。

KubernetesにおけるPodの配置の課題

Kubernetesにはノード上へのPodのスケジューリングを制御するための機能がいくつかあります。NodeSelectorAffinityとAntiAffinityTaintとTolerationPod 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-3990uneven 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の需要によっては今後開発リソースを投資するそうなので、ぜひ触ってみてフィードバックをお願いします。

株式会社スリーシェイク Sreake事業部
スマホ向けゲームのKubernetesエンジニアを経て、2023年3月に3-shakeのSreake事業部にJoin。Kubernetesやエコシステム周りを観察するのが好きです。

連載バックナンバー

仮想化/コンテナ技術解説
第4回

「Odigos」でノーコードの分散トレーシングを実現する

2024/3/14
第4回の今回は、アプリケーションのソースコードを変更せずに分散トレーシングを実現できる「Odigos」について紹介します。
仮想化/コンテナ技術解説
第3回

「Inspektor Gadget」でKubernetesクラスタをデバッグする

2024/1/24
第3回の今回は、eBPFを利用してKubernetesクラスタをデバッグする「Inspektor Gadget」について紹介します。
仮想化/コンテナ技術解説
第2回

ドメインを考慮した柔軟なPodの配置を実現する「Balancer」

2023/12/6
第2回目の今回は、ドメインを考慮した柔軟なPodの配置を実現する「Balancer」について紹介します。

Think ITメルマガ会員登録受付中

Think ITでは、技術情報が詰まったメールマガジン「Think IT Weekly」の配信サービスを提供しています。メルマガ会員登録を済ませれば、メルマガだけでなく、さまざまな限定特典を入手できるようになります。

Think ITメルマガ会員のサービス内容を見る

他にもこの記事が読まれています