Kubernetes是如何对资源(CPU、内存等)做限制的

Kubernetes对资源的限制

在Kubernetes中,对资源(CPU、内存等)的限制,需要定义在yaml中,以Deployment举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
apiVersion: v1
kind: Pod
metadata:
name: cpu-overload
namespace: test
spec:
containers:
- name: cpu-overload
image: stress/stress:latest
resources:
limits:
cpu: "2"
memory: 1000Mi
requests:
cpu: "1"
memory: 500Mi
command: ["stress"]
args: ["-c", "2"]

其中,CPU 有2个限制:

  1. requests:相对限制,是容器的最低申请资源,这个限制是相对的,无法做到绝对严格。
  2. limits:绝对限制,这个是限制的绝对的,不可能超越。

本例中,对容器 cpu-overload 的 CPU 的限制,是,申请1个核的运算资源,最多可以使用2个核。

这里需要特别说明一点,所谓的最多2个核,其实是均摊的,如果这个容器真的触发了计算瓶颈,在docker中看,CPU使用率是200%,但在宿主机去看,其实并非是将2个核占满了,而是将压力分摊到了多个CPU的核上。

对Kubernetes来说,只能做到限制容器资源,无法对pod资源做限制,Kubernetes官方认为,要计算一个pod的资源限制,将pod中各个容器的资源做加和就行了。这里不展示详细说,具体怎么为Kubernetes限制内存和CPU,可以直接参看官方文档:Managing Compute Resources for Containers ,这篇文章写的够详细了。

资源限制的传递

Kubernetes其实可以认为是一系列组件包装起来的一个大型工具。关于资源限制,其实Kubernetes自己做不了这些,而是将对资源限制,通过yaml中的定义,传递到Docker容器中。比如,之前我们在Deployment中容器的CPU,限制为最多使用2个核,这个限制,Kubernetes会传递给Docker来做,所以本质上,Kubernetes资源的限制能力,来源于Docker,而Docker能做到什么程度的限制,又取决于Linux的cgroups,所以在很早之前的Docker是不支持在Windows平台运行的,归根结底,还是因为cgroups是Linux内核支持的产物。

说了这么多,我们可以通过一个实例来说明这个传递性。在开始前,简单说一下步骤:

  1. 在Kubernetes中启动一个单独的pod,资源限制为最多4个CPU核。
  2. 找到这个pod对应的容器,看一下容器的运行配置,是不是限制了4个核。
  3. 找到这个容器对应的Cgroups配置,看是否对容器限制了4个核。

实验

首先,创建一个限制了1个核的pod

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
apiVersion: v1
kind: Pod
metadata:
labels:
testsss: cadvisor-test
name: cadvisor-test
namespace: test
spec:
containers:
- args:
- -allow_dynamic_housekeeping=true
- -global_housekeeping_interval=1m0s
- -housekeeping_interval=5s
- -disable_metrics=udp,tcp,percpu,sched,disk,network
- -storage_duration=15s
- -profiling=true
- -enable_load_reader=true
- -port=30008
- -max_procs=1
image: google/cadvisor
imagePullPolicy: IfNotPresent
name: cadvisor-test
ports:
- containerPort: 30008
hostPort: 30008
name: http
protocol: TCP
resources:
limits:
cpu: "1"
memory: 2000Mi
requests:
cpu: "0.1"
memory: 100Mi
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
volumeMounts:
- mountPath: /rootfs
name: rootfs
readOnly: true
- mountPath: /var/run
name: var-run
readOnly: true
- mountPath: /sys
name: sys
readOnly: true
- mountPath: /var/lib/docker
name: docker
readOnly: true
- mountPath: /dev/disk
name: disk
readOnly: true
dnsPolicy: ClusterFirst
hostNetwork: true
nodeName: nodexxx
restartPolicy: Always
volumes:
- hostPath:
path: /
type: ""
name: rootfs
- hostPath:
path: /var/run
type: ""
name: var-run
- hostPath:
path: /sys
type: ""
name: sys
- hostPath:
path: /DATA/docker
type: ""
name: docker
- hostPath:
path: /dev/disk
name: disk

在这个yaml中,我们队cAdvisor容器,限制为1核2G内存。我们通过

1
kubectl apply -f cadvisor-pod.yaml

将pod运行起来。

查看容器的运行时限制

运行为容器后,查看此pod所在节点,进入到节点,找到这个容器,通过下面指令查看此容器的运行时配置

1
docker inspect 6f9ecad83132

然后,从一大堆输出中,找到下面的重点部分:

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
"Isolation": "",
"CpuShares": 102,
"Memory": 2097152000,
"NanoCpus": 0,
"CgroupParent": "/kubepods/burstable/pod1fb8b728-dbed-11e8-a89e-801844e1f8c8",
"BlkioWeight": 0,
"BlkioWeightDevice": null,
"BlkioDeviceReadBps": null,
"BlkioDeviceWriteBps": null,
"BlkioDeviceReadIOps": null,
"BlkioDeviceWriteIOps": null,
"CpuPeriod": 100000,
"CpuQuota": 100000,
"CpuRealtimePeriod": 0,
"CpuRealtimeRuntime": 0,
"CpusetCpus": "",
"CpusetMems": "",
"Devices": [],
"DeviceCgroupRules": null,
"DiskQuota": 0,
"KernelMemory": 0,
"MemoryReservation": 0,
"MemorySwap": 4194304000,
"MemorySwappiness": -1,
"OomKillDisable": false,
"PidsLimit": 0,
"Ulimits": null,
"CpuCount": 0,
"CpuPercent": 0,

