GAミント至上主義

Web Monomaniacal Developer.

GKEのコスト節約を考える2 CPUリクエストを調整してリソースを有効活用する

いろいろ調べてたら前回に引き続くことになった。

uyamazak.hatenablog.com

これまでGKE上でpodが立ち上がらないエラーをkubectl evなどで確認すると原因はほぼ「Insufficient CPU」だった。

oceanusは、BigQueryにデータを流すなどのあまりメモリを使わない処理が大半なので、ほかのプロダクトだとメモリの場合もあるかもしれないけど

ではKubernetesが何をもってCPUの不足を判断しているか調べてみる。

nodeの状況を確認する

まずどのnodeにどのpodが配置されているか確認する
kubectlはシェルのaliasでkcにしてある。

% kc describe node

# 省略
Capacity:
 cpu:     2
 memory:  2054088Ki
 pods:    110

# 省略

Non-terminated Pods:         (7 in total)
  Namespace                  Name                                                              CPU Requests  CPU Limits  Memory Requests  Memory Limits
  ---------                  ----                                                              ------------  ----------  ---------------  -------------
  default                    arms-2064021732-v0q31                                             100m (5%)     0 (0%)      0 (0%)           0 (0%)
  default                    rabbitmq-1915863861-m93bp                                         100m (5%)     0 (0%)      0 (0%)           0 (0%)
  kube-system                event-exporter-v0.1.7-958884745-9n8sd                             0 (0%)        0 (0%)      0 (0%)           0 (0%)
  kube-system                fluentd-gcp-v2.0.9-554wh                                          100m (5%)     0 (0%)      200Mi (14%)      300Mi (21%)
  kube-system                heapster-v1.4.3-1324981535-1wtmq                                  138m (7%)     138m (7%)   302056Ki (21%)   302056Ki (21%)
  kube-system                kube-proxy-gke-oceanus-asia-northeast1-pool-c2m2-dee44aa5-5jgm    100m (5%)     0 (0%)      0 (0%)           0 (0%)
  kube-system                kubernetes-dashboard-1962351010-qr3r9                             100m (5%)     100m (5%)   100Mi (7%)       300Mi (21%)
Allocated resources:
  (Total limits may be over 100 percent, i.e., overcommitted.)
  CPU Requests  CPU Limits  Memory Requests  Memory Limits
  ------------  ----------  ---------------  -------------
  638m (33%)    238m (12%)  609256Ki (42%)   916456Ki (64%)

# 省略

各nodeについていろいろ出てくるけど、CPU周りに注目してみると、上記のnodeはCPUが2、計7つのPodが稼働中であり、CPU Requestsは638m (33%) だということが分かる。

podの7つ中5つはkube-systemに使われており、自分のアプリは2つしか使ってないと思うとちょっともったいない感じがする。特にログのfluentdと監視のheapsterの消費が大きい。

node一つ一つにこれらが入ってることを考えると、やはり前回の記事のように小さいnodeをたくさんより、大きいnode少しで済ますのは、全体のkube-systemの数を減らすことができるので、リソース有効活用の意味でも正しいかもしれない。

話がそれた。

これまで100m (5%) とか 638m (33%) は目にしていたけど何を表しているのか深くは考えていなかった。

Kubernetes上のCPUの意味を理解する

調べてみると下記ページに記載があった。

Managing Compute Resources for Containers - Kubernetes

Meaning of CPU

Limits and requests for CPU resources are measured in cpu units. One cpu, in Kubernetes, is equivalent to:
1 AWS vCPU
1 GCP Core
1 Azure vCore
1 Hyperthread on a bare-metal Intel processor with Hyperthreading
Fractional requests are allowed. A Container with spec.containers[].resources.requests.cpu of 0.5 is guaranteed half as much CPU as one that asks for 1 CPU. The expression 0.1 is equivalent to the expression 100m, which can be read as “one hundred millicpu”. Some people say “one hundred millicores”, and this is understood to mean the same thing. A request with a decimal point, like 0.1, is converted to 100m by the API, and precision finer than 1m is not allowed. For this reason, the form 100m might be preferred.
CPU is always requested as an absolute quantity, never as a relative quantity; 0.1 is the same amount of CPU on a single-core, dual-core, or 48-core machine

↓Google翻訳

