😺

Kubernetes Cluster AutoscalerのCapacityBuffer API とは

に公開

1. Buffer API とは何か?

CapacityBuffer APIは、Kubernetesクラスター内に**予備容量(spare capacity)**を事前にプロビジョニングするためのカスタムリソース定義(CRD)です。この機能は autoscaling.x-k8s.io/v1alpha1 APIグループに属しており、Cluster Autoscalerのオプショナル機能として提供されています。

新しいPodがスケジュールされる際、通常はノードのスケールアップを待つ時間が発生します。この待ち時間は、クラウドプロバイダーがVMインスタンスを起動し、Kubernetesクラスターに参加させるまでの時間を含むため、数分に及ぶこともあります。CapacityBuffer APIは、この待ち時間を事実上ゼロにすることを目指しています。また、トラフィックスパイクのような突発的な負荷増加時にも、即座に対応できる余剰キャパシティを常時維持することで、アプリケーションの応答性を大幅に向上させます。

技術的なアプローチとしては、**仮想Pod(fake pods)**を生成してCluster Autoscalerのスケールアップロジックに注入します。これらの仮想Podは実際にはKubernetes APIサーバーには登録されず、Cluster Autoscalerのメモリ内でのみ存在します。仮想Podはスケジュール不可能なPodとして認識されるため、Cluster Autoscalerはこれらをスケジュールするために必要なノードを計算し、自動的にスケールアップをトリガーします。その結果、実際のワークロードPodが作成される時点では、すでに十分な容量が確保されているため、即座にスケジュールが可能になります。

2. 従来手法(バルーンPod/Deployment)の課題

Kubernetes環境において予備容量を確保する従来の手法として、バルーンPodやバルーンDeploymentを使用する方法が広く知られていました。これは、低優先度のダミーPodをクラスター内に配置し、実際のワークロードPodが必要になったときに立ち退かせるという仕組みです。しかし、この手法にはいくつかの課題がありました。

設定の複雑さと保守負担

バルーンPodを使用する場合、ワークロードのリソース要求に正確に合わせてバルーンのサイズを設定する必要があります。例えば、あるアプリケーションのPodがCPU 8コア、メモリ16GiBを要求する場合、バルーンPodも同じリソース量を要求するように設定しなければなりません。アプリケーションの要件が変更されるたびに、バルーンPodの定義も手動で更新する必要があり、これは運用チームにとって継続的な保守負担となります。

# 従来手法の例。ワークロードごとにバルーンDeploymentを手動作成・管理
apiVersion: apps/v1
kind: Deployment
metadata:
  name: balloon-for-my-app  # アプリごとに個別定義が必要
spec:
  replicas: 5  # ワークロードの規模変更のたびに手動更新が必要
  template:
    spec:
      containers:
      - name: pause
        image: registry.k8s.io/pause:3.9
        resources:
          requests:
            cpu: "8"
            memory: "16Gi"  # ワークロードに合わせて手動調整が必須
      priorityClassName: balloon  # 低優先度で実Pod登場時に退避

さらに深刻な問題は、ノードプールごとに異なるマシンサイズに対応する必要があることです。例えば、あるノードプールがCPU 4コアのマシンを使用し、別のノードプールがCPU 8コアのマシンを使用している場合、それぞれに適したサイズのバルーンPodを用意する必要があります。GKE Custom Compute Classes(CCC)やKarpenterのような可変サイズノード環境では、同一ノードプール内でも異なるサイズのマシンが混在する可能性があるため、バルーンPodの管理はさらに複雑化します。

スケジューラーへの負荷とパフォーマンス問題

バルーンPod手法の最も大きな課題は、Kubernetesスケジューラーへの負荷です。実際のワークロードPodをスケジュールする際、スケジューラーはまずバルーンPodを立ち退かせる必要があります。この**preemption(立ち退き)**処理は、候補となるバルーンPodを特定し、立ち退きの影響を評価し、実際に削除するという複数のステップを含みます。

バルーンPodの数が数十個程度であれば大きな問題にはなりませんが、大規模なクラスターで数百個のバルーンPodが存在する場合、スケジューリングのレイテンシーが著しく増加します。この問題を回避するため、一部のユーザーは「1ノードにつき1つのバルーンPod」という運用方針を採用していました。この方法では、各ノードの全容量をほぼ占有する大きなバルーンPodを1つだけ配置することで、preemption処理の回数を減らすことができます。しかし、この方法も結局はノードサイズに合わせてバルーンPodのサイズを調整する必要があり、管理の煩雑さは解消されません。

動的な調整の困難さ

多くのユーザーは、「Deploymentのレプリカ数の10%分のバッファーを常に維持したい」という要件を持っています。しかし、バルーンDeployment手法では、この要件を実現するために、元のDeploymentがスケールするたびにバルーンDeploymentのレプリカ数も手動で計算して更新する必要があります。Horizontal Pod Autoscaler(HPA)によって自動的にスケールするDeploymentに対しては、この手動調整は事実上不可能です。

また、時間帯による動的な調整もできません。例えば、テスト環境では営業時間中のみバッファーを有効にして、夜間や週末はコスト削減のために無効にしたいという要望は一般的ですが、バルーンDeploymentではこのような柔軟な制御ができません。CronJobを使用してバルーンDeploymentをスケジュール管理することは技術的には可能ですが、複雑な運用スクリプトが必要になり、エラーが発生しやすくなります。