其中:

  • Memory:限制内存资源,单位为byte,2097152000 = 2G
  • CpuShares:CPU使用的相对权重,一个核为1024,我们设置的request cpu为 0.1 ,所以就是 102
  • CpuPeriod:一个CPU为100000,也就是100个milicpu,这个一般不需要改。
  • CpuQuota:CPU资源的绝对限制,一般和CpuPeriod结合在一起,CpuQuota/CpuPeriod,就是能够使用的核数,100000/100000=1,表示我们能最多使用1个CPU核心。
  • CpusetCpus:这个值表示当前容器运行时,绑定到哪几个CPU编号上,注意:这个不是CPU个数,而是绑定到哪几个CPU上,多个CPU编号用逗号分割。

从上面的docker运行时限制看,和Kubernetes的Pod的定义完全吻合。下面再看Cgroups的限制,这才是核心。

根据容器,查Cgroups的限制内容

首先,我们看一下pod的名称:

1
2
[root@nodexx test]# kubectl get pod -n test|grep cadvisor
cadvisor-test 1/1 Running 0 14h

然后,在pod所在的宿主机,找到这个pod对应的容器Id

1
2
3
4
5
6
docker ps|grep cadvisor

//找到结果
[root@nodexx docker]# docker ps|grep cadvisor
6f9ecad83132 google/cadvisor "/usr/bin/cadvisor..." 15 hours ago Up 15 hours k8s_cadvisor-test_cadvisor-test_test_1fb8b728-dbed-11e8-a89e-801844e1f8c8_0
139259f9a21c gcr.io/google_containers/pause-amd64:3.0 "/pause" 15 hours ago Up 15 hours

我们可以注意到,一个匹配出来2个容器,一个是 cadvisor 容器,一个是pause容器,pause容器,是 Kubernetes pod 的基础容器。我们只需要 cadivsor容器的Id:6f9ecad83132,我们要通过它拿到这个容器的Cgroup信息

1
2
3
4
# docker inspect 6f9ecad83132|grep Cgroup
"Cgroup": "",
"CgroupParent": "/kubepods/burstable/pod1fb8b728-dbed-11e8-a89e-801844e1f8c8",
"DeviceCgroupRules": null,

好了,完事具备,我们直接进入Cgroup配置目录:

1
cd /sys/fs/cgroup/cpu/kubepods/burstable/pod1fb8b728-dbed-11e8-a89e-801844e1f8c8

注意一下这个目录,是 /sys/fs/cgroup/cpu 与 /kubepods/burstable/pod1fb8b728-dbed-11e8-a89e-801844e1f8c8 拼接起来的一个整体路径。
在这个目录下,有很多文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
ll 
//
total 0
drwxr-xr-x 2 root root 0 Oct 30 18:14 139259f9a21cc26fb8b40b85b345566489fc3bedc1b36766b032abc60b93e702
drwxr-xr-x 2 root root 0 Oct 30 18:14 6f9ecad83132b77c7e0ce3bfe8e56b471c8c41b920970b3fdd6da4d158fb4b1e
-rw-r--r-- 1 root root 0 Oct 30 18:14 cgroup.clone_children
-rw-r--r-- 1 root root 0 Oct 30 18:14 cgroup.procs
-r--r--r-- 1 root root 0 Oct 30 18:14 cpuacct.stat
-rw-r--r-- 1 root root 0 Oct 30 18:14 cpuacct.usage
-r--r--r-- 1 root root 0 Oct 30 18:14 cpuacct.usage_all
-r--r--r-- 1 root root 0 Oct 30 18:14 cpuacct.usage_percpu
-r--r--r-- 1 root root 0 Oct 30 18:14 cpuacct.usage_percpu_sys
-r--r--r-- 1 root root 0 Oct 30 18:14 cpuacct.usage_percpu_user
-r--r--r-- 1 root root 0 Oct 30 18:14 cpuacct.usage_sys
-r--r--r-- 1 root root 0 Oct 30 18:14 cpuacct.usage_user
-rw-r--r-- 1 root root 0 Oct 30 10:40 cpu.cfs_period_us
-rw-r--r-- 1 root root 0 Oct 30 10:40 cpu.cfs_quota_us
-rw-r--r-- 1 root root 0 Oct 30 18:14 cpu.rt_period_us
-rw-r--r-- 1 root root 0 Oct 30 18:14 cpu.rt_runtime_us
-rw-r--r-- 1 root root 0 Oct 30 10:40 cpu.shares
-r--r--r-- 1 root root 0 Oct 30 18:14 cpu.stat
-rw-r--r-- 1 root root 0 Oct 30 18:14 notify_on_release
-rw-r--r-- 1 root root 0 Oct 30 18:14 tasks