CPUリソースの制限と要求は、CPU単位で測定されます。 Kubernetesにある1つのCPUは、次のものと同等です。
1 AWS vCPU
1 GCPコア
1 Azure vCore
1ハイパースレッディング機能を備えたベアメタルIntelプロセッサ上のハイパースレッド
分数リクエストが許可されます。 spec.containers []。resources.requests.cpuが0.5のコンテナは、CPUの1つを要求するものの半分のCPUが保証されています。 式0.1は式100mと等価であり、これは "100ミリプ"として読み取ることができる。 一部の人々は "100ミリコア"と言っています。これは同じことを意味すると理解されています。 小数点を含む要求(0.1など)はAPIによって100mに変換され、1mより細かい精度は許可されません。 このため、フォーム100mが好ましい場合がある。
CPUは常に絶対量として要求され、相対量としては決して要求されません。 0.1は、シングルコア、デュアルコア、または48コアマシン上のCPUの量と同じです

環境ごとの1CPUの違いは置いておいて、

1000m → 1CPUを100%使う

ということらしい。1CPUのリソースを時間で1=1000mに分割して管理している。

つまりさっきの2CPUのノードで、638m (33%)は、2000m分の638mだから33%(638/2000を計算すると32%ぐらいだけど)と、だいたい67%は空いているという意味になる。

そして、そのデフォルトは100mなので、どんなに頑張ってCPUを食わないように低リソース消費なコンテナを作ったととしても、デフォルトで100mは要求してしまい、Kube-systemを除くと実際は1ノードに最大10個までしか起動できないことになる。もったいない。そのPodが実際にどのくらい使うかは起動時点ではわからないから仕方ないかもしれない。

本当に必要なCPUを確認する

それぞれのpodがどれくらいリソースを使っているかは、稼働中のクラスタでkubectl top podで取得できる

% kc top pod
NAME                                 CPU(cores)   MEMORY(bytes)
arms-2064021732-p2153                30m          28Mi
arms-2064021732-8t1rm                26m          29Mi
arms-2064021732-v0q31                27m          29Mi
gopub-1201051946-x5twq               5m           7Mi
gopub-1201051946-f4gb6               5m           7Mi
redis-pd-2603876801-mbhm5            5m           22Mi
r2bq-3505699855-87rvn                41m          17Mi
rabbitmq-1915863861-0zdxc            1m           53Mi
revelation-2115532661-kl99b          9m           36Mi
revelation-worker-4162157242-m6bf9   3m           83Mi
table-manager-3441943665-xtbhp       4m           16Mi

午後4時に取ったので、ちょっとピークからは外れるけど、中には5m程度のpodもあり、すべてに100mをリクエストしておくのは明らかに無駄っぽい。

CPUの設定を反映する

とりあえず、余裕をもって実使用量の2~4倍程度を設定してみる。
リミットではなく、リクエストなので超えても全体で空いていれば止まることはないはず。

どこで設定したかは上でも貼ったページにパスと値をどう書けば書いてあるので、参考にしていじる。
Managing Compute Resources for Containers - Kubernetes

spec.containers[].resources.requests.cpu

まずCPU使用量が少ないgopubのdeployを編集

kc edit deploy gopub

下記ページを参考に追加する
Configure Default CPU Requests and Limits for a Namespace - Kubernetes

spec:
  replicas: 2
  selector:
    matchLabels:
      run: gopub
  strategy:
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 1
    type: RollingUpdate
  template:
    metadata:
      creationTimestamp: null
      labels:
        run: gopub
    spec:
      containers:
        image: asia.gcr.io/***
        imagePullPolicy: IfNotPresent
        name: gopub
        ports:
        - containerPort: 8765
          protocol: TCP
        resources:
          requests:
            cpu: 20m

とりあえず他にもいくつか追加して再びkc describe nodeをして変わっているか確認

% kc describe node
Non-terminated Pods:         (4 in total)
  Namespace                  Name                                                             CPU Requests  CPU Limits  Memory Requests  Memory Limits
  ---------                  ----                                                             ------------  ----------  ---------------  -------------
  default                    arms-723529091-2bgxz                                             50m (5%)      0 (0%)      0 (0%)           0 (0%)
  default                    gopub-1287380341-nt718                                           20m (2%)      0 (0%)      0 (0%)           0 (0%)
  kube-system                fluentd-gcp-v2.0.9-kb368                                         100m (10%)    0 (0%)      200Mi (14%)      300Mi (21%)
  kube-system                kube-proxy-gke-oceanus-asia-northeast1-c-pool-1-7f0c786d-kdfs    100m (10%)    0 (0%)      0 (0%)           0 (0%)

armsとgopubが100mから50m、20mになっているのが確認できた。

おそらくこれでCPU Request分不足によるpod立ち上げ失敗はなくなるはず。
一時的に負荷が上がりそうなpodは100mのままにして、本番で動かしながら様子を見る。