さらに、クラウドプロバイダーが提供する高度な最適化機能を活用できないという問題もあります。AWSのWarm Poolsは停止状態のEC2インスタンスをプールとして保持し、必要時に数秒で起動できる機能です。Azureも同様のStandby Pools機能を提供しています。これらの機能は、完全に起動したノードを維持するよりもコストが大幅に低く、起動時間も短縮できますが、バルーンPod手法ではこれらを活用する方法がありません。

3. CapacityBuffer による改善

CapacityBuffer APIは、従来のバルーンPod手法の課題を根本的に解決するように設計されています。

宣言的で簡潔な設定と自動追従

CapacityBufferの最大の利点は、設定が極めて宣言的で簡潔であることです。ユーザーは既存のDeploymentを参照し、パーセンテージを指定するだけで、Cluster Autoscalerが自動的に適切なバッファーを維持します。

# CapacityBufferによる改善後の設定例
# Deploymentサイズの10%を自動的にバッファー化
apiVersion: autoscaling.x-k8s.io/v1alpha1
kind: CapacityBuffer
metadata:
  name: my-app-buffer
  namespace: production
spec:
  scalableRef:
    apiGroup: apps
    kind: Deployment
    name: my-app  # 既存Deploymentを参照するだけ
  percentage: 10  # 常に10%分のバッファーを自動維持
  replicas: 1     # 最低保証レプリカ数(Deploymentが小規模でも最低1つは確保)

この設定では、参照しているDeploymentが10個のレプリカを持つ場合、1個分のバッファーが自動的に作成されます。HPAによってDeploymentが50個にスケールアップした場合、バッファーも自動的に5個分に増加します。ユーザーは何も設定を変更する必要がありません。

さらに重要なのは、ワークロードのPod仕様を自動検出する機能です。Buffer Controllerは、ScalableRefで指定されたDeploymentが管理するPodを実際に検索し、そのPod仕様を自動的に抽出します。これにより、手動でリソース要求量を指定する必要がなくなり、アプリケーションの要件が変更されても、CapacityBufferの定義を更新する必要はありません。Buffer Controllerが自動的に最新のPod仕様を検出し、適切なバッファーを維持します。

スケジューラーへの負荷完全排除

CapacityBufferが生成する仮想Podは、メモリ内のみに存在し、Kubernetes APIサーバーには一切登録されません。これは技術的に非常に重要な設計上の決定です。仮想Podは、Cluster Autoscalerの内部処理であるPod List Processorによって、スケールアップの判断を行う直前に動的に生成され、unschedulable podsリストに追加されます。

この仕組みにより、Kubernetesスケジューラーは仮想Podの存在を一切認識しません。したがって、preemption処理は全く発生せず、実際のワークロードPodをスケジュールする際のレイテンシーに影響を与えません。数百個のバッファーを定義しても、スケジューラーのパフォーマンスは全く低下しないのです。

また、仮想PodにはCapacityBuffer固有のアノテーション(podType: capacityBufferFakePod)が付与されており、Scale Up Status Processorがスケールアップ成功後にこれらをフィルタリングします。これにより、ユーザーに対して誤解を招くイベント通知が発生することもありません。

拡張性と柔軠性を考慮した設計

CapacityBuffer APIは、将来的な拡張を見据えた設計になっています。現在の実装(v1alpha1)では基本的なアクティブ容量バッファーのみをサポートしていますが、提案書では今後追加される可能性のある機能が明確に示されています。

時間帯指定のバッファー有効化は、コスト最適化の観点から強く要望されている機能です。例えば、テスト環境では営業時間中(例えば平日9時〜18時)のみバッファーを有効にし、それ以外の時間帯は無効にすることで、余分なコストを削減できます。この機能は、Specに schedule フィールドを追加することで実装される可能性があります。

クラウドプロバイダー固有の戦略については、provisioningStrategy フィールドが既に用意されています。現在は "buffer.x-k8s.io/active-capacity" のみがサポートされていますが、将来的には "buffer.aws.x-k8s.io/warm-pool""buffer.azure.x-k8s.io/standby-pool" といった値をサポートすることで、各クラウドプロバイダーの最適化機能を活用できるようになります。これらの戦略では、完全に起動したノードを維持するのではなく、停止状態のVMプールを保持することで、コストを大幅に削減しながら迅速な起動を実現します。

特定ワークロード専用バッファーも検討されている機能です。これはPodSelectorを使用して、特定のラベルを持つPodのみがバッファー容量を使用できるようにする仕組みです。例えば、高優先度のPod用に専用のバッファーを確保し、通常のPodは通常のスケールアップフローを使用する、といった運用が可能になります。この機能により、重要なワークロードに対してより確実な容量保証を提供できます。

ただし、これらの拡張機能は初期実装のスコープ外であり、提案書では「Out of scope, may be added as follow up proposals」として明記されています。コミュニティからのフィードバックや実際の利用状況を踏まえて、今後段階的に実装される予定です。

4. API の定義と仕様

CapacityBuffer CRDは、Specで要件を宣言し、Statusで実際の状態を確認できる、Kubernetesの標準的なリソース設計に従っています。

