学习笔记 K8S Managing Compute Resources for Containers

Pod作为Kubernetes上的基本的执行单元,在运行的时候可以设置Memory和CPU资源下限和上限。这篇学习笔记是记录如何进行这方面的设置,以及一些相关的概念。

Limit Ranges

默认情况下,Pod/Container是没有compute resources的限制的。如果想要为某个namespace下的container增加一个默认的限制,就需要定义一个Limit Ranges.

  1. Limit Ranges会应用到一个namespace下所有的Pod/Container,会使其资源限制在request和limit之间。
  2. Limit Ranges强制PersistentVolumeClaim (PVC)的最大最小值
  3. Limit Ranges设置的是默认值。如果Container要修改这些限制,就要overwrite掉这个值。

修改默认的Limit Ranges

首先看看一个默认情况下Pod在运行时候的状态。默认情况下是没有CPU 和 Memory的限制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
sonic@k8smaster:~/k8syaml$ kubectl describe po default-mem-demo  -n limitrange-test
Name: default-mem-demo
Namespace: limitrange-test
Priority: 0
Node: k8snode1/10.190.155.28
Start Time: Mon, 13 Apr 2020 14:55:10 +0000
Labels: <none>
Annotations: <none>
Status: Running
IP: 10.244.2.30
IPs:
IP: 10.244.2.30
Containers:
default-memo-demo-ctr:
Container ID: docker://a45a34ed450fd1cdbac7d4f69f63ee769c03145b5a58d64d49ac1ebfb5300423
Image: nginx
Image ID: docker-pullable://nginx@sha256:282530fcb7cd19f3848c7b611043f82ae4be3781cb00105a1d593d7e6286b596
Port: <none>
Host Port: <none>
State: Running
Started: Mon, 13 Apr 2020 14:55:20 +0000
Ready: True
Restart Count: 0
Environment: <none>
Mounts:
/var/run/secrets/kubernetes.io/serviceaccount from default-token-n6vnt (ro)
Conditions:
Type Status
Initialized True
Ready True
ContainersReady True
PodScheduled True
Volumes:
default-token-n6vnt:
Type: Secret (a volume populated by a Secret)
SecretName: default-token-n6vnt
Optional: false
QoS Class: BestEffort
Node-Selectors: <none>
Tolerations: node.kubernetes.io/not-ready:NoExecute for 300s
node.kubernetes.io/unreachable:NoExecute for 300s
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled <unknown> default-scheduler Successfully assigned limitrange-test/default-mem-demo to k8snode1
Normal Pulling 118s kubelet, k8snode1 Pulling image "nginx"
Normal Pulled 113s kubelet, k8snode1 Successfully pulled image "nginx"
Normal Created 112s kubelet, k8snode1 Created container default-memo-demo-ctr
Normal Started 111s kubelet, k8snode1 Started container default-memo-demo-ctr

下面是定义一个默认的Limit Ranges, 并且将这个Limit Ranges应用到这个namespace下。再看看会发生什么变化。
这里的Limit Range定义了最低memory为256M,CPU为单核的10%。上限为Memory = 512MB, CPU为单核的100%。

1
2
3
4
5
6
7
8
9
10
11
12
13
apiVersion: v1
kind: LimitRange
metadata:
name: mem-limit-range
spec:
limits:
- default:
memory: 512Mi
cpu: "1.0"
defaultRequest:
memory: 256Mi
cpu: "0.1"
type: Container
1
2
sonic@k8smaster:~/k8syaml$ kubectl apply -f memory-defaults.yaml -n limitrange-test
limitrange/mem-limit-range created

接下来重新创建刚才的Pod, 情况就会发生变化。可以看到这个Pod已经出现了Limit和Request的设置。

img

Overwrite Limit Ranges

同一个namespace下如果已经有了Limit Ranges, 那么创建的Pod下面没有针对Limit和Request的定义时就会采用Limit Ranges的默认值。Pod的定义里面可以显示的overwrite。例如下面的例子。将Limits提升到了1GB.

1
2
3
4
5
6
7
8
9
10
11
apiVersion: v1
kind: Pod
metadata:
name: default-mem-demo-2
spec:
containers:
- name: default-mem-demo-2-ctr
image: nginx
resources:
limits:
memory: "1Gi"

这个时候发现Pod的limit.memory被提升到了1GB.其他值并没有变化。
img

如果单独修改Requests,并且在不超过limit的情况下,request也会做相应的调整。
但是如果requests超过limit的限制,则会出现一个错误。例如下面的实例中,设定requests为1GB,好过了limit:512M的限制。那么会出现错 Invalid value: "1Gi": must be less than or equal to memory limit.

1
2
3
4
5
6
7
8
9
10
11
apiVersion: v1
kind: Pod
metadata:
name: default-mem-demo-2
spec:
containers:
- name: default-mem-demo-2-ctr
image: nginx
resources:
requests:
memory: "1Gi"
1
2
sonic@k8smaster:~/k8syaml$ kubectl create -f memory-defaults-pod-3.yaml -n limitrange-test
The Pod "default-mem-demo-2" is invalid: spec.containers[0].resources.requests: Invalid value: "1Gi": must be less than or equal to memory limit

Requests vs. Limits

资源的Requests和Limits这两个属性分别代表着不同的意思。

  1. Requests: Pod或Container使用的最小资源需求。这个是作为Container调度资源分配的判断依据。只有当node上可分配资源量>=Requests的要求时才允许将Container调度到该节点。如果分配资源量 < Requests的要求,创建可能失败,或者可能被驱逐。
  2. Limits: Pod或Container使用的最大资源需求。

