VPA Recommenderコードリーディングメモ

公開用にまとめた訳でもなく、コード読んだ時に取ってるメモなので内容が参考になるとは思っていないが、人のコードリーディング方法が気になるので公開してみるテスト。(inspired by  [コードリーディングの履歴を残しながら調査したい @inajob](https://qiita.com/inajob/items/672a4b5bbce0151a4f95))
私のコードリーディングの記録方法については以下参照:

https://ladicle.com/post/20200625_123915/

## Motivation

Recommenderはメトリクスを集計してリソースの推奨値を計算し、VPA/Checkpointリソースに書き込むコンポーネントである。VPAのRecommenderの挙動詳細が理解できていないので、推奨値の算出周りを中心にKEP読んでざっくり設計を掴んだ後コードを確認する。

> 2021/07/01現在の最新バージョンはv0.9.2, 読んだコードはmasterの`682dec`を対象としている。

# Design

## Recommendation Model

-   [kubernetes/community &#x2013; MVP Recommendation model design](https://github.com/kubernetes/community/blob/master/contributors/design-proposals/autoscaling/vertical-pod-autoscaler.md#recommendation-model) より<sup><a id="fnr.1" class="footref" href="#fn.1">1</a></sup>
    -   推奨値の計算にはname,image,command,argsといった同じプロパティを持つコンテナのメトリクスを利用
    -   memory/CPUの使用量が独立した確率変数(random variables)であり、過去N日間(default N=8d)に分布したものと仮定する
        -   将来的にはTrends、周期性、時間ごとのパターン検出に対応する
    -   CPUの目標値は、コンテナのリクエストに対するCPU使用率が高いパーセンテージ(例:95%)を超えたときの時間の割合を、しきい値以下(例:全時間で1%など)となるように保つこと
        -   このモデルでは CPU Usage を短い間隔で計測した使用率の平均値のことをいう
            -   SpikyなLatency Sensitiveなワークロードでは、計測間隔が短いほうが品質が向上する
            -   妥当な最小解像度は1/min, 推奨値は1/sec
    -   Memoryの目標値は、特定のTime WindowにおいてコンテナのMemory使用率がリクエスト値を超える確率を特定のしきい値未満(例:24時間で1%未満)に保つこと
        -   windowはOOMによって引き起こされたEvictionが以下に影響を与えないようにするために長くとる(>=24h)
            -   (a) アプリケーションの可用性
            -   (b) バッチ処理の進行 (より高度なモデルではユーザはSLOを指定できる)


### Handling OOMs

-   コンテナが使用可能なメモリを超えてevictionされたとき、OOMイベントとして記録する
    -   最後に観測した使用量に 安全マージン を乗算した値をメモリ使用量のサンプルに変換する


## History Storage

-   [kubernetes/community &#x2013; History Storage](https://github.com/kubernetes/community/blob/master/contributors/design-proposals/autoscaling/vertical-pod-autoscaler.md#history-storage) より
    -   VPAは過去のイベントやリソース利用率へのプロバイダへのデータアクセスAPIを定義する
        -   初期はPrometheusを(少なくともリソース使用率パートを)APIのリファレンス実装として利用する
        -   過去のイベントは他のソリューション(e.g. [infrastore](https://github.com/kubernetes/kubernetes/issues/44095))も利用可能にする
        -   ユーザによる実装もプラグインとして利用可能にする
    -   履歴のストレージでは、Recommenderと同様にリソース使用率とイベントのデータが投入される
        -   ストレージは最低8日のデータを保持
        -   このデータは起動時にRecommenderを初期化するために利用される


# Code Map


## Overview

-   [kubernetes/autoscaler/vertical-pod-autoscaler/pkg/recommender](https://github.com/kubernetes/autoscaler/blob/682dec3eb0a10d35cf18565b8c28d9b09d2ad524/vertical-pod-autoscaler/pkg/recommender/README.md) のディレクトリ構造
    -   **main.go:** Recommenderの初期化、Interval(default 1s)ごとにRecommenderを起動する
    -   **routines/:** Recommenderの定義、mainから定期的に呼ばれている `RunOnce()` などはここ
    -   **checkpoint/:** 集計した使用率の履歴をCheckpointリソースに書き込むWriterの実装
    -   **input/:** OOM eventやPod/VPA/CheckpointなどのAPIリソース、metricsを収集するClusterStateFeederの実装
    -   **model/:** vpaのmodel定義がある(VPA APIリソースとは別)
    -   **logic/:** リソースのestimater, estimaterを利用するrecommenderのロジック部分
    -   **util/:** histogramを処理するutility


## Recommender

-   大本のRecommenderのinterface
    -   <https://github.com/kubernetes/autoscaler/blob/682dec3eb0a10d35cf18565b8c28d9b09d2ad524/vertical-pod-autoscaler/pkg/recommender/routines/recommender.go#L47>
    -   `RunOnce()` の大まかな流れ
        -   **clusterStateFeeder.LoadVPA():** VPAリソースをvpaモデルに変換する
        -   **clusterStateFeeder.LoadPods():** VPA対象のPod収集
        -   **clusterStateFeeder.LoadRealTimeMetrics():** 対象Podのコンテナリソース使用状況やOOMの検出、記録
        -   **UpdateVPAs():** 推奨値の計算とVPAステータス更新をAPI Serverに通知する
        -   **MaintainCheckpoints():** 現在のcheckpointをAPI Server経由で更新し、古いものをGCする
        -   **GarbageCollect():** 古いAggregateCollectionStatesをGCする
-   PodResourceRecommenderのinterface (logic)
    -   <https://github.com/kubernetes/autoscaler/blob/682dec3eb0a10d35cf18565b8c28d9b09d2ad524/vertical-pod-autoscaler/pkg/recommender/logic/recommender.go#L32>
    -   前段で集計したContainerNameToAggregateStateMap(container名をキーとしたstateマップ)を引数にとり、算出したRecommendedContainerResourceを返す
        
        ```go
        fraction := 1.0 / float64(len(containerNameToAggregateStateMap))
        minResources := model.Resources{
          model.ResourceCPU:    model.ScaleResource(model.CPUAmountFromCores(*podMinCPUMillicores*0.001), fraction),
          model.ResourceMemory: model.ScaleResource(model.MemoryAmountFromBytes(*podMinMemoryMb*1024*1024), fraction),
        }
        
        recommender := &podResourceRecommender{
          WithMinResources(minResources, r.targetEstimator),
          WithMinResources(minResources, r.lowerBoundEstimator),
          WithMinResources(minResources, r.upperBoundEstimator),
        }
        ```
        
        -   podMinXXはrecommenderの起動フラグで指定できる最小スケール値(`main.go` ではなくここで定義されている)
            -   default: CPU=25, Mem=250
        -   戻り値のRecommendedContainerResourceはVPAリソースに格納されるlower/target/upperの3つを含む
            -   対象リソースは(e.g. cpuのみ)ここでフィルタされる
        -   ここで使われているbase recommenderは `CreatePodResourceRecommender()` で生成されている
            -   targetはCPU/Mem共に90ptile
            -   percentile estimatorにsafetyMarginFraction分のmarginを追加している
                -   minと同じく起動フラグで指定できる(default: 0.15)
            -   upper/lowerにはwith confidence multiplierも設定


### Estimator

-   Estimatorは予測値を返すGetResourceEstimation()を持つInterface
    -   <https://github.com/kubernetes/autoscaler/blob/682dec3eb0a10d35cf18565b8c28d9b09d2ad524/vertical-pod-autoscaler/pkg/recommender/logic/estimator.go#L31>
    -   実装されているEstimator一覧
        -   **const:** test用で定数のリソースを返す
        -   **percentile:** 指定されたpercentileのCPU使用率とMemoryピーク分布を返す
        -   **with margin:** 指定されたマージンをリソースにのせて返す
        -   **with min resources:** 指定された最小値を反映したリソースを返す
        -   **with confidence multiplier:** confidenceMultiplierが適用された値を返す (upper/lowerで利用される)
    -   percentileの実装はutil, checkpointに格納されているaggregated bucketsを処理している
        -   <https://github.com/kubernetes/autoscaler/blob/682dec3eb0a10d35cf18565b8c28d9b09d2ad524/vertical-pod-autoscaler/pkg/recommender/util/histogram.go#L159>


### LoadMetrics

-   ClusterStateFeederでreal-time metricsを収集しているのはここ
    -   <https://github.com/kubernetes/autoscaler/blob/682dec3eb0a10d35cf18565b8c28d9b09d2ad524/vertical-pod-autoscaler/pkg/recommender/input/cluster_feeder.go#L402>
    -   metrics clientから収集されたsampleは、aggregater通してvpa modelのhistogram bucketに追加される
        -   <https://github.com/kubernetes/autoscaler/blob/682dec3eb0a10d35cf18565b8c28d9b09d2ad524/vertical-pod-autoscaler/pkg/recommender/model/aggregate_container_state.go#L182>
        -   bucketに投入されるCPUのweightは `max(cpu-request-core, min-sample-weight)` (min sample weight=0.1)
            -   使用量の増加にすく反応できるようにweightをかけてhistoryを減衰させている
    -   OOMもここでsampleに変換している
        -   <https://github.com/kubernetes/autoscaler/blob/682dec3eb0a10d35cf18565b8c28d9b09d2ad524/vertical-pod-autoscaler/pkg/recommender/input/cluster_feeder.go#L103>
        -   OOMはEventをwatchしてchannelに流している
        -   OOMされた瞬間のリソース使用量は取得できないのでartificialにmemoryサンプルが生成されている
            -   <https://github.com/kubernetes/autoscaler/blob/682dec3eb0a10d35cf18565b8c28d9b09d2ad524/vertical-pod-autoscaler/pkg/recommender/model/container.go#L193>


# Footnotes

<sup><a id="fn.1" href="#fnr.1">1</a></sup> [Minimum Viable Product (MVP) and Design - Balancing Risk to Gain Reward | Interaction Design Foundation (IxDF)](https://www.interaction-design.org/literature/article/minimum-viable-product-mvp-and-design-balancing-risk-to-gain-reward)