CRDの基本構造と詳細解説

apiVersion: autoscaling.x-k8s.io/v1alpha1
kind: CapacityBuffer
metadata:
  name: my-buffer
  namespace: default
spec:
  # バッファーの形状を定義する2つの方法が用意されています
  # これらは排他的 で、どちらか一方のみを指定する必要があります

  # 方法1: PodTemplateを直接参照する方式
  # この方法は、特定のリソース形状を直接定義したい場合に使用します
  podTemplateRef:
    name: my-pod-template

  # 方法2: スケーラブルなオブジェクト(Deployment、StatefulSet等)を参照する方式
  # この方法は、既存のワークロードに追従するバッファーを作成する場合に使用します
  scalableRef:
    apiGroup: apps
    kind: Deployment
    name: my-deployment

  # レプリカ数の指定には複数の方法があります
  replicas: 5              # 固定数を指定(この場合は常に5個のバッファーを維持)
  percentage: 10           # scalableRefの現在のレプリカ数の10%分を維持
                           # replicasとpercentageの両方が指定された場合、大きい方が採用されます

  # リソース制限によるバッファー上限の設定
  # この設定により、バッファーが消費できる総リソース量を制限できます
  limits:
    cpu: "40"
    memory: "5120Mi"

  # プロビジョニング戦略の指定
  # 現在はactive-capacityのみサポートされていますが、
  # 将来的にwarm-poolなどの戦略が追加される予定です
  provisioningStrategy: "buffer.x-k8s.io/active-capacity"

status:
  # Buffer ControllerがSpecを解析した結果として生成されたPodTemplateへの参照
  # これはBuffer Controllerが自動的に作成または検出したPodTemplateの名前です
  podTemplateRef:
    name: generated-template

  # 実際に作成されるバッファーチャンク(仮想Pod)の数
  # この値はSpecのreplicas、percentage、limitsを考慮して計算されます
  replicas: 5

  # 使用されているPodTemplateの世代番号
  # この値が変更されると、Buffer Controllerは新しいPod仕様でバッファーを再生成します
  podTemplateGeneration: 123

  # バッファーの状態を示すConditions
  # Buffer ControllerとPod List Processorがそれぞれ独立してConditionsを更新します
  # 実装上、Conditions配列は上書きされるため、通常は1つのConditionのみが存在します
  conditions:
    # ReadyForProvisioning: Buffer Controllerによって設定される
    # または
    # Provisioning: Pod List Processorによって設定される
    - type: ReadyForProvisioning  # または Provisioning
      status: "True"
      reason: "atrtibutesSetSuccessfully"  # 実装のまま(スペルミス含む)
      message: "ready"
      lastTransitionTime: "2025-11-22T10:00:00Z"

  # 実際に使用されているプロビジョニング戦略
  # Specで指定されていない場合はデフォルト値が設定されます
  provisioningStrategy: "buffer.x-k8s.io/active-capacity"

Specフィールドの詳細な動作

CapacityBuffer Specには、バッファーの形状を定義する2つの異なるアプローチがあります。

PodTemplateRef方式では、ユーザーが事前にPodTemplateリソースを作成し、それを参照します。この方式は、特定のリソース形状を直接制御したい場合に適しています。例えば、特定のnodeSelectorやtolerationsを持つバッファーを作成したい場合、PodTemplateに直接それらを記述できます。

ScalableRef方式は、より動的で保守性の高いアプローチです。この方式では、Deployment、StatefulSet、ReplicaSet、Job、ReplicationControllerなど、scaleサブリソースを持つ任意のKubernetesリソースを参照できます。Buffer Controllerは、参照されたリソースのlabel selectorを使用して、実際に稼働しているPodを検索し、最も新しいPodの仕様を抽出します。この仕組みにより、ワークロードの変更に自動的に追従するバッファーを実現できます。

レプリカ数の決定には優先順位があります。replicaspercentageの両方が指定された場合、大きい方の値が採用されます。これは最低保証の概念を実現するためです。例えば、replicas: 5percentage: 10を同時に指定し、参照しているDeploymentが30個のレプリカを持つ場合、10%の3個ではなく、より大きい5個のバッファーが作成されます。

limitsフィールドは、バッファーが消費できる総リソース量を制限します。例えば、cpu: "40"と指定した場合、各バッファーチャンクがCPU 8コアを要求していれば、最大5個のチャンクが作成されます。これにより、バッファーが過度に大きくなることを防ぎ、コストを制御できます。

Statusフィールドとライフサイクル

StatusフィールドはBuffer Controllerによって管理され、バッファーの現在の状態を反映します。

podTemplateRefは、実際に使用されているPodTemplateへの参照です。ScalableRef方式の場合、Buffer Controllerが自動生成したPodTemplateの名前がここに記録されます。このPodTemplateは実際にKubernetes APIに作成され、ユーザーも確認できます。

replicasは、最終的に決定されたバッファーチャンクの数です。この値は、Specのreplicas、percentage、limitsのすべてを考慮して計算されます。例えば、percentage: 20limits.cpu: "40"が指定され、参照しているDeploymentが100レプリカでCPU 2コアずつ要求している場合、20%の20個を作成したいところですが、limits制約により最大20個(40コア ÷ 2コア)までしか作成できないため、結果として20個が設定されます。

