Kubernetes Vertical Pod Autoscalerの応答性を最適化する

English version here.

先月社内プレゼンがあり、そこでVertical Pod Autoscalerのデモンストレーションを行いました。 5~10分程度のプレゼンでVertical Pod Autoscalerの動作を見せようとしましたが、デフォルト設定のVertical Pod Autoscalerは1日程度の周期で動作するよう設計されており、数分で効果を見せるにはコード改造まで含めた最適化が必要でした。

本稿ではVPAがどのように動作するか説明し、コード改造を含めたパフォーマンス最適化の方法を説明します。

Vertical Pod Autoscalerとは

Vertical Pod Autoscalerリソース要求を動的に定義する機能を提供します。 PodのCPUとメモリ使用量を測定し、リソース要求を実行時に更新します。

Vertical Pod Autoscalerの振る舞い

VPAは以下のコンポーネントで構成されます。

admission-controlleradmission webhookを登録します。

recommenderはPodのリソース使用量を確認し、リソース要求の推奨値を見積り、Podに紐づけたVPAオブジェクトを更新します。 この処理は定期的に実行されます。

updaterはVPAオブジェクトに登録されたリソース要求の推奨値を確認し、Podの現在のリソース要求が推奨値と一致しなければ、Podを削除します。

DeploymentやReplicaSetのPodがupdaterによって削除されると、Replica set controllerがPodの不足を検出し、新しいPodの作成を試みます。 Pod作成のリクエストがapi-serverに送信され、api-serverはadmission-controllerが登録したWebHookを実行します。 admission-controllerはリソース要求の推奨値にもとづいてPod作成リクエストを改変します。 このようにして新しいPodは適切なリソース要求で作成されます。

定期タスクの実行周期を変更する

admission-controllerはPod作成リクエストが発生したとき同期的に動作するため、本稿では無視できます。 admission-controllerの動作はVPA全体の応答性に影響がありません。

recommenderupdaterは周期的に動作し、この実行頻度がVPA全体の応答性に直結するため、応答性を改善するにはこの設定変更が必要です。 これらの動作周期は--recommender-interval--updater-intervalで変更できます。

recommender-deployment.yamlupdater-deployment.yamlを以下のように編集しましょう。

Patch license: Apache-2.0 (same as original autoscaler)
https://github.com/kubernetes/autoscaler

diff --git a/vertical-pod-autoscaler/deploy/recommender-deployment.yaml b/vertical-pod-autoscaler/deploy/recommender-deployment.yaml
index f45d87127..739c35da6 100644
--- a/vertical-pod-autoscaler/deploy/recommender-deployment.yaml
+++ b/vertical-pod-autoscaler/deploy/recommender-deployment.yaml
@@ -38,3 +38,9 @@ spec:
         ports:
         - name: prometheus
           containerPort: 8942
+        command:
+        - /recommender
+        - --v=4
+        - --stderrthreshold=info
+        - --prometheus-address=http://prometheus.monitoring.svc
+        - --recommender-interval=10s
diff --git a/vertical-pod-autoscaler/deploy/updater-deployment.yaml b/vertical-pod-autoscaler/deploy/updater-deployment.yaml
index a97478a8e..b367f1a04 100644
--- a/vertical-pod-autoscaler/deploy/updater-deployment.yaml
+++ b/vertical-pod-autoscaler/deploy/updater-deployment.yaml
@@ -43,3 +43,7 @@ spec:
           ports:
             - name: prometheus
               containerPort: 8943
+          args:
+          - --v=4
+          - --stderrthreshold=info
+          - --updater-interval=10s

--v, --stderrthreshold, --recommender-intervalrecommender/Dockerfileupdater/Dockerfileに定義されているデフォルトの引数です。

注意: --recommender-interval=10s--updater-interval=10sは通常のユースケースでは短かすぎます。本番クラスタにこの設定値を使用しないでください。

推奨値を決定するアルゴリズム

updaterの応答性は--updater-intervalのみで制御できますが、recommenderにはもう少し変更が必要です。 recommenderの応答性を改善するには、まずそのアルゴリズムを理解しなければなりません。

recommenderはPodのリソース要求の推奨範囲の最小値と最大値を定義し、updaterは現在のPodのリソース要求がその推奨値の範囲に入っているか調べます。 問題はPodの実際のリソース使用量が高速に変化しているときにrecommenderが推奨範囲を広げるように設計されていることです。 このデザインは通常のユースケースには合理的ですが、今回のデモンストレーションに向けては不都合でした。 数分で動作を見せる必要があるので、もっと素早く応答できないと困ります。

recommenderはPodのリソース使用量の履歴を記録し、その変動が大きいときに推奨範囲を広げるように作られています。 この変動量をどのくらい考慮するか、--cpu-histogram-decay-half-lifeオプションによって制御できます。

Patch license: Apache-2.0 (same as original autoscaler)
https://github.com/kubernetes/autoscaler

