本节先讲解Pod的两个重要参数:CPU Request与Memory Request。在大多数情况下,我们在定义Pod时并没有定义这两个参数,此时Kubernetes会认为该Pod所需的资源很少,并可以将其调度到任何可用的Node上。这样一来,当集群中的计算资源不很充足时,如果集群中的Pod负载突然加大,就会使某个Node的资源严重不足。
为了避免系统挂掉,该Node会选择“清理”某些Pod来释放资源,此时每个Pod都可能成为牺牲品。但有些Pod担负着更重要的职责,比其他Pod更重要,比如与数据存储相关的、与登录相关的、与查询余额相关的,即使系统资源严重不足,也需要保障这些Pod的存活,Kubernetes中该保障机制的核心如下。
◎ 通过资源限额来确保不同的Pod只能占用指定的资源。
◎ 允许集群的资源被超额分配,以提高集群的资源利用率。
◎ 为Pod划分等级,确保不同等级的Pod有不同的服务质量(QoS),资源不足时,低等级的Pod会被清理,以确保高等级的Pod稳定运行
Kubernetes集群里的节点提供的资源主要是计算资源,计算资源是可计量的能被申请、分配和使用的基础资源,这使之区别于API资源(API Resources,例如Pod和Services等)。当前Kubernetes集群中的计算资源主要包括CPU、GPU及Memory,绝大多数常规应用是用不到GPU的,因此这里重点介绍CPU与Memory的资源管理问题。
CPU与Memory是被Pod使用的,因此在配置Pod时可以通过参数CPU Request及Memory Request为其中的每个容器指定所需使用的CPU与Memory量,Kubernetes会根据Request的值去查找有足够资源的Node来调度此Pod,如果没有,则调度失败。
我们知道,一个程序所使用的CPU与Memory是一个动态的量,确切地说,是一个范围,跟它的负载密切相关:负载增加时,CPU和Memory的使用量也会增加。因此最准确的说法是,某个进程的CPU使用量为0.1个CPU~1个CPU,内存占用则为500MB~1GB。对应到Kubernetes的Pod容器上,就是下面这4个参数:
◎ spec.container[].resources.requests.cpu;
◎ spec.container[].resources.limits.cpu;
◎ spec.container[].resources.requests.memory;
◎ spec.container[].resources.limits.memory。
其中,limits对应资源量的上限,即最多允许使用这个上限的资源量。由于CPU资源是可压缩的,进程无论如何也不可能突破上限,因此设置起来比较容易。对于Memory这种不可压缩资源来说,它的Limit设置就是一个问题了,如果设置得小了,当进程在业务繁忙期试图请求超过Limit限制的Memory时,此进程就会被Kubernetes杀掉。因此,Memory的Request与Limit的值需要结合进程的实际需求谨慎设置。如果不设置CPU或Memory的Limit值,会怎样呢?在这种情况下,该Pod的资源使用量有一个弹性范围,我们不用绞尽脑汁去思考这两个Limit的合理值,但问题也来了,考虑下面的例子:
Pod A的 Memory Request被设置为1GB,Node A当时空闲的Memory为1.2GB,符合Pod A的需求,因此Pod A被调度到Node A上。运行3天后,Pod A的访问请求大增,内存需要增加到1.5GB,此时Node A的剩余内存只有200MB,由于Pod A新增的内存已经超出系统资源,所以在这种情况下,Pod A就会被Kubernetes杀掉。
没有设置Limit的Pod,或者只设置了CPU Limit或者Memory Limit两者之一的Pod,表面看都是很有弹性的,但实际上,相对于4个参数都被设置的Pod,是处于一种相对不稳定的状态的,它们与4个参数都没设置的Pod相比,只是稳定一点而已。理解了这一点,就很容易理解Resource QoS问题了。
如果我们有成百上千个不同的Pod,那么先手动设置每个Pod的这4个参数,再检查并确保这些参数的设置,都是合理的。比如不能出现内存超过2GB或者CPU占据2个核心的Pod。最后还得手工检查不同租户(Namespace)下的Pod的资源使用量是否超过限额。为此,Kubernetes提供了另外两个相关对象:LimitRange及ResourceQuota,前者解决request与limit参数的默认值和合法取值范围等问题,后者则解决约束租户的资源配额问题。
本章从计算资源管理(Compute Resources)、服务质量管理(QoS)、资源配额管理(LimitRange、ResourceQuota)等方面,对Kubernetes集群内的资源管理进行详细说明,并结合实践操作、常见问题分析和一个完整的示例,力求对Kubernetes集群资源管理相关的运维工作提供指导。
计算资源管理
1.详解Requests和Limits参数
尽管Requests和Limits只能被设置到容器上,但是设置Pod级别的Requests和Limits能大大提高管理Pod的便利性和灵活性,因此在Kubernetes中提供了对Pod级别的Requests和Limits的配置。对于CPU和内存而言,Pod的Requests或Limits是指该Pod中所有容器的Requests或Limits的总和(对于Pod中没有设置Requests或Limits的容器,该项的值被当作0或者按照集群配置的默认值来计算)。下面对CPU和内存这两种计算资源的特点进行说明。
1 ) CPU
CPU的Requests和Limits是通过CPU数(cpus)来度量的。CPU的资源值是绝对值,而不是相对值,比如0.1CPU在单核或多核机器上是一样的,都严格等于0.1 CPU core。
2 ) Memory
内存的Requests和Limits计量单位是字节数。使用整数或者定点整数加上国际单位制(International System of Units)来表示内存值。国际单位制包括十进制的E、P、T、G、M、K、m,或二进制的Ei、Pi、Ti、Gi、Mi、Ki。KiB与MiB是以二进制表示的字节单位,常见的KB与MB则是以十进制表示的字节单位,比如:
◎ 1 KB(KiloByte)= 1000 Bytes = 8000 Bits;
◎ 1 KiB(KibiByte)= 210 Bytes = 1024 Bytes = 8192 Bits。
因此,128974848、129e6、129M、123Mi的内存配置是一样的。
以某个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 1apiVersion: v1
2kind: Pod
3metadata:
4 name: frontend
5spec:
6 continers:
7 - name: db
8 image: mysql
9 resources:
10 requests:
11 memory: "64Mi"
12 cpu: "250m"
13 limits:
14 memory: "128Mi"
15 cpu: "500m"
16 - name: wp
17 image: wordpress
18 resources:
19 requests:
20 memory: "64Mi"
21 cpu: "250m"
22 limits:
23 memory: "128Mi"
24 cpu: "500m"
25
如上所示,该Pod包含两个容器,每个容器配置的Requests都是0.25CPU和64MiB(226 Bytes)内存,而配置的Limits都是0.5CPU和128MiB(227 Bytes)内存。
这个Pod的Requests和Limits等于Pod中所有容器对应配置的总和,所以Pod的Requests是0.5CPU和128MiB(2^27Bytes)内存,Limits是1CPU和256MiB(2^28 Bytes)内存。
2.基于Requests和Limits的Pod调度机制
当一个Pod创建成功时,Kubernetes调度器(Scheduler)会为该Pod选择一个节点来执行。对于每种计算资源(CPU和Memory)而言,每个节点都有一个能用于运行Pod的最大容量值。调度器在调度时,首先要确保调度后该节点上所有Pod的CPU和内存的Requests总和,不超过该节点能提供给Pod使用的CPU和Memory的最大容量值。
例如,某个节点上的CPU资源充足,而内存为4GB,其中3GB可以运行Pod,而某Pod的Memory Requests为1GB、Limits为2GB,那么在这个节点上最多可以运行3个这样的Pod。
这里需要注意:可能某节点上的实际资源使用量非常低,但是已运行Pod配置的Requests值的总和非常高,再加上需要调度的Pod的Requests值,会超过该节点提供给Pod的资源容量上限,这时Kubernetes仍然不会将Pod调度到该节点上。如果Kubernetes将Pod调度到该节点上,之后该节点上运行的Pod又面临服务峰值等情况,就可能导致Pod资源短缺。
接着上面的例子,假设该节点已经启动3个Pod实例,而这3个Pod的实际内存使用都不足500MB,那么理论上该节点的可用内存应该大于 1.5GB。但是由于该节点的Pod Requests总和已经达到节点的可用内存上限,因此Kubernetes不会再将任何Pod实例调度到该节点上。
3.Requetes和Limits的背后机制
kubelet在启动Pod的某个容器时,会将容器的Requests和Limits值转化为相应的容器启动参数传递给容器执行器(Docker或者rkt)。
如果容器的执行环境是Docker,那么容器的如下4个参数是这样传递给Docker的。
1 ) spec.container[].resources.requests.cpu
这个参数会转化为core数(比如配置的100m会转化为0.1),然后乘以1024,再将这个结果作为–cpu-shares参数的值传递给docker run命令。在docker run命令中,–cpu-share参数是一个相对权重值(Relative Weight),这个相对权重值会决定Docker在资源竞争时分配给容器的资源比例。
举例说明–cpu-shares参数在Docker中的含义:比如将两个容器的CPU Requests分别设置为1和2,那么容器在docker run启动时对应的–cpu-shares参数值分别为1024和2048,在主机CPU资源产生竞争时,Docker会尝试按照1∶2的配比将CPU资源分配给这两个容器使用。
这里需要区分清楚的是:这个参数对于Kubernetes而言是绝对值,主要用于Kubernetes调度和管理;同时Kubernetes会将这个参数的值传递给docker run的–cpu-shares参数。–cpu-shares参数对于Docker而言是相对值,主要用于资源分配比例。
2 )spec.container[].resources.limits.cpu
这个参数会转化为millicore数(比如配置的1被转化为1000,而配置的100m被转化为100),将此值乘以100000,再除以1000,然后将结果值作为–cpu-quota参数的值传递给docker run命令。docker run命令中另外一个参数–cpu-period默认被设置为100000,表示Docker重新计量和分配CPU的使用时间间隔为100000μs(100ms)。
Docker的–cpu-quota参数和–cpu-period参数一起配合完成对容器CPU的使用限制:比如Kubernetes中配置容器的CPU Limits为0.1,那么计算后–cpu-quota为10000,而–cpu-period为100000,这意味着Docker在100ms内最多给该容器分配10ms×core的计算资源用量,10/100=0.1 core的结果与Kubernetes配置的意义是一致的。
注意:如果kubelet的启动参数–cpu-cfs-quota被设置为true,那么kubelet会强制要求所有Pod都必须配置CPU Limits(如果Pod没有配置,则集群提供了默认配置也可以)。从Kubernetes 1.2版本开始,这个–cpu-cfs-quota启动参数的默认值就是true。
3 )spec.container[].resources.requests.memory
这个参数值只提供给Kubernetes调度器作为调度和管理的依据,不会作为任何参数传递给Docker。
4 )spec.container[].resources.limits.memory
这个参数值会转化为单位为Bytes的整数,数值会作为–memory参数传递给docker run命令。
如果一个容器在运行过程中使用了超出了其内存Limits配置的内存限制值,那么它可能会被杀掉,如果这个容器是一个可重启的容器,那么之后它会被kubelet重新启动。因此对容器的Limits配置需要进行准确测试和评估。
与内存Limits不同的是,CPU在容器技术中属于可压缩资源,因此对CPU的Limits配置一般不会因为偶然超标使用而导致容器被系统杀掉。
4.计算资源使用情况监控
Pod的资源用量会作为Pod的状态信息一同上报给Master。如果在集群中配置了Heapster来监控集群的性能数据,那么还可以从Heapster中查看Pod的资源用量信息。
小结:
本节内容到此结束,明天我们接着讲解!
谢谢大家!