podTemplateGenerationは、PodTemplateの変更を検知するために使用される世代番号です。Buffer ControllerはPodTemplateGenerationChangedFilterを使用して、参照しているPodTemplateが更新されたかを検出し、必要に応じてバッファーを再生成します。

Conditionsは、バッファーのライフサイクルを追跡するために使用されます。重要な点として、Buffer ControllerとPod List Processorはそれぞれ独立してConditionsを更新するため、実装上はConditions配列が完全に置き換えられます。

ReadyForProvisioning条件は、Buffer ControllerがSpecを正常に解析し、Statusフィールドを適切に設定できたことを示します。この条件がFalseの場合、messageフィールドにエラーの詳細が記録されます。実装では、成功時のReasonは"atrtibutesSetSuccessfully"(attributesのスペルミスがそのまま)、失敗時のReasonは"error"となります。

Provisioning条件は、Cluster AutoscalerのPod List Processorがバッファーを実際に処理し、仮想Podを注入したことを示します。成功時のReasonは"FakePodsInjected"、失敗時は"FailedToGetPodTemplate"または"FailedToMakeFakePods"などのエラー固有のReasonが設定されます。この条件により、バッファーが実際に機能しているかを確認できます。

実際の運用では、Buffer Controllerが先にReadyForProvisioning条件を設定し、その後Pod List ProcessorがProvisioning条件に更新する流れになります。ただし、現在の実装ではConditions配列が完全に置き換えられるため、両方の条件が同時に表示されるかは実装の詳細に依存します。

5. 具体的な使い方

CapacityBuffer APIは、ユーザーの要件に応じて2つの異なるアプローチで使用できます。既存のワークロードに追従する動的なバッファーが必要な場合はScalableRef方式を、特定のリソース形状を固定的に確保したい場合はPodTemplateRef方式を選択します。

使用例1 - Deploymentベースのバッファー

この例では、既存のDeploymentを参照し、そのサイズの10%分を常にバッファーとして維持する設定を示しています。

apiVersion: autoscaling.x-k8s.io/v1alpha1
kind: CapacityBuffer
metadata:
  name: my-deployment-buffer
  namespace: my-namespace
spec:
  scalableRef:
    apiGroup: apps
    kind: Deployment
    name: my-deployment
  percentage: 10  # Deploymentサイズの10%分のバッファー
  replicas: 1     # 最低1レプリカは確保

この設定により、参照しているDeploymentが10個のレプリカを持つ場合、10%の1個分のバッファーが自動的に作成されます。Horizontal Pod Autoscaler(HPA)によってDeploymentが50個にスケールアップした場合、バッファーも自動的に5個分に増加します。ユーザーは何も設定を変更する必要がありません。

replicas: 1という最低保証の設定により、Deploymentのサイズが非常に小さい場合でも最低1個のバッファーが確保されます。例えば、Deploymentが5個のレプリカしか持たない場合、10%は0.5個となりますが、切り上げと最低保証の両方により1個のバッファーが維持されます。

Buffer Controllerは、参照されたDeploymentのlabel selectorを使用して実際に稼働しているPodを検索し、最も新しいPodの仕様を自動的に抽出します。これにより、Deploymentのコンテナイメージやリソース要求が変更された場合でも、バッファーは自動的に新しい仕様に追従します。

使用例2 - カスタムPodTemplateベースのバッファー

この例では、特定のリソース形状を直接定義し、リソース制限に基づいてバッファー数を制御する方法を示しています。

apiVersion: v1
kind: PodTemplate
metadata:
  name: my-custom-template
  namespace: my-namespace
template:
  spec:
    containers:
    - name: cpu-buffer-container
      image: registry.k8s.io/pause:3.9
      resources:
        requests:
          cpu: "8"
          memory: "16Gi"
        limits:
          cpu: "8"
          memory: "16Gi"
    nodeSelector:
      cloud.google.com/compute-class: my-custom-class
    tolerations:
    - key: "workload-type"
      operator: "Equal"
      value: "compute-intensive"
      effect: "NoSchedule"
---
apiVersion: autoscaling.x-k8s.io/v1alpha1
kind: CapacityBuffer
metadata:
  name: testing-capacity
  namespace: my-namespace
spec:
  podTemplateRef:
    name: my-custom-template
  limits:
    cpu: "40"
    memory: "5120Mi"

この設定では、CPU 8コア、メモリ16GiBを要求するPodテンプレートを使用しています。limitsフィールドにより、バッファーが消費できる総リソース量をCPU 40コア、メモリ5120MiB(約5GiB)に制限しています。Buffer Controllerはこれらの制限に基づいて、最大5個のバッファーチャンク(40コア ÷ 8コア = 5個)を作成します。ただし、メモリ制限の観点では約0.3個(5GiB ÷ 16GiB)しか作成できないため、実際には制約の厳しい方が優先され、0個となってしまいます。このような場合は、limits設定を調整する必要があります。

PodTemplateには、nodeSelectorやtolerationsなど、特定のノードプールやノードタイプを指定する設定も含められます。この例では、GKE Custom Compute Class(CCC)の特定クラスと、compute-intensiveワークロード用のtaintを持つノードにバッファーを配置する設定を示しています。