其中,当前目录下有很多Cgroup内容,而有2个子目录:

1
2
139259f9a21cc26fb8b40b85b345566489fc3bedc1b36766b032abc60b93e702
6f9ecad83132b77c7e0ce3bfe8e56b471c8c41b920970b3fdd6da4d158fb4b1e

这2个目录,其实就是 pod 中的2个容器(pause容器,cadvisor-test容器),我们进入 cadvisor-test容器的目录下

1
cd 6f9ecad83132b77c7e0ce3bfe8e56b471c8c41b920970b3fdd6da4d158fb4b1e

为什么可以判定2个目录中,6f9ecad83132b77c7e0ce3bfe8e56b471c8c41b920970b3fdd6da4d158fb4b1e就是cadvisor-test容器目录呢?因为容器的ID,就是6f9ecad83132呀!

好了,查看下目录下的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
-rw-r--r-- 1 root root 0 Oct 30 18:15 cgroup.clone_children
-rw-r--r-- 1 root root 0 Oct 30 10:40 cgroup.procs
-r--r--r-- 1 root root 0 Oct 30 18:15 cpuacct.stat
-rw-r--r-- 1 root root 0 Oct 30 18:15 cpuacct.usage
-r--r--r-- 1 root root 0 Oct 30 18:15 cpuacct.usage_all
-r--r--r-- 1 root root 0 Oct 30 18:15 cpuacct.usage_percpu
-r--r--r-- 1 root root 0 Oct 30 18:15 cpuacct.usage_percpu_sys
-r--r--r-- 1 root root 0 Oct 30 18:15 cpuacct.usage_percpu_user
-r--r--r-- 1 root root 0 Oct 30 18:15 cpuacct.usage_sys
-r--r--r-- 1 root root 0 Oct 30 18:15 cpuacct.usage_user
-rw-r--r-- 1 root root 0 Oct 30 10:40 cpu.cfs_period_us
-rw-r--r-- 1 root root 0 Oct 30 10:40 cpu.cfs_quota_us
-rw-r--r-- 1 root root 0 Oct 30 18:15 cpu.rt_period_us
-rw-r--r-- 1 root root 0 Oct 30 18:15 cpu.rt_runtime_us
-rw-r--r-- 1 root root 0 Oct 30 10:40 cpu.shares
-r--r--r-- 1 root root 0 Oct 30 18:15 cpu.stat
-rw-r--r-- 1 root root 0 Oct 30 18:15 notify_on_release
-rw-r--r-- 1 root root 0 Oct 30 18:15 tasks

其中,能够看到好几个熟悉的词,比如 cpu.cfs_quota_us,这个正是对CPU资源做限制的。我们查看一下其内容:

1
2
[root@nodexxx # cat cpu.cfs_quota_us
100000

没错,这个值正是100000,也就是1个CPU。

Linux Cgroup 是如何与Dock而关联的?

上面的方式,已经层层找到了对CPU、内存等限制,是如何通过Kubernets的Deployment,一步步追查到Cgroup的。那么,Linux Cgroup,怎么与容器关联起来的呢?

我们看一个cgroup目录中的tasks

1
2
[root@nodexxx]# cat tasks 
21694

这个值,就是进程ID,所以,Cgroup对资源的限制,就是对进程ID来限制的。我们看一下这个进程ID

1
2
[root@nodexxx]# ps -ef|grep 21694
root 21694 21663 7 Oct30 ? 01:05:05 /usr/bin/cadvisor -logtostderr -allow_dynamic_housekeeping=true -global_housekeeping_interval=1m0s -housekeeping_interval=5s -disable_metrics=udp,tcp,percpu,sched,disk,network -storage_duration=15s -profiling=true -enable_load_reader=true -port=30008 -max_procs=1

此进程ID,正是cadvisor的进程ID,其实这个进程,是容器内的进程,换句话说,其父进程,肯定是一个容器进程:

1
2
3
[root@nodexxx]# ps -ef|grep 21663
root 21663 2567 0 Oct30 ? 00:00:00 docker-containerd-shim 6f9ecad83132b77c7e0ce3bfe8e56b471c8c41b920970b3fdd6da4d158fb4b1e /var/run/docker/libcontainerd/6f9ecad83132b77c7e0ce3bfe8e56b471c8c41b920970b3fdd6da4d158fb4b1e docker-runc
root 21694 21663 7 Oct30 ? 01:05:16 /usr/bin/cadvisor -logtostderr -allow_dynamic_housekeeping=true -global_housekeeping_interval=1m0s -housekeeping_interval=5s -disable_metrics=udp,tcp,percpu,sched,disk,network -storage_duration=15s -profiling=true -enable_load_reader=true -port=30008 -max_procs=1

总结

  • Kubernete对资源的限制,靠的是Docker,Docker对资源的限制,靠的是Linux Cgroup 。
  • Linux Cgroup 限制资源,是限制进程,只需要在Cgroup配置目录的tasks文件中,添加进程ID,限制立即生效。
  • Linux Cgroup 不仅仅可以限制CPU,内存,还可以限制磁盘IO等。
Donate comment here