PodのResource設定(spec.containers.resources
)はこうあるべき、みたいなふんわりとしたイメージが自分の中であるのですが言語化できていないのでしていきたいと思います。
なお、この記事の内容は運用するKubernetes上には自分たちが運用するサービスに関するコンテナのみが載っている前提です。
不特定多数のユーザーのコンテナが載る際はまた別の考えになると思います。
あとこの記事の内容は僕個人の考えです。
TL;DR
- CPU
- requests
- 必要。コンテナを動かすために必要な値を設定してね。低すぎちゃだめよ。
- limits
- 不要。でも場合によっては必要だよ。
- requests
- Memory
- requests
- 必要。コンテナを動かすために必要な値より少し余裕を持って設定してね。
- limits
- 必要。requestsと同じ値にしてね。
- requests
1. CPU
1-1. requests
- 必ず設定しなくてはいけないもの
- コンテナを動かすのに必要な値を設定する
- このくらいは常に使うよねっていう値にする。低くしすぎてはいけない。
1-1-1. なぜ必ず設定が必要なのか?
この数値は以下の用途で使用されるためです。
- PodをNodeにスケジューリングするとき
- HPAのCPU使用率の計算元
詳しく見ていきます。
1-1-1-1. PodをNodeにスケジューリングするとき
PodはどこかのNodeに配置しないといけません。
その配置先のNodeを決める際に requests.cpu
の数値を使用します。
CPUに割り当て可能な容量(CPU使用率のことではない)があるNodeに対してPodを配置することができます。
逆にこの数値を設定しない場合、Nodeに割り当て可能な容量があろうがなかろうがどこにでも配置することができます。
その代わりNodeのCPU使用率が高まってきたときに、パフォーマンスが悪くなる場合があります。
1-1-1-1-1. cpu_share
cpu_share
とは cgroupの1機能で、コンテナ内のプロセスが使用できるCPU時間を制御するものです。
以下の記事が詳しいです。
requests.cpu
で指定された値は cpu_share
の値に変換されます。
cpu_share
の値はコンテナ間で相対的な数値です。
たとえば1CPUあるNodeに展開される場合、それぞれのコンテナが得られる保証されたCPU時間は以下のようになります。
コンテナ名 | requests.cpu | cpu_share | 得られるCPU時間 |
---|---|---|---|
A | 250m | 256 | 0.5cpu |
B | 125m | 128 | 0.25cpu |
C | 125m | 128 | 0.25cpu |
Nodeにいるコンテナが1つの場合は、そのコンテナはNodeにあるCPUをフル活用できるわけです。
requests.cpu
が指定されていない場合、 cpu_share
は0になります。
0だと保証されたCPU時間も0なので、NodeのCPU使用率が高まるとCPUの利用ができなくなりパフォーマンスが悪くなります。
1-1-1-2. HPAのCPU使用率の計算元
HPAでCPU使用率によってPodの増減をしたい場合は requests.cpu
の指定は必須になります。
計算式はこのようになっています。
currentUtilization = int32((metricsTotal * 100) / requestsTotal)
1-2. limits
- 基本的に設定は不要だが必要な場合もある
1-2-1. なぜ不要なのか?
まずHPAがある場合を考えてみます。
CPU使用率によってPodが増減する設定をしている場合は、CPU使用率が高まればPodが増える(≒Nodeが増える)のでNode自体のCPUスロットリングは継続して生じません。
また急なトラフィックが来た場合も空いたCPUをフルで使えるため一時しのぎができます。
limitsが設定されているとこのような一時しのぎができません。(limitsを十分大きな値にしている場合は可能です。でもそれって意味あるんでしょうか?)
他のメトリクスでPodの増減をする場合でもCPU使用が継続して高くなりすぎることは考えづらいです。そうなったら増減のパラメータの設定がおかしいことを疑ってください。
よってHPAがある場合は不要と考えています。
HPAがないPodの場合も、Kubernetes上にある他のPodに resources.requests.cpu
が設定されていれば cpu_share
によって最低限そのCPU時間はもらえるのが保証されているため、PodのCPU使用が多くなったとしても他のPodに影響を与えることは考えづらいです。(そのために resources.requests.cpu
は低すぎてはいけない)
また短時間で高負荷を与えるようなバッチのようなものの場合は、NodeのCPUに余裕があればどんどんCPUを使ってほしいのでそういった場合、limitsは邪魔になります。
1-2-2. 必要なケースは?
limits.cpu
が必要なケースを考えてみます。
1-2-2-1. Node(Pod)の数を簡単に増やせない
環境がクラウドではなかったり予算に上限があるなど、Nodeを簡単に増やせないとPodの数を増やすのも難しいと思います。
その場合は制限を設け、アプリケーションのパフォーマンス劣化もやむなしと判断できます。
1-2-2-2. 可能な限り生き続けてほしいPod
長時間動くバッチやステートフルなアプリケーションの場合、急にPodが削除(Evict)されると困ることがあります。
PodがEvictされるケースは以下のようなケースです。
- NodeがDrainされた
- Preemption
- Node-pressure Eviction
Drainに関してはオートスケールとは関係ないNodeGroup上のNodeにそのようなPodを配置するなどで回避することができます。
PreemptionはEvictされないように優先度の高いPriorityClassをPodにつけましょう。
Node-pressure Evictionに関してはEvictionの対象にならないようにする必要があります。
そのためにQuality of Service (QoS)という仕組みがあり、最優先にするために limits.cpu
を設定します。(これだけではだめです。詳しくは次の項目を読んでください)
1-2-2-2-1. Quality of Service (QoS)
KubernetesはPodのスケジュールやEvictをする際にQoSを参照します。
https://kubernetes.io/docs/tasks/configure-pod-container/quality-service-pod/
上から順に優先度が高く、Evictされづらいです。
- すべてのコンテナでCPUのlimitsとrequestsが同一かつMemoryのlimitsとrequestsが同一の場合は
Guaranteed
- いずれかのコンテナでCPUまたはMemoryにrequestsが設定されていれば
Burstable
- それ以外は
BestEffort
となります。
Nodeになにか問題があり(例えばメモリ逼迫)PodをEvictしなければならないときにこの設定が参照されます。
2. Memory
2-1. requests
- 必ず設定しなくてはいけないもの
- コンテナを動かすのに必要な値を設定する
- このくらいは常に使うよねっていう値よりも少し余裕のある数値にする
2-1-1. なぜ必ず設定が必要なのか?
この数値は以下の用途で使用されるためです。
- PodをNodeにスケジューリングするとき
- HPAのメモリ使用率の計算元(あまり使わない気がするけど・・・)
理屈はCPUと同じなので割愛します。
2-2. limits
- 必ず設定しなくてはいけないもの
-
resources.requests.memory
と同じ値にする
2-2-1. なぜ必ず設定が必要なのか?
メモリの使いすぎを防ぐためです。
CPUとは違いメモリは絶対的な数値なため、使いすぎると他のPodに影響があります。
たとえばメモリ1GBのNodeがあるとして、
requestが200MBのコンテナがある状態でそのコンテナが1GB使ってしまったらどうなるでしょうか?
新たなPodをNodeにスケジューリングするとき、そのNodeのメモリに割り当て可能な容量(メモリ使用率ではない)は空いてても
実際にスケジュールしたら起動に必要なメモリが足りずCrashLoopBackOffしてしまうことになります。
このようなことを避けるために制限は必要です。
2-2-2. なぜ resources.requests.memory
と同じ値にするの?
これも同じ話でメモリで指定する値は絶対的な数値なので、Nodeのメモリの空き状況とメモリ使用率は近い状態にしておかないと、Podをスケジュールした際に起動できないことが考えられるからです。
たとえば以下のようなコンテナがメモリ4GBのNode上にいるとしましょう。
コンテナ名 | requests | limits |
---|---|---|
A | 1GB | 2GB |
B | 1GB | 2GB |
AとBが制限いっぱいまでメモリを使うとこの時点でNodeは4GBフルで使ってしまっていますが、Nodeのメモリに割り当て可能な容量はまだ2GBあります。
同様のリソースを求めるコンテナCがこのNodeにスケジュールされると起動するために必要なメモリを確保できないためPodが起動できなくなる可能性があります。
参考資料