8
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Kubernetes Pod の resources.limits.memory がいつのまにか 200PiB に設定されていた話

Last updated at Posted at 2023-11-10

この記事は Wantedly Advent Calendar 2023 の17日目の記事にしようと思ったけど早く書きすぎたので普通の記事です。

事の始まり

あるとき Kubernetes Node を見ていると、Node のメモリを大幅にオーバーコミットしている Pod を見つけました。以下は kubectl describe node したときの出力の一部です。

Non-terminated Pods:
  Namespace Name                          CPU Requests  CPU Limits  Memory Requests Memory Limits       Age
  --------- ----                          ------------  ----------  --------------- -------------       ---
  irotoris  my-deployment-85df655dc-7cvwq 150m (1%)     300m (3%)   1Gi (1%)        200Pi (344797407%)  3d12h

Memory Limits が 200Pi (344797407%) ってなに??

さすがにびっくりして調べることにしました。

Pod の設定を見てみる

Pod の設定を kubectl describe po で確認してみると、ただの nginx コンテナが動いているだけの Pod ですが、確かにメモリの limit が 200Pi になっています。CPU や メモリの requests は常識的な値です。

Name:             my-deployment-85df655dc-7cvwq
Namespace:        irotoris
Priority:         0
Service Account:  default
Node:             ip-10-3-240-126.ap-northeast-1.compute.internal/10.3.240.126
Start Time:       Tue, 31 Oct 2023 18:05:47 +0900
Labels:           app=my-app
                  pod-template-hash=85df655dc
Annotations:      kubectl.kubernetes.io/restartedAt: 2023-10-31T18:05:47+09:00
                  vpaObservedContainers: nginx
                  vpaUpdates: Pod resources updated by irotoris: container 0: cpu request, memory request, cpu limit, memory limit
Status:           Running
IP:               10.3.238.241
IPs:
  IP:           10.3.238.241
Controlled By:  ReplicaSet/my-deployment-85df655dc
Containers:
  nginx:
    Container ID:   containerd://e47974f78334fe254e2f5e1169f9b95aea21756ed2b25dcb6845bead0d99f8de
    Image:          public.ecr.aws/nginx/nginx:1.25
    Image ID:       public.ecr.aws/nginx/nginx@sha256:56fae3726ce208394da92a3ac447caacfccd59d8325f700b8542e6faf614cc4a
    Port:           80/TCP
    Host Port:      0/TCP
    State:          Running
      Started:      Tue, 31 Oct 2023 18:05:48 +0900
    Ready:          True
    Restart Count:  0
    Limits:
      cpu:     300m
      memory:  200Pi
    Requests:
      cpu:        150m
      memory:     1Gi
    Environment:  <none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-9p9gn (ro)
Conditions:
  Type              Status
  Initialized       True
  Ready             True
  ContainersReady   True
  PodScheduled      True
Volumes:
  kube-api-access-9p9gn:
    Type:                    Projected (a volume that contains injected data from multiple sources)
    TokenExpirationSeconds:  3607
    ConfigMapName:           kube-root-ca.crt
    ConfigMapOptional:       <nil>
    DownwardAPI:             true
QoS Class:                   Burstable
Node-Selectors:              <none>
Tolerations:                 node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
                             node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:                      <none>

この Pod の ReplicaSet を見に行くと、どうやら my-deployment という Deployment で管理されているようです。この Deployment の設定を見に行きましょう。

Deployment の設定を見てみる

Name:                   my-deployment
Namespace:              irotoris
CreationTimestamp:      Tue, 31 Oct 2023 17:43:32 +0900
Labels:                 app=my-app
Annotations:            deployment.kubernetes.io/revision: 3
Selector:               app=my-app
Replicas:               1 desired | 1 updated | 1 total | 1 available | 0 unavailable
StrategyType:           RollingUpdate
MinReadySeconds:        0
RollingUpdateStrategy:  25% max unavailable, 25% max surge
Pod Template:
  Labels:       app=my-app
  Annotations:  kubectl.kubernetes.io/restartedAt: 2023-10-31T18:05:47+09:00
  Containers:
   nginx:
    Image:      public.ecr.aws/nginx/nginx:1.25
    Port:       80/TCP
    Host Port:  0/TCP
    Limits:
      cpu:     200m
      memory:  200Mi
    Requests:
      cpu:        100m
      memory:     100m
    Environment:  <none>
    Mounts:       <none>
  Volumes:        <none>