Requests的意义在于确保Pod/Container的最小工作资源。假设Node的资源非常的丰富,无论何时都可以 保证memory和CPU的使用,对于Pod/Container来说非常富足。这种情况下,Requests是没有太多的意义的。但是反过来,如果memory/CPU的情况非常吃紧。要让Pod/Container能够良好的工作,必须要保证memory/CPU维持在某个合理值之上,这个时候可以通过设置Requests来达到要求。

Limits的意义在于确保Pod/Container使用的资源memory/CPU维持在给定值之下。将Node上的资源留给其他的Node来使用,确保其他Pod/Container的正常运行。如果超过Limit,将有可能遇到被中止、节流、驱逐等风险。

Min && Max

在Limit Ranges中还可用设置Memory和CPU的最大值和最小值。在创建好Limit Ranges中的Min & Max之后,创建容器时Kubernetes 就会执行下面的步骤:

  1. 如果 Container 未指定自己的内存请求和限制,将为它指定默认的Memory/CPU的Requests和Limits。
  2. 验证 Container 的Memory/CPU Request是否 >= Min。
  3. 验证 Container 的Memory/CPU Limit是否 <= Max。

Memory

内存的Limits和Requests以bytes为单位。可以使用以下后缀之一作为平均整数或定点整数表示内存:E,P,T,G,M,K。您还可以使用两个字母的等效的幂数:Ei,Pi,Ti ,Gi,Mi,Ki。例如,1G = 1,000,000,000 或者 1Gi = 1GB = 1024 * 1024 * 1024.

Kubernetes scheduler会选择一个合适Node来运行这个Pod. 当Requests必须要低于Limits,否则显示下面错误:

1
2
sonic@k8smaster:~/k8syaml$ kubectl create -f memory-defaults-pod-4.yaml -n limitrange-test
The Pod "default-mem-demo-4" is invalid: spec.containers[0].resources.requests: Invalid value: "4Gi": must be less than or equal to memory limit

Requests也不能设置太高。如果没有任何一个node达到资源要求,Pod的创建将会发生问题。比如下面这个例子,将Requests设定在32GB,没有任何一个node有这么高的可用memory, 出现下面的错误0/3 nodes are available: 3 Insufficient memory.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
sonic@k8smaster:~/k8syaml$ kubectl describe -f memory-defaults-pod-5.yaml -n limitrange-test
Name: default-mem-demo-5
Namespace: limitrange-test
Priority: 0
Node: <none>
Labels: <none>
Annotations: kubernetes.io/limit-ranger:
LimitRanger plugin set: cpu request for container default-mem-demo-5-ctr; cpu limit for container default-mem-demo-5-ctr
Status: Pending
IP:
IPs: <none>
Containers:
default-mem-demo-5-ctr:
Image: nginx
Port: <none>
Host Port: <none>
Limits:
cpu: 1
memory: 64Gi
Requests:
cpu: 100m
memory: 32Gi
Environment: <none>
Mounts:
/var/run/secrets/kubernetes.io/serviceaccount from default-token-n6vnt (ro)
Conditions:
Type Status
PodScheduled False
Volumes:
default-token-n6vnt:
Type: Secret (a volume populated by a Secret)
SecretName: default-token-n6vnt
Optional: false
QoS Class: Burstable
Node-Selectors: <none>
Tolerations: node.kubernetes.io/not-ready:NoExecute for 300s
node.kubernetes.io/unreachable:NoExecute for 300s
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling <unknown> default-scheduler 0/3 nodes are available: 3 Insufficient memory.
Warning FailedScheduling <unknown> default-scheduler 0/3 nodes are available: 3 Insufficient memory.

当Pod使用的memory超过上限的时候,Pod就会有被terminated的风险。会出现如下的错误exitCode:137.

1
2
3
4
5
6
7
lastState:
terminated:
containerID: docker://65183c1877aaec2e8427bc95609cc52677a454b56fcb24340dbd22917c23b10f
exitCode: 137
finishedAt: 2017-06-20T20:52:19Z
reason: OOMKilled
startedAt: null

CPU

这里设置的CPU等同于Node在操作系统层面所能提供的一个CPU的资源, 相当于 1 vCPU/Core。

CPU的Requests和Limits可以设定为部分CPU。例如0.1这样的情况。这里的小数部分是按照毫秒(millisecond)来进行计算。例如,0.1代表着 0.1 * 1000 ms = 100 ms. 以1s的CPU时间为例,将会分配到 100ms, 也就是单个CPU的10%的CPU时间。这个是一开始让我比较疑惑的地方。

例如下面的设置,代表着最大可用使用2个CPU。能运行2个CPU的使用率达到100%。如果这个node上有4个CPU的话,意味着最高能使用node上50%的CPU Usage.

1
2
3
limits:
- default:
cpu: "2.0"

例如下面的设置,代表着最大可用1个CPU的10%的使用率。如果这个node上有4个CPU的话,意味着更低的CPU Usage.

1
2
3
limits:
- default:
cpu: "0.1"

由于K8S上CPU资源是可用压缩的。当Pod/Container的CPU使用率超过了限制,POD/Container将会被节流(throttling)。而不是像memory那样被强制关闭。这一点跟memory的区别还是比较大的。

总结

  1. 默认情况下,Pod/Container是没有使用资源限制的,Memory,CPU随便用。
  2. 通过设定LimitRange能够在指定的namespace下设定资源限制。
  3. Limits用来设置上限,Requets用来指定下限。
  4. 达到Memory上限,Pod会被关闭。达到CPU上限,Pod会被节流。