diff --git a/vertical-pod-autoscaler/deploy/recommender-deployment.yaml b/vertical-pod-autoscaler/deploy/recommender-deployment.yaml
index 739c35da6..09113a02e 100644
--- a/vertical-pod-autoscaler/deploy/recommender-deployment.yaml
+++ b/vertical-pod-autoscaler/deploy/recommender-deployment.yaml
@@ -44,3 +44,4 @@ spec:
         - --stderrthreshold=info
         - --prometheus-address=http://prometheus.monitoring.svc
         - --recommender-interval=10s
+        - --cpu-histogram-decay-half-life=10s

注意: --cpu-histogram-decay-half-life=10sは通常のユースケースでは速すぎます。本番クラスタにこの設定値を使用しないでください。

もう一つrecommenderの推奨範囲を広げる要因があります。 recommenderが記録しているPodのリソース使用量の履歴が短いと、recommenderは推奨範囲を広げるように作られています。

CreatePodResourceRecommenderの実装をみると、推奨値の計算はestimatorの組合せで実現されています。

confidenceMultiplierは以下の式で推奨範囲を計算します。

履歴の長さが3分でMeasuredResourceUtilization = 0.6 cpuのとき、計算結果は以下のようになります。

この場合、resources.requests.cpuが[0.27 cpu, 289 cpu]の範囲にある間はVPAは何もしません。 つまり、updaterresources.requests.cpuが推奨値の範囲にあるため現在のリソース要求の設定値は問題ないと判断し、何もしません。

この推奨範囲は一日程度のオーダーで変化するため、この実装を使用している限り5~10分程度のプレゼンの中でVPAの動作を見せることができません。

補足事項: 本節の説明は資源使用量ヒストグラムに触れていないため少し不正確ですが、結論としては概ね正しいはずです。

推奨値を決定するアルゴリズムを改造する

残念ながら推奨範囲を決定するパラメータはハードコードされています。仕方がないので、コードを改造することにしました。

プレゼンに向けての要件は以下の通り。

  • 推奨値の範囲の広さは履歴の長さに依存してはいけない
  • 推奨値は実際の資源使用量を反映すべき
  • 資源使用量の実測値が推奨値の範囲外となるようPodを手動で操作できるようにしたい

これらの要件を満たすデザインを作りました。手動で状況を操作するにはシンプルなほうが都合がよいのです。

このデザインは以下のように実装できます。

Patch license: Apache-2.0 (same as original autoscaler)
https://github.com/kubernetes/autoscaler

diff --git a/vertical-pod-autoscaler/pkg/recommender/logic/recommender.go b/vertical-pod-autoscaler/pkg/recommender/logic/recommender.go
index bc2320cca..cdce617fd 100644
--- a/vertical-pod-autoscaler/pkg/recommender/logic/recommender.go
+++ b/vertical-pod-autoscaler/pkg/recommender/logic/recommender.go
@@ -111,9 +111,9 @@ func CreatePodResourceRecommender() PodResourceRecommender {
        lowerBoundEstimator := NewPercentileEstimator(lowerBoundCPUPercentile, lowerBoundMemoryPeaksPercentile)
        upperBoundEstimator := NewPercentileEstimator(upperBoundCPUPercentile, upperBoundMemoryPeaksPercentile)

-       targetEstimator = WithMargin(*safetyMarginFraction, targetEstimator)
-       lowerBoundEstimator = WithMargin(*safetyMarginFraction, lowerBoundEstimator)
-       upperBoundEstimator = WithMargin(*safetyMarginFraction, upperBoundEstimator)
+       targetEstimator = WithMargin(0, targetEstimator)
+       lowerBoundEstimator = WithMargin(-0.3, lowerBoundEstimator)
+       upperBoundEstimator = WithMargin(0.3, upperBoundEstimator)

        // Apply confidence multiplier to the upper bound estimator. This means
        // that the updater will be less eager to evict pods with short history
@@ -126,7 +126,7 @@ func CreatePodResourceRecommender() PodResourceRecommender {
        // 12h history    : *3    (force pod eviction if the request is > 3 * upper bound)
        // 24h history    : *2
        // 1 week history : *1.14
-       upperBoundEstimator = WithConfidenceMultiplier(1.0, 1.0, upperBoundEstimator)
+       // upperBoundEstimator = WithConfidenceMultiplier(1.0, 1.0, upperBoundEstimator)

        // Apply confidence multiplier to the lower bound estimator. This means
        // that the updater will be less eager to evict pods with short history
@@ -140,7 +140,7 @@ func CreatePodResourceRecommender() PodResourceRecommender {
        // 5m history   : *0.6 (force pod eviction if the request is < 0.6 * lower bound)
        // 30m history  : *0.9
        // 60m history  : *0.95
-       lowerBoundEstimator = WithConfidenceMultiplier(0.001, -2.0, lowerBoundEstimator)
+       // lowerBoundEstimator = WithConfidenceMultiplier(0.001, -2.0, lowerBoundEstimator)

        return &podResourceRecommender{
                targetEstimator,

まとめ

  • Vertical Pod Autoscalerリソース要求の変更頻度があまり高くならないように作られています
  • リソース要求の変更頻度を決めるパラメータはハードコードされています
  • リソース使用量の変化に対して数分といったオーダーで応答するには、コード改造が必要です

関連プロジェクト

  • In-place Pod Vertical Scaling feature (Work in progress)

参考文献


コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です