Conditions:
  Type           Status  Reason
  ----           ------  ------
  Available      True    MinimumReplicasAvailable
  Progressing    True    NewReplicaSetAvailable
OldReplicaSets:  <none>
NewReplicaSet:   my-deployment-85df655dc (1/1 replicas created)
Events:          <none>

Deployment には 200Pi なんてメモリは設定されてないですね…。ん?resources.limits.memory 以外にも Deployment と Pod のリソースが全体的に少し違います。

Pod

    Limits:
      cpu:     300m
      memory:  200Pi
    Requests:
      cpu:        150m
      memory:     1Gi

Deployment

    Limits:
      cpu:     200m
      memory:  200Mi
    Requests:
      cpu:        100m
      memory:     100m

Pod の垂直オートスケーリング (VPA) が怪しそう

Kubernetes において Pod の resource をいい感じにしてくれるものと言えば、Pod の垂直オートスケーリングを可能にする VerticalPodAutoscaler というものがあります。そういえばこのクラスタにもインストールしていました。今思い出しました。

Pod の設定をよく見ると annotation に vpaUpdates: Pod resources updated by irotoris: container 0: cpu request, memory request, cpu limit, memory limit というものがあります。この VerticalPodAutoscaler (VPA) のリソースを探しに行きます。

❯ kubectl -n irotoris describe vpa
Name:         irotoris
Namespace:    irotoris
Labels:       <none>
Annotations:  <none>
API Version:  autoscaling.k8s.io/v1
Kind:         VerticalPodAutoscaler
Metadata:
  Creation Timestamp:  2023-10-31T08:40:29Z
  Generation:          114
  Resource Version:    1237148614
  UID:                 47654dab-fbe0-4139-a937-d0b93bcaec9c
Spec:
  Resource Policy:
    Container Policies:
      Container Name:  nginx
      Min Allowed:
        Cpu:     150m
        Memory:  1Gi
      Mode:      Auto
  Target Ref:
    API Version:  apps/v1
    Kind:         Deployment
    Name:         my-deployment
  Update Policy:
    Update Mode:  Initial
Status:
  Conditions:
    Last Transition Time:  2023-10-31T08:44:28Z
    Status:                True
    Type:                  RecommendationProvided
  Recommendation:
    Container Recommendations:
      Container Name:  nginx
      Lower Bound:
        Cpu:     150m
        Memory:  1Gi
      Target:
        Cpu:     150m
        Memory:  1Gi
      Uncapped Target:
        Cpu:     25m
        Memory:  262144k
      Upper Bound:
        Cpu:     150m
        Memory:  1Gi
Events:          <none>

ありました。どうやら nginx コンテナの resources.requests の下限 (Min Allowed) を CPU 150m Memory 1Gi として Pod のリソースをスケールアップしているようです。Mode: AutoUpdate Mode: Initial になっているので、Pod の作成時にのみこれまでのリソース使用状況を見てスケールされています。

現状この Pod へのアクセスは少なく、リソースもそこまで使用していなかったので resources.requests が VPA の Min Allowed までスケールされています。

では VPA において resources.limits はどうスケールされるのでしょうか?ドキュメントを読んでみます。

VPA will try to cap recommendations between min and max of limit ranges. If limit range conflicts and VPA resource policy conflict, VPA will follow VPA policy (and set values outside the limit range).

なるほど。Limit Ranges や VPA Policy と設定がかち合わない限り、もともとの requests と limit の幅を維持してスケールするようですね。

CPU だと、もともとの Deployment の requests が 100m で Pod が 150m へスケールしてるので、1.5倍 の変更です。limit はもともと 200m なのでその 1.5倍の 300m になります。

ではメモリはというと、もともとの Deployment の requests が 100m で…

100m…? 0.1 Byte ってこと…?

0.1 Byte が 1GiB までスケールするのでその倍率は… 1GiB は 1073741824 Byte だから…

約107億4千万倍!!!!

limit の方はもともと 200Mi なのでその107億4千万倍は…

2000PiB!!!!!!!