この方式は、CI/CDパイプラインのテストジョブ用のバッファーなど、特定の用途に最適化されたバッファーを作成する場合に特に有用です。テストジョブは通常、特定のリソース形状を持ち、Deploymentとは異なる一時的な性質を持つため、PodTemplateRef方式がより適しています。

6. Cluster Autoscaler への組み込み方

CapacityBuffer機能は、Cluster Autoscalerにオプショナルな機能として統合されています。有効化するかどうかはユーザーが選択でき、既存のCluster Autoscalerの動作には影響を与えません。

アーキテクチャと責任分担

┌─────────────────────────────────────────────┐
│   Cluster Autoscaler                        │
│                                             │
│  ┌───────────────────────────────────────┐ │
│  │ Buffer Controller (サブプロセス)      │ │
│  │ ・CapacityBuffer CRDをListWatch       │ │
│  │ ・Specを解析してPodTemplateを決定     │ │
│  │ ・レプリカ数を計算                    │ │
│  │ ・Status.Conditionsを更新             │ │
│  │   - ReadyForProvisioning条件を設定    │ │
│  └───────────────────────────────────────┘ │
│              ↓ (5秒ごとのループ)            │
│  ┌───────────────────────────────────────┐ │
│  │ Pod List Processor                    │ │
│  │ ・ReadyなBufferをフィルタリング       │ │
│  │ ・Buffer.Status.Replicasに基づいて    │ │
│  │  仮想Podを動的に生成                  │ │
│  │ ・unschedulablePodsリストに注入       │ │
│  │ ・Status.Conditionsを更新             │ │
│  │   - Provisioning条件を設定            │ │
│  └───────────────────────────────────────┘ │
│              ↓ (各スケールアップサイクル)    │
│  ┌───────────────────────────────────────┐ │
│  │ Scale Up Logic                        │ │
│  │ ・仮想Podを含むunschedulableリストから│ │
│  │  必要なノードタイプと数を計算         │ │
│  │ ・クラウドプロバイダーAPIを呼び出して │ │
│  │  ノードグループをスケールアップ       │ │
│  └───────────────────────────────────────┘ │
│              ↓                              │
│  ┌───────────────────────────────────────┐ │
│  │ Scale Up Status Processor             │ │
│  │ ・仮想Podをアノテーションで識別       │ │
│  │ ・仮想Podはイベント通知から除外       │ │
│  │ ・実際のワークロードPodのみ報告       │ │
│  └───────────────────────────────────────┘ │
└─────────────────────────────────────────────┘

この設計により、バッファーの設定解釈とプロビジョニングの責任が明確に分離されています。Buffer Controllerは「何をプロビジョニングすべきか」を決定し、Cluster Autoscalerは「どのようにプロビジョニングするか」を実行します。

有効化方法

Cluster Autoscalerの起動時に以下のフラグを指定することで、CapacityBuffer機能を有効化できます。

./cluster-autoscaler \\
  --capacity-buffer-controller-enabled=true \\
  --capacity-buffer-pod-injection-enabled=true \\
  --kubeconfig=/path/to/kubeconfig \\
  --cloud-provider=gce \\
  ...

2つのフラグは独立して制御できますが、通常は両方を有効にします。capacity-buffer-controller-enabledはBuffer Controllerサブプロセスを起動し、capacity-buffer-pod-injection-enabledはPod List Processorによる仮想Pod注入を有効にします。理論的には、Buffer Controllerのみを有効にして外部から監視する用途や、既に別のBuffer Controllerが動作している環境でPod注入のみを有効にする用途も考えられますが、一般的なユースケースでは両方を有効にします。

内部処理フローの詳細

CapacityBuffer機能の処理は、複数のコンポーネントが連携して動作します。

Buffer Controller の動作(5秒ごとのループ)

Buffer Controllerは、Cluster Autoscalerのメインプロセスとは独立したgoroutineとして5秒ごとに実行されます。各イテレーションで以下の処理を実行します。

まず、Kubernetes APIサーバーからすべてのCapacityBufferリソースを取得します。このリストに対して複数のフィルターを適用し、処理対象のBufferを絞り込みます。StrategyFilterは、サポートされているプロビジョニング戦略(現在はbuffer.x-k8s.io/active-capacityのみ)を持つBufferのみを通過させます。StatusFilterとPodTemplateGenerationChangedFilterは、既に正しく処理済みのBufferを除外し、新規または変更されたBufferのみを再処理します。

フィルターを通過したBufferに対して、Translatorチェーンが実行されます。PodTemplateTranslatorはpodTemplateRefが指定されている場合に、参照されたPodTemplateを取得します。ScalableObjectsTranslatorはscalableRefが指定されている場合に、参照されたDeploymentなどのリソースからPod仕様を抽出し、必要に応じて新しいPodTemplateを作成します。ResourceLimitsTranslatorは、Specのreplicaspercentagelimitsフィールドを評価し、最終的なレプリカ数を計算します。

これらの処理結果は、Status UpdaterによってCapacityBufferリソースのStatusフィールドに書き込まれます。podTemplateRefreplicaspodTemplateGenerationprovisioningStrategyが設定され、ConditionsにReadyForProvisioning: Trueが追加されます。何らかのエラーが発生した場合は、ReadyForProvisioning: Falseとエラーメッセージが記録されます。

