概要
Kubernetesには以下のフィールドでCPUやメモリを制限することが可能です。
spec.containers[].resources.limits.cpu
spec.containers[].resources.limits.memory
spec.containers[].resources.requests.cpu
spec.containers[].resources.requests.memory
ref: Resource Management for Pods and Containers | Kubernetes
cpu: 1
、cpu:2
といった整数値はイメージが湧きやすいですが、
cpu: 100m
といった時にどういう動きをするのか?- マルチコアのノード環境で↑の時、アプリケーションコードでマルチコア前提のものはきちんと動くのだろうか?
という疑問があったので調べてみました。
環境
- Kubernetes v1.10.11
サンプル
今回検証に使用したサンプルコードは以下です。
検証
まずは推測よりも計測です。
アプリケーションコード
以下のような
- 最初にCPU数を出力
- 多数のgoroutineを走らせる
プログラムを用意しました。
package main import ( "fmt" "net/http" "runtime" ) func main() { cpus := runtime.NumCPU() fmt.Println("CPUs:", cpus) for i := 0; i < 100; i++ { goroutine() } http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "hello world") }) http.ListenAndServe(":8000", nil) } func goroutine() { go func() { var counter int64 for { counter++ } }() }
goroutineを用意しているのは
で検証した時のように、スケジューラの挙動を確認するためです。
deployment
Kubernetesのdeployment.yamlは以下。
apiVersion: apps/v1 kind: Deployment metadata: name: sample-resource spec: replicas: 1 selector: matchLabels: app: sample-app template: metadata: labels: app: sample-app spec: containers: - name: golang image: jun06t/show-cpus # env: # - name: GODEBUG # value: "scheddetail=1,schedtrace=1000" resources: requests: memory: "128Mi" cpu: "100m" limits: memory: "256Mi" cpu: "200m"
実行
- CPUコア数:6
- メモリ:4G
で検証してみます。
CPU数チェック
$ kubectl apply -f resource.yaml deployment.apps "sample-resource" created
ログを見てみます。
$ stern sample
+ sample-resource-5bd456968-44kxv › golang
sample-resource-5bd456968-44kxv golang CPUs: 6
ちゃんと6つ検知していますね。
試しにreplicas: 2
にしてpodを増やしてみます。
$ stern sample + sample-resource-5bd456968-44kxv › golang + sample-resource-5bd456968-prqnc › golang sample-resource-5bd456968-prqnc golang CPUs: 6 sample-resource-5bd456968-44kxv golang CPUs: 6
どちらも6です。CPU数は全て検知できているようです。
GODEBUGでgoroutineの動きをチェック
先程のdeploymentの環境変数をアンコメントして実行します。
SCHED 11095ms: gomaxprocs=6 idleprocs=0 threads=9 spinningthreads=0 idlethreads=0 runqueue=0 gcwaiting=0 nmidlelocked=0 stopwait=0 sysmonwait=0 P0: status=1 schedtick=1 syscalltick=25 m=0 runqsize=12 gfreecnt=0 P1: status=1 schedtick=2 syscalltick=0 m=3 runqsize=24 gfreecnt=0 P2: status=1 schedtick=1 syscalltick=0 m=5 runqsize=49 gfreecnt=0 P3: status=1 schedtick=1 syscalltick=0 m=4 runqsize=3 gfreecnt=0 P4: status=1 schedtick=1 syscalltick=0 m=7 runqsize=5 gfreecnt=0 P5: status=1 schedtick=1 syscalltick=0 m=8 runqsize=2 gfreecnt=0 M8: p=5 curg=88 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=false lockedg=-1 M7: p=4 curg=85 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=false lockedg=-1 M6: p=-1 curg=-1 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=true lockedg=-1 M5: p=2 curg=54 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=false lockedg=-1 M4: p=3 curg=92 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=false lockedg=-1 M3: p=1 curg=79 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=false lockedg=-1 M2: p=-1 curg=-1 mallocing=0 throwing=0 preemptoff= locks=1 dying=0 spinning=false blocked=false lockedg=-1 M1: p=-1 curg=17 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=false lockedg=17 M0: p=0 curg=105 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=false lockedg=-1 G1: status=4(IO wait) m=-1 lockedm=-1 G17: status=6() m=1 lockedm=1 G2: status=4(force gc (idle)) m=-1 lockedm=-1
するとこの様になりました。P
が6つ、M
が9つあることからちゃんと全CPUを使っているようですね。
解説
requests, limitsはどうリソース制限しているのか。
Kubernetesのこのリソース制限はDockerの機能を利用しています。
Kubernets | Docker |
---|---|
requests.cpu |
--cpu-shares として |
limits.cpu |
--cpu-period: 100ms 固定で--cpu-quota として |
cpu-sharesとは
cpu-sharesはCPUリソースを配分する際の重みです。
具体的に表すと以下です。
CPUコア数 | コンテナ数 | cpu-shares | リソース配分 |
---|---|---|---|
1 | 1 | 1024 | 1コアの100% |
1 | 2 | コンテナA: 1024 コンテナB: 1024 |
コンテナA: 1コアの50% コンテナB: 1コアの50% |
1 | 2 | コンテナA: 2048 コンテナB: 2048 |
コンテナA: 1コアの50% コンテナB: 1コアの50% |
1 | 2 | コンテナA: 1024 コンテナB: 3072 |
コンテナA: 1コアの25% コンテナB: 1コアの75% |
2 | 2 | コンテナA: 1024 コンテナB: 1024 |
コンテナA: 2コアの50% (=1コアの100%分) コンテナB: 2コアの50% |
まとめると
- リソース配分はコンテナ数に影響される
- 値は絶対値ではなく他の重みとの相対値
- 全CPUが使用される
なので、リソースの下限値として説明されることが多いです。
cpu-period, cpu-quota
これはカーネルのCFS(Completely Fair Scheduler)を利用しています。
--cpu-period
で指定した時間の内--cpu-quota
で指定した時間までコンテナにCPUを割り当てる事が可能です。
k8sでは--cpu-period: 100ms
でセットされており、limits
の数値のx100が--cpu-quota
にセットされます。
以下具体的な設定とリソース配分です。
CPUコア数 | limits | cpu-quata(ms) | リソース配分 |
---|---|---|---|
1 | 1 | 100 | 100ms中100ms使う =1コアの100% |
1 | 100m(=0.1) | 10 | 100ms中10ms使う =1コアの10% |
2 | 1 | 100 | 100ms中100ms使う =1コアの100% =2コアの50% |
2 | 2 | 200 | 200ms中200ms使う =2コアの100% =1コアの200% |
4 | 2 | 200 | 400ms中200ms使う =4コアの50% =1コアの200% |
まとめると
- 値はCPU数に限らず絶対値
- 全CPUが使用される
となります。 CFSはその設定を超えた秒数CPUを使用した場合、次のperiodまで利用できなくなるので、CPUバウンドなアプリケーションであれば不可解な一時停止が発生する可能性があります。
まとめ
結論としてマルチコアであればrequests, limitsをどう設定してもマルチコアを使用するということが分かりました。
なのでアプリケーション側でそこを意識して何かする必要はなさそうです。