limits.memory がとんでもない数値になっていたのは VPA がスケールさせていたからでした。メモリの requests が 100m なのは 100Mi の間違いないのか、オーバーコミットのために 0 以外を設定しようとしたのかはもはや覚えていませんが、Kubernetes でメモリ設定する時に間違えて lower case で m と書いて問題になることはよくあるらしく、Warining が表示されるようになっていました。

犯人はわかったが計算が合わない

ところで現状 Pod には 200Pi が設定されているので、理論上 2000Pi が設定されるのだとすると 0 が一個足りません。なんででしょう。考えてみます。

まず Kubernetes で設定できるメモリの最大数はどれくらいなんでしょう?

試しに Pod に 10EiB のメモリを limit に指定して apply したところ、実際には 9223372036854775807(=8EiB) が設定されました。

apiVersion: v1
kind: Pod
metadata:
  name: my-pod
  namespace: irotoris
spec:
  containers:
  - image: public.ecr.aws/nginx/nginx:1.25
    name: nginx
    ports:
    - containerPort: 80
      protocol: TCP
    resources:
      limits:
        cpu: 300m
        memory: 10Ei
      requests:
        cpu: 100m
        memory: 50Mi
❯ kubectl -n irotoris describe po my-pod
Name:             my-pod

...(省略)...

    Limits:
      cpu:     300m
      memory:  9223372036854775807
    Requests:
      cpu:        100m
      memory:     50Mi

...(省略)...

これはメモリ設定が Kubernetes 内部の Go において int64 で表現されるためで、math.MaxInt64 が上限になっていると思われます。つまり符号付き64ビット整数の最大値である2の63乗-1の 9223372036854775807 です。

Kubernetes で設定できる limits.memory は 2000PiB 以上ということがわかりました。ということは VPA の方がなにかしてるのでしょうか?

VPA で limits.memory を計算している箇所はここです。今更ですが、筆者の環境では EKS 1.26, VPA 0.14 を使っています。

func scaleQuantityProportionallyMem(scaledQuantity, scaleBase, scaleResult *resource.Quantity, rounding roundingMode) (*resource.Quantity, bool) {
	originalValue := big.NewInt(scaledQuantity.Value())
	scaleBaseValue := big.NewInt(scaleBase.Value())
	scaleResultValue := big.NewInt(scaleResult.Value())
	var scaledOriginal big.Int
	scaledOriginal.Mul(originalValue, scaleResultValue)
	scaledOriginal.Div(&scaledOriginal, scaleBaseValue)
	if scaledOriginal.IsInt64() {
		result := resource.NewQuantity(scaledOriginal.Int64(), scaledQuantity.Format)
		if rounding == roundUpToFullUnit {
			result.RoundUp(resource.Scale(0))
		}
		if rounding == roundDownToFullUnit {
			result.Sub(*resource.NewMilliQuantity(999, result.Format))
			result.RoundUp(resource.Scale(0))
		}
		return result, false
	}
	return resource.NewQuantity(math.MaxInt64, scaledQuantity.Format), true
}

うーんどうやら big.NewInt(scaleBase.Value()) しているときに 100m1 に丸まってる気配を感じます。動かして確認してみます。

package main

import (
	"fmt"
	"math/big"

	"k8s.io/apimachinery/pkg/api/resource"
)

func main() {
	mem := resource.MustParse("100m")
	memValue := big.NewInt(mem.Value())
	fmt.Printf("%v\n", mem)         // {{100 -3} {<nil>} 100m DecimalSI}
	fmt.Printf("%v\n", mem.Value()) // 1
	fmt.Printf("%v\n", memValue)    // 1
}

こいつですね。

func (q *Quantity) Value() int64
Value returns the unscaled value of q rounded up to the nearest integer away from 0.

limits.memory を計算する際に もともとの requests.memory である 100m(=0.1) が、VPA の内部では 1 と扱われてしまい、そこから VPA が limits.memory を計算したもんだから 2000Pi となるところが 200Pi となり、0 が一個足りない状態になっていると思われます。

まとめ

ということで Kubernetes Pod の resources.limits.memory がいつのまにか 200PiB に設定されていた話は、Podの resources.requests.memory が非常に小さい値(100m)とされていたため、VerticalPodAutoscaler(VPA)がこの値を基に(仕様通りに)自動的にスケーリングを行った結果でした。

謎が解けてスッキリしました。

8
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?