GKE Autopilotのつらみ：コスト最適化の難しさ

# 対象読者
- GKE Autopilot / Standardクラスタで選択を悩んでいるひと
- Autopilotのコスト的なつらみ（こんな事例もあるよ）を知りたいひと

# これは何？
現在業務で、GKE StandardクラスタとAutopilotクラスタを両方運用しています。
そこで感じている、Autopilotのつらみについて書いたものです。

主に**リソース配置における制約が多いためコスト最適化が難しく、どうしても割高になる**ことについて書きました。

:::note warn
- あくまでも自プロジェクトでのケースに則った見解です。ワークロードの特性によっては、本記事で書くつらみは無い場合もあります。
- 2023年12月時点の情報をもとに書いています。今後アップデートにより制限緩和される可能性もあるので、その時の正確な情報を知りたい方はドキュメントをご覧ください。
:::

# 前提知識：GKE Autopilotとは
他にわかりやすく詳細な記事がたくさんあるため、本記事ではすごく簡単に説明します。

GoogleCloudでKubernetesを運用したい場合、GKE `Standard`/`Autopilot` クラスタの選択肢があります。大きな違いは、ノード（データプレーン）がGoogleマネージドであるかどうかです。

`Standard`ではノードのバージョンアップやスケーリング設定など、運用をユーザ側で行う必要があります。
一方`Autopilot`では、ノードはGoogle管理となるため運用が簡単化し、ユーザ側はワークロードに集中できるというメリットがあります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/577028/78516ef2-cd29-83ed-1897-17d913b6afc5.png)

続いて本題です↓

# Autopilotのリソース配置的なつらみ
まず、Autopilotクラスタでのresouce配置の仕様は、下記ドキュメントに書かれています。
これを読むと、Standardクラスタでは意識しなくて良い、けっこう複雑な制約があります。
https://cloud.google.com/kubernetes-engine/docs/concepts/autopilot-resource-requests?hl=ja

個人的に、特に強い制約だと感じたものを3点後述します。

## その1: limitを考慮できない


:::note info
【2024年3月追記】
ドキュメントを再度確認したところ記載が変わっており、この制約はなくなったようです。
リリースノートなどでは、いつサポートされたか確認できませんでした。
:::

ドキュメントの説明を見るに、`limits`を記載しなかった場合、リソース使用量が増えた時はバーストするようです（Standardクラスタと同様の挙動）。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/577028/d2fe1506-3157-6554-cf49-64bf5b2c15a9.png)


https://cloud.google.com/kubernetes-engine/docs/concepts/autopilot-resource-requests?hl=ja#resource-limits

<details><summary>以前までの制約あった時の記述はこちら</summary><div>

Autopilotでは、limitの考慮がないです。
つまり、cpu/memoryリソースはrequest値で固定配置されます。

> Autopilot では requests のみが考慮されます。
この動作により、Autopilot Pod はノード上の追加のリソースを使用してバーストできなくなります。

https://cloud.google.com/kubernetes-engine/docs/concepts/autopilot-resource-requests?hl=ja#resource-limits

Deploymentマニフェスト側でcpu/memoryのlimit値を書いてapplyしたとしても、それはAutopilot側では無視されて、`limit値=request値`となります。

たとえば、下記のようにlimitをrequestと違う値にしたとします。
```yaml
    resources:
      requests:
        cpu: 250m
      limits:
        cpu: 400m
```

これをapplyしてもデプロイ自体できますが、Autopilot側で自動で書き換えられて、実体は下記のようになります。（$ kubectl get -o yaml すれば確認できます）

```yaml
    resources:
      requests:
        cpu: 250m
      limits:
        cpu: 250m # request値に強制的に書き換えられる
```

この制約による不便なことは、2点あると考えています。

1. 平常時はrequest値でリソース確保し、ピーク時のみlimit値でスケーリングという設定ができない。
2. スパイク時にも一時的にバースト利用できるように、「意図的にlimitを書かない」設定ができない。（主にCPUでこうしたいケースがある）

要は、**ピーク時・スパイク時のことを見越してあらかじめrequest値を設定する必要があり、そうすると平常時は余剰リソースが多くなるため、コスト効率が悪くなります**。

※ ただし、HPAによりうまくPod数の水平スケーリングを制御することで緩和できるケースもありえそうです。

個人的に今関わっているプロジェクトでは、スパイク対応のためにcpu limitを意図的に設定しないことでバーストさせたいものが多いため、これは結構強い制約だと考えています。

</div></details>


## その2: 最小値がそこそこ大きい
Autopilotでは、Podあたり配置の cpu/memory ともに最小値の制限があります。
（最大値も制約ありますが、引っかかるケースは少なそうなので本記事では触れません）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/577028/2813c81b-4e34-f451-92fc-4ccfc905b49f.png)

https://cloud.google.com/kubernetes-engine/docs/concepts/autopilot-resource-requests?hl=ja#compute-class-min-max

つまり、下記制約になります。

```text
podごとのrequest値下限
- cpu: 250m
- memory: 500MiB
```

あまり負荷が高くならないワークロードであったり、ローンチ直後でユーザ数もまだ多くないようなサービスであれば、リソース量はそんなに必要ないかと思いますが、（たとえばcpu100m以下、memory300MiB以下にしたいケースは結構多いイメージ）、そんな場合でも**余分なリソースを無理やり配置する必要があるため、コスト効率は悪くなってしまいます**。


## その3: cpu増設時にmemoryも増設する必要がある
上記画像にも書かれていますが、cpu : memoryの比率にも制限があります。

```
CPU/Memory比率
vCPU:GiB = 1:1以上
```

具体例を出すと、下記で設定していたとします。
```yaml
    resources:
      requests:
        cpu: 500m
        memory: 500Mi
```

cpuの使用量が多くなってきたので750mに増設したくなったとします。**本当はcpuの値だけ引き上げたいところですが、1:1以上の制約があるため、memoryも必然的に引き上げないといけません。**
（仮にmemory引き上げずにapplyしても、Autopilot側で強制的に書き換えられます）

変更後は下記のようになります。
```yaml
    resources:
      requests:
        cpu: 750m # 本当はこっちだけ引き上げたい
        memory: 750Mi # 実際要らないがこっちも引き上げ必要
```

このような場合、**使わないメモリのためにいくらか無駄なコストを費やすことになります。**


# 最後に
Autopilotだと複雑な制約がありますが、裏側でやってるGoogleCloud側のGCEノード配置的なことを想像すると、自然なことで分からなくもないって感じです。

なんだかAutopilotのネガキャンっぽくなってしまいましたが、AutopilotはPod単位課金となるため料金体系が明確ですし、Standardで必然的に発生するノードの余剰コストが削減できるため、場合によってはStandardより低コストで運用できます。

バッチ処理や、（GPUサポートされていることもあり）ML系の運用をしたい場合は有効な選択肢となることが多いようです。~~（バッチならCloudRunJobsで良い気もする）~~