Pod List Processorの動作(各スケールアップサイクル)

Pod List Processorは、Cluster Autoscalerがスケールアップの必要性を判断する際に毎回実行されます。通常、これは10秒ごとのメインループで発生します。

まず、すべてのCapacityBufferリソースを取得し、StatusFilterを適用してReadyForProvisioning: TrueかつProvisioning: Trueの両方の条件を満たすBufferのみを選択します。ただし、実装上の制約により、実際にはこれらの条件が両方同時に存在しない可能性があるため、実際のフィルター動作は実装の詳細に依存します。

選択されたBufferごとに、Status.Replicasフィールドで指定された数の仮想Podを生成します。各仮想Podには、参照されているPodTemplateの仕様がコピーされ、一意のUID(BufferのUID + 連番)と名前(capacity-buffer-<buffer-name>-<番号>)が付与されます。また、podType: capacityBufferFakePodというアノテーションが追加され、後続の処理で仮想Podを識別できるようにします。

生成された仮想Podは、既存のunschedulable podsリストに追加されます。このリストはCluster Autoscalerのスケールアップロジックに渡され、通常のunschedulable podと同様に扱われます。

仮想Podの生成に成功した場合、Pod List ProcessorはProvisioning: True条件をBufferのStatusに設定します。PodTemplateの取得や仮想Podの生成に失敗した場合は、Provisioning: Falseと具体的なエラー理由(FailedToGetPodTemplateまたはFailedToMakeFakePods)が記録されます。

Scale Up Logicの動作

Scale Up Logicは、仮想Podと実際のunschedulable podを区別せずに処理します。各Podのリソース要求、nodeSelector、tolerations、affinityなどを考慮して、どのノードグループにどれだけのノードを追加すべきかを計算します。

仮想Podが特定のnodeSelectorやtolerationsを持つ場合、それに対応するノードグループのみがスケールアップ候補となります。これにより、バッファーを特定のノードタイプ(例えば、GPUノードやspot instance)に配置することができます。

計算結果に基づいて、クラウドプロバイダーのAPIを呼び出してノードグループをスケールアップします。この時点で、仮想Podのために確保された容量は、実際のノードとして物理的に存在することになります。

Scale Up Status Processorの動作

スケールアップが成功した後、Scale Up Status Processorが実行されます。このプロセッサーは、スケールアップされたPodのリストから仮想Podをフィルタリングし、実際のワークロードPodのみをユーザーに報告します。

仮想PodはpodType: capacityBufferFakePodアノテーションによって識別されます。これらの仮想PodについてはKubernetesイベントを発行せず、ログにも記録しません。これにより、ユーザーは実際のワークロードのスケールアップのみを認識し、内部的なバッファー管理の詳細を意識する必要がありません。

7. 実装の詳細

CapacityBuffer機能の実装は、モジュール化された設計により、各コンポーネントが独立してテスト可能な構造になっています。

主要コンポーネントの構成

cluster-autoscaler/
├── apis/capacitybuffer/
│   └── autoscaling.x-k8s.io/v1alpha1/
│       ├── types.go                    # CRD型定義(CapacityBuffer, Spec, Status)
│       ├── register.go                  # APIグループ登録
│       └── zz_generated.deepcopy.go    # 自動生成されたDeepCopy実装
├── capacitybuffer/
│   ├── client/
│   │   ├── client.go                   # CapacityBufferClientの実装
│   │   └── client_test.go              # クライアントのユニットテスト
│   ├── controller/
│   │   └── controller.go               # BufferControllerのメインループ
│   ├── filters/
│   │   ├── filter.go                   # フィルターインターフェース
│   │   ├── status_filter.go            # Condition-basedフィルター
│   │   ├── strategy_filter.go          # プロビジョニング戦略フィルター
│   │   └── buffer_generation_filter.go # 世代変更検知フィルター
│   ├── translators/
│   │   ├── translator.go               # Translatorインターフェース
│   │   ├── pod_template_translator.go  # PodTemplateRef処理
│   │   ├── scalable_objects_translator.go # ScalableRef処理
│   │   └── resource_limits_translator.go  # Limits計算
│   ├── updater/
│   │   └── status_updater.go           # Status更新ロジック
│   ├── common/
│   │   └── common.go                   # 共通定数とヘルパー関数
│   └── testutil/
│       └── testutil.go                 # テスト用のヘルパー関数
└── processors/capacitybuffer/
    ├── pod_list_processor.go           # 仮想Pod注入プロセッサー
    ├── pod_list_processor_test.go      # 注入ロジックのテスト
    ├── scale_up_status_processor.go    # スケールアップ後処理
    └── scale_up_status_processor_test.go # 後処理のテスト

主要な定数と設定値

実装では、以下の定数が定義されています。

const (
    // プロビジョニング戦略
    ActiveProvisioningStrategy = "buffer.x-k8s.io/active-capacity"

    // Condition Types
    ReadyForProvisioningCondition = "ReadyForProvisioning"
    ProvisioningCondition = "Provisioning"

    // Condition Status値
    ConditionTrue = "True"
    ConditionFalse = "False"

    // Buffer Controllerのループ設定
    defaultLoopInterval = 5 * time.Second
    defaultIterationsToReprocessAll = 60  // 5分ごとに全Bufferを再評価

    // 仮想Podのアノテーション
    CapacityBufferFakePodAnnotationKey = "podType"
    CapacityBufferFakePodAnnotationValue = "capacityBufferFakePod"
)

