Kubernetesで常に余剰Nodeを確保しPodのスケールアウトの低速化を防ぐ「オーバープロビジョニング」について

KubernetesのNodeそのもののスケールイン・アウトを行うための手段として「ClusterAutoscaler」を採用することがあると思います。
ClusterAutoscalerを採用する場合、**オーバープロビジョニング** という仕組みを導入するとPodのスケールアウトがスムーズになるよって話をしたいと思います。

# オーバープロビジョニングとは？
簡単にいうと **余剰Nodeを確保する機能** です。

まずNodeのスケールアウトについておさらいしましょう。
PodのスケーリングにHorizontal Pod Autoscaler(HPA)を導入すると、Pod自体のスケーリングが可能になります。
負荷に合わせPodの数を合わせる場合、NodeにそのPodをスケジュールするための余剰スペースが必要になります。
そのスペースがない場合は、それを検知しClusterAutoscalerがNodeを増やしてくれます。

というわけでPodを起動する際は2つのパターンがあるわけです。

1. Podを起動する
2. Nodeを増やす -> Podを起動する

2は1と比較するとNodeを用意しなければいけない分、遅いことがわかります。

この速度低下を回避するには、常に空きのあるNodeがいればいいということになります。
その機能を提供するのが **オーバープロビジョニング** です。

# オーバープロビジョニングの導入方法
オーバープロビジョニングを導入する手順はこちらにあります。
https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/FAQ.md#how-can-i-configure-overprovisioning-with-cluster-autoscaler

この手順通りやると、以下の3つのリソースが生成されます。
それぞれについて説明します。(RBACの設定も必要なのですが説明から省きます)

* PriorityClass
* Deployment/overprovisioning
* Deployment/overprovisioning-autoscaler

## PriorityClass
Podには優先度をつけることができます。
Podをスケジューリングする際、もしスケジューリングできない場合は優先度の低いPodを追い出すことができます。
それを表現するのが[PriorityClass](https://kubernetes.io/ja/docs/concepts/configuration/pod-priority-preemption/#priorityclass)というリソースです。
これが設定されていないPodは優先度0が設定されます。

今回はオーバープロビジョニング用に優先度が通常よりも低い数値(-1)のPriorityClassを用意します。

## Deployment/overprovisioning
オーバープロビジョニングを実現するためのPodを定義しています。
上述しているPriorityClassをこのPodに割り当てています。

`k8s.gcr.io/pause` というコンテナイメージを使っているのを見るとなにもしないコンテナであることがわかります。
これでどうオーバープロビジョニングを実現しているのか？というのは後述します。

## Deployment/overprovisioning-autoscaler
上述したオーバープロビジョニングPodをスケールさせるためのPodです。
HPAとは異なり、この `cluster-proportional-autoscaler` というコンテナイメージは

* CPUコア数
* Node数

を元にPodをスケールアウトしてくれます。
この導入手順では「CPUコアが1つにつきオーバープロビジョニングPodを1つ起動する」という設定になっています。

# オーバープロビジョニングの実現方法
導入手順では上述したとおり3つのリソースを用意しただけです。
これでオーバープロビジョニングの導入は完了していますが、どのようにそれを実現しているのかの仕組みを解説していきます。

まずNode上には以下のPodが起動しているとします。（今回関係ないPodは除外）

* ApplicationPod
    * ユーザーがデプロイしているアプリケーションのPodとする
* OverProvisioningPod
    * オーバープロビジョニング用のPod


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152303/499d62d2-1a6e-48de-b96b-dba2d140ea6b.png)

NodeのCapacityはパツパツだとします。
この状態で新しいPodを起動したいとすると、Nodeに空きがないのでスケジューリングできません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152303/d6b9d002-26ac-ace5-14e0-e4e352bd3e1c.png)

ただしオーバープロビジョニングのPodは優先度が低い設定をされており（PriorityClass）、排出されます。
そうすると次はオーバープロビジョニングのPodのスケジューリングができなくなります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152303/ea1d3bbb-e076-a1ff-13c2-831587a59dec.png)

この状態になるとClusterAutoscalerはそれを検知し、NodeGroupをスケールアウトさせます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152303/08df947c-0a7f-788c-1a3e-342597e84d15.png)

空きがあるNodeが増えるのでオーバープロビジョニングのPodはそこにスケジュールされます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/152303/bf6fb1b8-9b24-a2cb-2e78-f08016e2a003.png)

新たに作成されたNodeはオーバープロビジョニングのPodしか載っていません。
こうすることで、余剰Nodeを確保することができるわけです。

# オーバープロビジョニングのチューニング
デフォルトの設定でもいいのですが気にしたほうがよいポイントを挙げておきます。

* オーバープロビジョニングのPod数
* オーバープロビジョニングのPodのresource

## オーバープロビジョニングのPod数
デフォルトではCPUコア数1に対して1Pod起動します。
https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/FAQ.md?plain=1#L473

大規模の場合、CPUコア数の総数も相当になると思います。
その数だけPodを起動すると、無駄なリソースやIPを食いつぶすことになります。

どのくらいの数にしておけばいいのかというのは環境によるので一概には言えませんが
メインとなるPodがスケールアウトする際に短時間の間にどのくらいの数のPodが必要になるかを計算し、
その数に合わせるようにしておくと最低限の余剰リソースは確保できそうです。

## オーバープロビジョニングのPodのresource
デフォルトではCPU: 200m確保します。
https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/FAQ.md?plain=1#L447

これもPod数と同じ考え方で、メインとなるPodが確保するリソースをこちらでも同様に確保しておくとわかりやすいと思います。

# 最後に
このオーバープロビジョニングはEKSでもベストプラクティスとして紹介されてたりもします。
https://aws.github.io/aws-eks-best-practices/cluster-autoscaling/#overprovisioning

しかし日本語で説明している記事が見当たらなかったので今回作成させていただきました。
導入がまだの方は検討していただければと思いますー