defaultIterationsToReprocessAll設定により、Buffer Controllerは60回のイテレーション(約5分)ごとに、すべてのBufferを強制的に再評価します。これにより、一時的なエラーで処理がスキップされたBufferや、外部からStatusが直接変更されたBufferを自動的に復旧できます。

Translatorの役割と優先順位

Translatorは、CapacityBuffer Specを解析し、最終的なPodTemplateとレプリカ数を決定する責任を持ちます。複数のTranslatorがチェーン状に実行され、各Translatorは前のTranslatorの結果を引き継ぎます。

PodTemplateTranslatorは、spec.podTemplateRefが指定されている場合に実行されます。参照されたPodTemplateリソースをKubernetes APIから取得し、その名前とgeneration番号をStatusに記録します。PodTemplateが見つからない場合や、アクセス権限がない場合は、エラーConditionを設定してBufferを無効化します。

ScalableObjectsTranslatorは、spec.scalableRefが指定されている場合に実行されます。参照されたリソース(Deployment、StatefulSet、ReplicaSet、Job、ReplicationController)のscaleサブリソースを取得し、そのlabel selectorを使用してPodを検索します。最も新しいcreationTimestampを持つPodの仕様を抽出し、新しいPodTemplateリソースを自動生成します。このPodTemplateには、capacity-buffer-<buffer-name>-templateという名前が付けられ、Buffer Controllerによって管理されます。

spec.percentageが指定されている場合、ScalableObjectsTranslatorはscaleサブリソースの現在のreplicasフィールドを取得し、パーセンテージを適用してバッファーのレプリカ数を計算します。計算結果は切り上げられ、最低1個が保証されます。

ResourceLimitsTranslatorは、spec.limitsが指定されている場合に実行されます。PodTemplateで定義されたリソース要求量と、limitsで指定された総リソース量を比較し、作成可能な最大チャンク数を計算します。CPU、メモリ、その他のリソースタイプごとに制限をチェックし、最も制約の厳しいリソースに基づいて最大数を決定します。

spec.replicaspercentage計算結果、limits計算結果の3つの値がすべて存在する場合、最大の値が最終的なレプリカ数として採用されます。これにより、「最低N個は確保しつつ、パーセンテージにも追従する」という要件を実現できます。

8. 現在の実装状況と今後の展望

CapacityBuffer APIは、基本的な機能が実装完了しており、実用段階に達しています。ただし、APIバージョンがv1alpha1であることが示すように、まだ進化中の機能です。

実装済みの機能

CRD定義は完全に実装されており、autoscaling.x-k8s.io/v1alpha1APIグループでCapacityBufferリソースを作成・管理できます。Kubebuilderのvalidation rulesにより、Specフィールドの整合性がAPIサーバーレベルで検証されます。例えば、podTemplateRefscalableRefの両方を同時に指定することはできず、podTemplateRefを指定する場合はreplicasまたはlimitsのいずれかも必須です。

Buffer Controllerは、Cluster Autoscalerのサブプロセスとして安定して動作します。5秒ごとのループで継続的にBufferを監視し、変更を検知して自動的にStatusを更新します。エラー処理も適切に実装されており、一時的なAPIサーバーの障害やPodTemplateの削除などの異常状態から自動的に復旧します。

Pod List Processorによる仮想Pod注入は、メモリ効率的に実装されています。仮想PodはKubernetes APIに登録されないため、大量のBufferを定義しても、etcdのストレージやKubernetes APIサーバーの負荷には影響しません。スケジューラーも仮想Podを認識しないため、スケジューリングパフォーマンスへの影響もありません。

Scale Up Status Processorは、仮想Podを適切にフィルタリングし、ユーザーに対して誤解を招くイベント通知を防ぎます。Cluster Autoscalerのログにも、実際のワークロードPodのスケールアップのみが記録されます。

各種フィルター機能(Status、Strategy、Generation変更検知)は、不要な再処理を防ぎ、Buffer Controllerのパフォーマンスを最適化します。Translatorの実装も、PodTemplate、ScalableObjects、ResourceLimitsの3つすべてが完成しており、多様なユースケースをカバーしています。

ユニットテストは包括的に実装されており、各コンポーネントが独立してテスト可能です。テストカバレッジは高く、エッジケースやエラー処理も十分にテストされています。

コマンドラインフラグによる有効化機能により、既存のCluster Autoscalerユーザーは段階的にCapacityBuffer機能を導入できます。デフォルトでは無効化されているため、既存の動作に影響を与えることなく、準備ができたタイミングで有効化できます。

現時点での制約と注意点

APIバージョンがv1alpha1であることは、APIが将来変更される可能性があることを意味します。破壊的変更が導入される可能性もあるため、本番環境で使用する場合は、アップグレード時に設定の見直しが必要になる可能性を考慮する必要があります。ただし、Kubernetesのalphaからbetaへの移行プロセスでは、通常、移行パスが提供されるため、完全な互換性喪失は稀です。

デフォルトでは無効化されているため、使用するには明示的にフラグを設定する必要があります。これは安全性の観点からは良い設計ですが、初めて使用するユーザーは有効化手順を理解する必要があります。

現在サポートされているプロビジョニング戦略はbuffer.x-k8s.io/active-capacityのみです。この戦略では、バッファーのために完全に起動したノードが常時維持されるため、クラウドプロバイダーのコストが継続的に発生します。将来的に実装予定のWarm Pools戦略では、停止状態のVMを維持することでコストを削減できますが、現時点では利用できません。

Resource Quotaとの統合は未実装です。NamespaceにResource Quotaが設定されている場合でも、CapacityBufferはそれを考慮せずにバッファーを作成しようとします。これにより、意図しない大きなバッファーが作成される可能性があります。現時点では、ノードプール/ノードグループレベルの最大サイズ設定や、クラウドプロバイダーのクォータ設定を使用してクラスターの上限を制御する必要があります。

今後の拡張予定

提案書では、初期実装のスコープ外として複数の拡張機能が言及されています。

スケジュールベースのバッファーは、時間帯によってバッファーの有効/無効を切り替える機能です。例えば、テスト環境では営業時間中(平日9時〜18時)のみバッファーを有効にし、夜間や週末は無効にすることで、コストを大幅に削減できます。実装方法としては、Specにscheduleフィールドを追加し、cron式や時間範囲で有効期間を指定する形が考えられます。Buffer Controllerは、現在時刻がスケジュール範囲外の場合、Bufferを無効として扱い、仮想Podを生成しません。

特定ワークロード専用バッファーは、PodSelectorを使用して、特定のラベルを持つPodのみがバッファー容量を使用できるようにする機能です。例えば、priority: highラベルを持つPod用に専用のバッファーを確保し、通常のPodは通常のスケールアップフローを使用する、という運用が可能になります。実装方法としては、SpecにpodSelectorフィールドを追加し、実際のPodがスケジュールされたときにPodSelectorにマッチするかをチェックします。マッチするPodがバッファー容量を消費し、その分のバッファーが減少します。

クラウドプロバイダー固有の戦略は、各クラウドプロバイダーが提供する最適化機能を活用する拡張です。AWS Warm Poolsは、停止状態のEC2インスタンスをプールとして保持し、必要時に数秒で起動できる機能です。完全に起動したインスタンスよりも大幅に低コストで維持でき、起動時間も短縮できます。Azureも同様のStandby Pools機能を提供しています。これらを活用するには、provisioningStrategyフィールドにbuffer.aws.x-k8s.io/warm-poolbuffer.azure.x-k8s.io/standby-poolといった値をサポートする必要があります。クラウドプロバイダー固有のコードは、各cloudproviderパッケージ内に実装され、Cluster Autoscalerの既存のクラウドプロバイダー抽象化レイヤーを活用します。

Resource Quotaとの統合は、Namespace単位でバッファーの総リソース量を制限する機能です。現在、Kubernetes Resource Quotaは実際のPodのみをカウントし、バッファーは考慮しません。これにより、ユーザーがQuotaを超える大きなバッファーを作成できてしまう問題があります。Buffer ControllerをQuota-awareにすることで、バッファーのリソース要求量もQuotaにカウントし、Quotaを超える場合はバッファーサイズを自動的に縮小する、という動作が可能になります。ただし、これには複雑な考慮事項があります。例えば、実際のワークロードPodがスケールアップしてQuotaを消費した場合、バッファーを自動的に縮小すべきか、といった問題です。

まとめ

Buffer APIは実装が完了しており、Cluster Autoscalerにフラグで有効化することで即座に使用可能な状態です。従来のバルーンPod/Deployment方式と比較して、設定の簡潔性、スケジューラーへのオーバーヘッドの少なさ、保守性の高さという明確な利点があります。

本番環境で使用する場合は、v1alpha1というAPIバージョンが示すように、まだ進化中の機能であることを認識する必要があります。以下の点を考慮して導入することを推奨します。

まず、小規模な環境やdev/staging環境で十分にテストを行い、期待通りの動作を確認してください。特に、参照しているDeploymentやPodTemplateが変更された場合の自動追従動作、エラー時の復旧動作、リソース制限の計算ロジックなどを検証することが重要です。

コストへの影響を事前に評価してください。バッファーは常時起動したノードを維持するため、クラウドプロバイダーのコストが継続的に発生します。limitsフィールドを適切に設定し、バッファーが過度に大きくなることを防ぐことが重要です。また、本当にバッファーが必要なワークロードのみに適用し、すべてのDeploymentに無差別に適用することは避けるべきです。

モニタリングとアラートを設定してください。CapacityBufferリソースのStatus Conditionsを監視し、ReadyForProvisioning: FalseProvisioning: Falseの状態が継続する場合はアラートを発行するようにします。また、実際にバッファーがどれだけのリソースを消費しているかをメトリクスとして収集し、コスト最適化の判断材料にします。

Cluster Autoscalerのバージョンアップ時には、CapacityBuffer APIの変更点をリリースノートで確認してください。破壊的変更が導入される場合は、事前に設定の移行を計画する必要があります。

これらの点に注意しながら使用すれば、CapacityBuffer APIはPodの起動時間を劇的に短縮し、ユーザー体験を大幅に向上させる強力なツールとなります。

Discussion