047.Kubernetes集群管理-资源及配额管理
一 资源管理
1.1 资源调度机制
对于Kubernetes资源,有两个重要参数:CPU Request与Memory Request。
通常在定义Pod时并没有定义这两个参数,此时Kubernetes会认为该Pod所需的资源很少,并可以将其调度到任何可用的Node上。因此,当集群中的计算资源不很充足时,如果集群中的Pod负载突然增大,就会使某个Node的资源严重不足。为了避免Node系统挂掉,该Node会选择“清理”某些Pod来释放资源,此时每个Pod都可能被“清理”。若其中某些Pod非常重要,比如与数据存储相关的、与登录相关的、与查询余额相关的,需要在系统资源严重不足时,也得保障这些Pod的存活。
Kubernetes中该保障机制的核心如下:
- 通过资源限额来确保不同的Pod只能占用指定的资源。
- 允许集群的资源被超额分配,以提高集群的资源利用率。
- 为Pod划分等级,确保不同等级的Pod有不同的服务质量(QoS),资源不足时,低等级的Pod会被清理,以确保高等级的Pod稳定运行。
Kubernetes集群里的节点提供的资源主要是计算资源,计算资源是可计量的能被申请、分配和使用的基础资源,这使之区别于API资源(API Resources,例如Pod和Services等)。
当前Kubernetes集群中的计算资源主要包括CPU、GPU及Memory,通常绝大多数常规应用不需要GPU资源。CPU与Memory是被Pod使用的,因此在配置Pod时可以通过参数CPU Request及Memory Request为其中的每个容器指定所需使用的CPU与Memory量,Kubernetes会根据Request的值去查找有足够资源的Node来调度此Pod,如果没有相应的Node能满足,则调度失败。
通常,一个程序所使用的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限制的Memory时,此进程就会被Kubernetes杀掉。因此,Memory的Request与Limit的值需要结合进程的实际需求谨慎设置。
1.2 批量设置
若存在成百上千个不同的Pod,那么先手动设置每个Pod的这4个参数,再检查并确保这些参数的设置是否合理。比如不能出现内存超过2GB或者CPU占据2个核心的Pod。最后需要手工检查不同租户(Namespace)下的Pod的资源使用量是否超过限额。
若上设置相对繁琐复杂,为此,Kubernetes提供了另外两个相关对象:LimitRange及ResourceQuota,前者解决request与limit参数的默认值和合法取值范围等问题,后者则解决约束租户的资源配额问题。集群管理涉及计算资源管理(ComputeResources)、服务质量管理(QoS)、资源配额管理(LimitRange、ResourceQuota)等方面。
ResourceQoS解读:若不设置CPU或Memory的Limit值,该Pod的资源使用量有一个弹性范围,假设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,或者只设置了CPULimit或者MemoryLimit两者之一的Pod,表面看都是很有弹性的,但实际上,相对于4个参数都被设置的Pod,是处于一种相对不稳定的状态的,它们与4个参数都没设置的Pod相比,只是稳定一点而已。
二 计算资源管理
2.1 Requests和Limits
以CPU为例,下图显示了未设置Limits和设置了Requests、Limits的CPU使用率的区别。
尽管Requests和Limits只能被设置到容器上,但是设置Pod级别的Requests和Limits能大大提高管理Pod的便利性和灵活性,因此在Kubernetes中提供了对Pod级别的Requests和Limits的配置。对于CPU和内存而言,Pod的Requests或Limits是指该Pod中所有容器的Requests或Limits的总和(对于Pod中没有设置Requests或Limits的容器,该项的值被当作0或者按照集群配置的默认值来计算)。
2.2 CPU和Memory计算
CPU的Requests和Limits是通过CPU数(cpus)来度量的。CPU的资源值是绝对值,而不是相对值,比如0.1CPU在单核或多核机器上是一样的,都严格等于0.1CPUcore。
Memory内存的Requests和Limits计量单位是字节数。使用整数或者定点整数加上国际单位制来表示内存值。国际单位制包括十进制的E、P、T、G、M、K、m,或二进制的Ei、Pi、Ti、Gi、Mi、Ki。KiB与MiB是以二进制表示的字节单位,常见的KB与MB则是以十进制表示的字节单位,比如:
- 1KB(KiloByte)= 1000Bytes = 8000Bits
- 1KiB(KibiByte)= 2^10Bytes = 1024Bytes = 8192Bits
因此,128974848、129e6、129M、123Mi的内存配置是一样的。
Kubernetes的计算资源单位是大小写敏感的,因为m可以表示千分之一单位(milliunit),而M可以表示十进制的1000,二者的含义不同;同理,小写的k不是一个合法的资源单位。
示例1:
[root@k8smaster01 study]# vi requests01.yaml
1 apiVersion: v1 2 kind: Pod 3 metadata: 4 name: frontend 5 spec: 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(227 Bytes)内存,Limits是1CPU和256MiB(228 Bytes)内存。
2.3 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。假设该节点已经启动3个此Pod实例,而这3个Pod的实际内存使用都不足500MB,那么理论上该节点的可用内存应该大于1.5GB。但是由于该节点的Pod Requests总和已经达到节点的可用内存上限,因此Kubernetes不会再将任何Pod实例调度到该节点上。
注意:可能某节点上的实际资源使用量非常低,但是已运行Pod配置的Requests值的总和非常高,再加上需要调度的Pod的Requests值,会超过该节点提供给Pod的资源容量上限,这时Kubernetes仍然不会将Pod调度到该节点上。如果Kubernetes将Pod调度到该节点上,之后该节点上运行的Pod又面临服务峰值等情况,就可能导致Pod资源短缺。
2.4 Requests和Limits机制
kubelet在启动Pod的某个容器时,会将容器的Requests和Limits值转化为相应的容器启动参数传递给容器执行器(Docker或者rkt)。如果容器的执行环境是Docker,那么会传递如下4个参数给Docker容器:
- spec.container[].resources.requests.cpu
这个参数会转化为core数(比如配置的100m会转化为0.1),然后乘以1024,再将这个结果作为--cpu-shares参数的值传递给docker run命令。在docker run命令中,--cpu-share参数是一个相对权重值(RelativeWeight),这个相对权重值会决定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而言是相对值,主要用于资源分配比例。
- 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.1core的结果与Kubernetes配置的意义是一致的。
注意:如果kubelet的启动参数--cpu-cfs-quota被设置为true,那么kubelet会强制要求所有Pod都必须配置CPU Limits(如果Pod没有配置,则集群提供了默认配置也可以)。从Kubernetes1.2版本开始,这个--cpu-cfs-quota启动参数的默认值就是true。
- spec.container[].resources.requests.memory
这个参数值只提供给Kubernetes调度器作为调度和管理的依据,不会作为任何参数传递给Docker。
- spec.container[].resources.limits.memory
这个参数值会转化为单位为Bytes的整数,数值会作为--memory参数传递给docker run命令。如果一个容器在运行过程中使用了超出了其内存Limits配置的内存限制值,那么它可能会被杀掉,如果这个容器是一个可重启的容器,那么之后它会被kubelet重新启动。
因此对容器的Limits配置需要进行准确测试和评估。与内存Limits不同的是,CPU在容器技术中属于可压缩资源,因此对CPU的Limits配置一般不会因为偶然超标使用而导致容器被系统杀。
2.5 计算资源使用情况监控
Pod的资源用量会作为Pod的状态信息一同上报给Master。如果在集群中配置了Heapster来监控集群的性能数据,那么还可以从Heapster中查看Pod的资源用量信息。
2.6 计算资源调度常见问题
- Pod状态为Pending,错误信息为FailedScheduling
如果Kubernetes调度器在集群中找不到合适的节点来运行Pod,那么这个Pod会一直处于未调度状态,直到调度器找到合适的节点为止。每次调度器尝试调度失败时,Kubernetes都会产生一个事件,我们可以通过下面这种方式来查看事件的信息:
kubectl describe pod <podname>| grep -A 3 Events
如果一个或者多个Pod调度失败且有这类错误,那么可以尝试以下几种解决方法:
- 添加更多的节点到集群中;
- 停止一些不必要的运行中的Pod,释放资源;
- 检查Pod的配置,错误的配置可能导致该Pod永远无法被调度执行。比如整个集群中所有节点都只有1CPU,而Pod配置的CPURequests为2,该Pod就不会被调度执行。
可以使用kubectl describe nodes命令来查看集群中节点的计算资源容量和已使用量:
[root@k8smaster01 ~]# kubectl describe nodes k8snode01
超过可用资源容量上限(Capacity)和已分配资源量(Allocatedresources)差额的Pod无法运行在该Node上。
- 容器被强行终止(Terminated)
如果容器使用的资源超过了它配置的Limits,那么该容器可能会被强制终止。可以通过kubectl describe pod命令来确认容器是否因为这个原因被终止:
kubectl describe pod
Restart Count:5说明这个名为simmemleak的容器被强制终止并重启了5次。可以在使用kubectl get pod命令时添加-o go-template=...格式参数来读取已终止容器之前的状态信息:
kubectl get pod -o go-template='{{range.status.containerStatuses}}{{"Container Name:"}}{{.name}}{{"\r\nLastate:"}}{{.lastState}}{{end}}'
可以看到这个容器因为reason:OOM Killed而被强制终止,说明这个容器的内存超过了限制(OutofMemory)。
三 资源配置范围管理(LimitRange)
3.1 LimitRange
在默认情况下,Kubernetes不会对Pod加上CPU和内存限制,这意味着Kubernetes系统中任何Pod都可以使用其所在节点的所有可用的CPU和内存。通过配置Pod的计算资源Requests和Limits,可以限制Pod的资源使用,但对于Kubernetes集群管理员而言,配置每一个Pod的Requests和Limits是烦琐的,而且很受限制。更多时候,需要对集群内Requests和Limits的配置做一个全局限制。
常见的配置场景如下:
- 集群中的每个节点都有2GB内存,集群管理员不希望任何Pod申请超过2GB的内存:因为在整个集群中都没有任何节点能满足超过2GB内存的请求。如果某个Pod的内存配置超过2GB,那么该Pod将永远都无法被调度到任何节点上执行。为了防止这种情况的发生,集群管理员希望能在系统管理功能中设置禁止Pod申请超过2GB内存。
- 集群由同一个组织中的两个团队共享,分别运行生产环境和开发环境。生产环境最多可以使用8GB内存,而开发环境最多可以使用512MB内存。集群管理员希望通过为这两个环境创建不同的命名空间,并为每个命名空间设置不同的限制来满足这个需求。
- 用户创建Pod时使用的资源可能会刚好比整个机器资源的上限稍小,而恰好剩下的资源大小非常尴尬:不足以运行其他任务但整个集群加起来又非常浪费。因此,集群管理员希望设置每个Pod都必须至少使用集群平均资源值(CPU和内存)的20%,这样集群能够提供更好的资源一致性的调度,从而减少了资源浪费。
针对这些需求,Kubernetes提供了LimitRange机制对Pod和容器的Requests和Limits配置进一步做出限制。
示例1:
[root@k8smaster01 study]# kubectl create namespace limit-example #创建namespace
[root@k8smaster01 study]# vi limits.yaml #创建limitrange
1 apiVersion: v1 2 kind: LimitRange 3 metadata: 4 name: mylimits 5 spec: 6 limits: 7 - max: 8 cpu: "4" 9 memory: 2Gi 10 min: 11 cpu: 200m 12 memory: 6Mi 13 maxLimitRequestRatio: 14 cpu: 3 15 memory: 2 16 type: Pod 17 - default: 18 cpu: 300m 19 memory: 200Mi 20 defaultRequest: 21 cpu: 200m 22 memory: 100Mi 23 max: 24 cpu: "2" 25 memory: 1Gi 26 min: 27 cpu: 100m 28 memory: 3Mi 29 maxLimitRequestRatio: 30 cpu: 5 31 memory: 4 32 type: Container 33
[root@k8smaster01 study]# kubectl create -f limits.yaml --namespace=limit-example #为Namespace“limit-example”创建LimitRange
[root@k8smaster01 study]# kubectl get limitranges -n limit-example
[root@k8smaster01 study]# kubectl describe limitranges mylimits -n limit-example
解读:
- 不论是CPU还是内存,在LimitRange中,Pod和Container都可以设置Min、Max和MaxLimit/RequestsRatio参数。Container还可以设置Default Request和Default Limit参数,而Pod不能设置Default Request和DefaultLimit参数。
- 对Pod和Container的参数解释如下:
- Container的Min(如上图100m和3Mi)是Pod中所有容器的Requests值下限;Container的Max(如上图2和1Gi)是Pod中所有容器的Limits值上限;Container的Default Request(如上图200m和100Mi)是Pod中所有未指定Requests值的容器的默认Requests值;Container的DefaultLimit(如上图300m和200Mi)是Pod中所有未指定Limits值的容器的默认Limits值。对于同一资源类型,这4个参数必须满足以下关系:Min ≤ Default Request ≤ Default Limit ≤ Max。
- Pod的Min(如上图200m和6Mi)是Pod中所有容器的Requests值的总和下限;Pod的Max(如上图4和2Gi)是Pod中所有容器的Limits值的总和上限。当容器未指定Requests值或者Limits值时,将使用Container的Default Request值或者Default Limit值。
- Container的Max Limit/Requests Ratio(如上图5和4)限制了Pod中所有容器的Limits值与Requests值的比例上限;而Pod的MaxLimit/RequestsRatio(如上图3和2)限制了Pod中所有容器的Limits值总和与Requests值总和的比例上限。
- 如果设置了Container的Max,那么对于该类资源而言,整个集群中的所有容器都必须设置Limits,否则无法成功创建。Pod内的容器未配置Limits时,将使用Default Limit的值(本例中的300mCPU和200MiB内存),如果也未配置Default,则无法成功创建。
- 如果设置了Container的Min,那么对于该类资源而言,整个集群中的所有容器都必须设置Requests。如果创建Pod的容器时未配置该类资源的Requests,那么在创建过程中会报验证错误。Pod里容器的Requests在未配置时,可以使用默认值default Request(本例中的200mCPU和100MiB内存);如果未配置而又没有使用默认值default Request,那么会默认等于该容器的Limits;如果此时Limits也未定义,就会报错。
- 对于任意一个Pod而言,该Pod中所有容器的Requests总和必须大于或等于6MiB,而且所有容器的Limits总和必须小于或等于1GiB;同样,所有容器的CPU Requests总和必须大于或等于200m,而且所有容器的CPU Limits总和必须小于或等于2。
- Pod里任何容器的Limits与Requests的比例都不能超过Container的MaxLimit/RequestsRatio;Pod里所有容器的Limits总和与Requests的总和的比例不能超过Pod的MaxLimit/RequestsRatio。
[root@k8smaster01 study]# kubectl run nginx --image=nginx --replicas=1 --namespace=limit-example
[root@k8smaster01 study]# kubectl get pods --namespace=limit-example
NAME READY STATUS RESTARTS AGE
nginx-7bb7cd8db5-mzcvb 1/1 Running 0 54s
解读:命名空间中LimitRange只会在Pod创建或者更新时执行检查。如果手动修改LimitRange为一个新的值,那么这个新的值不会去检查或限制之前已经在该命名空间中创建好的Pod。如果在创建Pod时配置的资源值(CPU或者内存)超过了LimitRange的限制,那么该创建过程会报错,在错误信息中会说明详细的错误原因。
[root@k8smaster01 study]# kubectl get pods nginx-7bb7cd8db5-mzcvb --namespace=limit-example -o yaml | grep resources -C 6 #查看该Pod的resource
1 uid: 5fd37e03-ea08-44f3-a2c7-30ad31c7ab4a 2 spec: 3 containers: 4 - image: nginx 5 imagePullPolicy: Always 6 name: nginx 7 resources: 8 limits: 9 cpu: 300m 10 memory: 200Mi 11 requests: 12 cpu: 200m 13 memory: 100Mi 14
解读:由于该Pod未配置资源Requests和Limits,所以使用了namespace limit-example中的默认CPU和内存定义的Requests和Limits值。
[root@k8smaster01 study]# vi invalid-pod.yaml
1 apiVersion: v1 2 kind: Pod 3 metadata: 4 name: invalid-pod 5 spec: 6 containers: 7 - name: kubernetes-serve-hostname 8 image: gcr.azk8s.cn/google_containers/server_hostname 9 resources: 10 limits: 11 cpu: "3" 12 memory: 100Mi 13
[root@k8smaster01 study]# kubectl create -f invalid-pod.yaml --namespace=limit-example
Error from server (Forbidden): error when creating "invalid-pod.yaml": pods "invalid-pod" is forbidden: maximum cpu usage per Container is 2, but limit is 3
解读:创建该Pod,会出现系统报错了,并且提供的错误原因为超过资源限制。
[root@k8smaster01 study]# vi limit-test-nginx.yaml
1 apiVersion: v1 2 kind: Pod 3 metadata: 4 name: limit-test-nginx 5 labels: 6 name: limit-test-nginx 7 spec: 8 containers: 9 - name: limit-test-nginx 10 image: nginx 11 resources: 12 limits: 13 cpu: "1" 14 memory: 512Mi 15 requests: 16 cpu: "0.8" 17 memory: 250Mi 18
[root@k8smaster01 study]# kubectl create -f limit-test-nginx.yaml -n limit-example
Error from server (Forbidden): error when creating "limit-test-nginx.yaml": pods "limit-test-nginx" is forbidden: memory max limit to request ratio per Pod is 2, but provided ratio is 2.048000
解读:由于limit-test-nginx这个Pod的全部内存Limits总和与Requests总和的比例为512∶250,大于在LimitRange中定义的Pod的最大比率2(maxLimitRequestRatio.memory=2),因此创建失败。
[root@k8smaster01 study]# vi valid-pod.yaml
1 apiVersion: v1 2 kind: Pod 3 metadata: 4 name: valid-pod 5 labels: 6 name: valid-pod 7 spec: 8 containers: 9 - name: kubernetes-serve-hostname 10 image: gcr.io/google_containers/serve_hostname 11 resources: 12 limits: 13 cpu: "1" 14 memory: 512Mi 15
[root@k8smaster01 study]# kubectl create -f valid-pod.yaml -n limit-example
[root@k8smaster01 study]# kubectl get pods valid-pod -n limit-example -o yaml | grep resources -C 6 #查看该Pod的资源信息
1 uid: 59e3d05a-8c09-479e-a3ad-1a4dbfd8e946 2 spec: 3 containers: 4 - image: gcr.io/google_containers/serve_hostname 5 imagePullPolicy: Always 6 name: kubernetes-serve-hostname 7 resources: 8 limits: 9 cpu: "1" 10 memory: 512Mi 11 requests: 12 cpu: "1" 13 memory: 512Mi 14
解读:该Pod配置了明确的Limits和Requests,因此该Pod不会使用在namespace limit-example中定义的default和default Request。
注意:CPU Limits强制配置这个选项在Kubernetes集群中默认是开启的;除非集群管理员在部署kubelet时,通过设置参数--cpucfs-quota=false来关闭该限制:如果集群管理员希望对整个集群中容器或者Pod配置的Requests和Limits做限制,那么可以通过配置Kubernetes命名空间中的LimitRange来达到该目的。在Kubernetes集群中,如果Pod没有显式定义Limits和Requests,那么Kubernetes系统会将该Pod所在的命名空间中定义的LimitRange的default和default Requests配置到该Pod上。
四 资源服务质量管理(Resource QoS)
4.1 服务资源质量
Kubernetes会根据Pod的Requests和Limits配置来实现针对Pod的不同级别的资源服务质量控制(QoS)。在Kubernetes的资源QoS体系中,需要保证高可靠性的Pod可以申请可靠资源,而一些不需要高可靠性的Pod可以申请可靠性较低或者不可靠的资源。
容器的资源配置分为Requests和Limits,其中Requests是Kubernetes调度时能为容器提供的完全可保障的资源量(最低保障),而Limits是系统允许容器运行时可能使用的资源量的上限(最高上限)。Pod级别的资源配置是通过计算Pod内所有容器的资源配置的总和得出来的。
Kubernetes中Pod的Requests和Limits资源配置有如下特点:
- 如果Pod配置的Requests值等于Limits值,那么该Pod可以获得的资源是完全可靠的。
- 如果Pod的Requests值小于Limits值,那么该Pod获得的资源可分成两部分:
- 完全可靠的资源,资源量的大小等于Requests值;
- 不可靠的资源,资源量最大等于Limits与Requests的差额,这份不可靠的资源能够申请到多少,取决于当时主机上容器可用资源的余量。
通过这种机制,Kubernetes可以实现节点资源的超售(OverSubscription),比如在CPU完全充足的情况下,某机器共有32GiB内存可提供给容器使用,容器配置为Requests值1GiB,Limits值为2GiB,那么在该机器上最多可以同时运行32个容器,每个容器最多可以使用2GiB内存,如果这些容器的内存使用峰值能错开,那么所有容器都可以正常运行。超售机制能有效提高资源的利用率,同时不会影响容器申请的完全可靠资源的可靠性。
4.2 Requests和Limits限制机制
容器的资源配置满足以下两个条件:
- Requests <= 节点可用资源
- Requests <= Limits
Kubernetes根据Pod配置的Requests值来调度Pod,Pod在成功调度之后会得到Requests值定义的资源来运行;而如果Pod所在机器上的资源有空余,则Pod可以申请更多的资源,最多不能超过Limits的值。Requests和Limits针对不同计算资源类型的限制机制存在差异。这种差异主要取决于计算资源类型是可压缩资源还是不可压缩资源。
- 可压缩资源
Kubernetes目前支持的可压缩资源是CPU。
Pod可以得到Pod的Requests配置的CPU使用量,而能否使用超过Requests值的部分取决于系统的负载和调度。不过由于目前Kubernetes和Docker的CPU隔离机制都是在容器级别隔离的,所以Pod级别的资源配置并不能完全得到保障;Pod级别的cgroups等待引入,以便于确保Pod级别的资源配置准确运行。
空闲CPU资源按照容器Requests值的比例分配。举例说明:容器A的CPU配置为Requests 1 Limits 10,容器B的CPU配置为Request 2 Limits8,A和B同时运行在一个节点上,初始状态下容器的可用CPU为3 cores,那么A和B恰好得到在它们的Requests中定义的CPU用量,即1CPU和2CPU。如果A和B都需要更多的CPU资源,而恰好此时系统的其他任务释放出1.5CPU,那么这1.5 CPU将按照A和B的Requests值的比例1∶2分配给A和B,即最终A可使用1.5CPU,B可使用3CPU。
如果Pod使用了超过在Limits 10中配置的CPU用量,那么cgroups会对Pod中的容器的CPU使用进行限流(Throttled);如果Pod没有配置Limits 10,那么Pod会尝试抢占所有空闲的CPU资源(Kubernetes从1.2版本开始默认开启--cpu-cfs-quota,因此在默认情况下必须配置Limits)
- 不可压缩资源
Kubernetes目前支持的不可压缩资源是内存。
Pod可以得到在Requests中配置的内存。如果Pod使用的内存量小于它的Requests的配置,那么这个Pod可以正常运行;如果Pod使用的内存量超过了它的Requests的配置,那么这个Pod有可能被Kubernetes杀掉:比如Pod A使用了超过Requests而不到Limits的内存量,此时同一机器上另外一个Pod B之前只使用了远少于自己的Requests值的内存,此时程序压力增大,Pod B向系统申请的总量不超过自己的Requests值的内存,那么Kubernetes可能会直接杀掉Pod A,而优先保障Pod B的Requests得到满足;另外一种情况是Pod A使用了超过Requests而不到Limits的内存量,此时Kubernetes将一个新的Pod调度到这台机器上,新的Pod需要使用内存,而只有Pod A使用了超过了自己的Requests值的内存,那么Kubernetes也可能会杀掉Pod A来释放内存资源。
如果Pod使用的内存量超过了它的Limits设置,那么操作系统内核会杀掉Pod所有容器的所有进程中使用内存最多的一个,直到内存不超过Limits为止。
4.3 对调度策略的影响
Kubernetes的kubelet通过计算Pod中所有容器的Requests的总和来决定对Pod的调度。
不管是CPU还是内存,Kubernetes调度器和kubelet都会确保节点上所有Pod的Requests的总和不会超过在该节点上可分配给容器使用的资源容量上限。
4.4 服务质量等级(QoSClasses)
在一个超用(Over Committed,容器Limits总和大于系统容量上限)系统中,由于容器负载的波动可能导致操作系统的资源不足,最终可能导致部分容器被杀掉。在这种情况下,理想是优先杀掉那些不太重要的容器。Kubernetes将容器划分成3个QoS等级来衡量重要程度:
Guaranteed(完全可靠的)、Burstable(弹性波动、较可靠的)和BestEffort(尽力而为、不太可靠的),这三种优先级依次递减。
QoS等级和优先级的关系从理论上来说,QoS级别应该作为一个单独的参数来提供API,并由用户对Pod进行配置,这种配置应该与Requests和Limits无关。但在当前版本的Kubernetes的设计中,为了简化模式及避免引入太多的复杂性,QoS级别直接由Requests和Limits来定义。在Kubernetes中容器的QoS级别等于容器所在Pod的QoS级别,而Kubernetes的资源配置定义了Pod的如上三种QoS级别。
- Guaranteed
如果Pod中的所有容器对所有资源类型都定义了Limits和Requests,并且所有容器的Limits值都和Requests值全部相等(且都不为0),那么该Pod的QoS级别就是Guaranteed。
注意:在这种情况下,容器可以不定义Requests,因为Requests值在未定义时默认等于Limits。
示例1:如下定义的PodQoS级别就是Guaranteed。
1 containers: 2 name: foo 3 resources: 4 limits: 5 cpu: 10m 6 memory: 1Gi 7 name: bar 8 resources: 9 limits: 10 cpu: 100m 11 memory: 100Mi 12
解读:如上未定义Requests值,所以其默认等于Limits值。
示例2:
1 containers: 2 name: foo 3 resources: 4 requests: 5 cpu: 10m 6 memory: 1Gi 7 limits: 8 cpu: 10m 9 memory: 1Gi 10 name: bar 11 resources: 12 requests: 13 cpu: 10m 14 memory: 1Gi 15 limits: 16 cpu: 100m 17 memory: 100Mi 18
解读:该定义的Requests和Limits的值完全相同。
- BestEffort
如果Pod中所有容器都未定义资源配置(Requests和Limits都未定义),那么该Pod的QoS级别就是BestEffort。
示例3:
1 containers: 2 name: foo 3 resources: 4 name: bar 5 resources: 6
解读:该容器都未定义资源配置。
- Burstable
当一个Pod既不为Guaranteed级别,也不为BestEffort级别时,该Pod的QoS级别就是Burstable。Burstable级别的Pod包括两种情况。
- 第1种情况:Pod中的一部分容器在一种或多种资源类型的资源配置中定义了Requests值和Limits值(都不为0),且Requests值小于Limits值;
- 第2种情况:Pod中的一部分容器未定义资源配置(Requests和Limits都未定义)。
注意:在容器未定义Limits时,Limits值默认等于节点资源容量的上限。
示例4:容器foo的CPURequests不等于Limits。
1 containers: 2 name: foo 3 resources: 4 requests: 5 cpu: 5m 6 memory: 1Gi 7 limits: 8 cpu: 10m 9 memory: 1Gi 10 name: bar 11 resources: 12 requests: 13 cpu: 5m 14 memory: 1Gi 15 limits: 16 cpu: 100m 17 memory: 100Mi 18
示例5:容器bar未定义资源配置而容器foo定义了资源配置。
1 containers: 2 name: foo 3 resources: 4 requests: 5 cpu: 10m 6 memory: 1Gi 7 limits: 8 cpu: 10m 9 memory: 1Gi 10 name: bar 11
示例6:容器foo未定义CPU,而容器bar未定义内存。
1 containers: 2 name: foo 3 resources: 4 limits: 5 memory: 1Gi 6 name: bar 7 resources: 8 limits: 9 cpu: 100m 10
示例7:容器bar未定义资源配置,而容器foo未定义Limits值。
containers:
name: foo
resources:
requests:
cpu: 5m
memory: 1Gi
name: bar
4.5 Kubernetes QoS的工作特点
Pod的CPU Requests无法得到满足(比如节点的系统级任务占用过多的CPU导致无法分配足够的CPU给容器使用)时,容器得到的CPU会被压缩限流。由于内存是不可压缩的资源,所以针对内存资源紧缺的情况,会按照以下逻辑进行处理。
- BestEffort Pod的优先级最低,在这类Pod中运行的进程会在系统内存紧缺时被第一优先杀掉。当然,从另外一个角度来看,BestEffort Pod由于没有设置资源Limits,所以在资源充足时,它们可以充分使用所有的闲置资源。
- Burstable Pod的优先级居中,这类Pod初始时会分配较少的可靠资源,但可以按需申请更多的资源。当然,如果整个系统内存紧缺,又没有BestEffort容器可以被杀掉以释放资源,那么这类Pod中的进程可能会被杀掉。
- Guaranteed Pod的优先级最高,而且一般情况下这类Pod只要不超过其资源Limits的限制就不会被杀掉。当然,如果整个系统内存紧缺,又没有其他更低优先级的容器可以被杀掉以释放资源,那么这类Pod中的进程也可能会被杀掉。
4.6 OOM计分系统
OOM(Out Of Memory)计分规则包括如下内容:
OOM计分的计算方法为:计算进程使用内存在系统中占的百分比,取其中不含百分号的数值,再乘以10的结果,这个结果是进程OOM的基础分;将进程OOM基础分的分值再加上这个进程的OOM分数调整值OOM_SCORE_ADJ的值,作为进程OOM的最终分值(除root启动的进程外)。在系统发生OOM时,OOM Killer会优先杀掉OOM计分更高的进程。
进程的OOM计分的基本分数值范围是0~1000,如果A进程的调整值OOM_SCORE_ADJ减去B进程的调整值的结果大于1000,那么A进程的OOM计分最终值必然大于B进程,会优先杀掉A进程。
不论调整OOM_SCORE_ADJ值为多少,任何进程的最终分值范围也是0~1000。在Kubernetes,不同QoS的OOM计分调整值规则如下所示。
其中:
- BestEffortPod设置OOM_SCORE_ADJ调整值为1000,因此BestEffortPod中容器里所有进程的OOM最终分肯定是1000。
- GuaranteedPod设置OOM_SCORE_ADJ调整值为-998,因此GuaranteedPod中容器里所有进程的OOM最终分一般是0或者1(因为基础分不可能是1000)。
- BurstablePod规则分情况说明:如果BurstablePod的内存Requests超过了系统可用内存的99.8%,那么这个Pod的OOM_SCORE_ADJ调整值固定为2;否则,设置OOM_SCORE_ADJ调整值为1000-10×(%of memory requested);如果内存Requests为0,那么OOM_SCORE_ADJ调整值固定为999。这样的规则能确保OOM_SCORE_ADJ调整值的范围为2~999,而BurstablePod中所有进程的OOM最终分数范围为2~1000。BurstablePod进程的OOM最终分数始终大于GuaranteedPod的进程得分,因此它们会被优先杀掉。如果一个BurstablePod使用的内存比它的内存Requests少,那么可以肯定的是它的所有进程的OOM最终分数会小于1000,此时能确保它的优先级高于BestEffortPod。如果在一个BurstablePod的某个容器中某个进程使用的内存比容器的Requests值高,那么这个进程的OOM最终分数会是1000,否则它的OOM最终分会小于1000。假设在下面的容器中有一个占用内存非常大的进程,那么当一个使用内存超过其Requests的BurstablePod与另外一个使用内存少于其Requests的BurstablePod发生内存竞争冲突时,前者的进程会被系统杀掉。如果在一个BurstablePod内部有多个进程的多个容器发生内存竞争冲突,那么此时OOM评分只能作为参考,不能保证完全按照资源配置的定义来执行OOMKill。
OOM还有一些特殊的计分规则,如下所述。
- kubelet进程和Docker进程的调整值OOM_SCORE_ADJ为-998。
- 如果配置进程调整值OOM_SCORE_ADJ为-999,那么这类进程不会被OOMKiller杀掉。
五 资源配额管理( Resource Quotas)
5.1 配额管理
ResourceQuotas通常用于在共享集群资源场景中平衡资源,通过ResourceQuota对象,可以定义资源配额,这个资源配额可以为每个命名空间都提供一个总体的资源使用的限制:它可以限制命名空间中某种类型的对象的总数目上限,也可以设置命名空间中Pod可以使用的计算资源的总上限。
ResourceQuotas典型的场景如下:
- 不同的团队工作在不同的命名空间下,目前这是非约束性的,在未来的版本中可能会通过ACL(Access Control List,访问控制列表)来实现强制性约束。
- 集群管理员为集群中的每个命名空间都创建一个或者多个资源配额项。
- 当用户在命名空间中使用资源(创建Pod或者Service等)时,Kubernetes的配额系统会统计、监控和检查资源用量,以确保使用的资源用量没有超过资源配额的配置。
- 如果在创建或者更新应用时资源使用超过了某项资源配额的限制,那么创建或者更新的请求会报错(HTTP 403 Forbidden),并给出详细的出错原因说明。
- 如果命名空间中的计算资源(CPU和内存)的资源配额启用,那么用户必须为相应的资源类型设置Requests或Limits;否则配额系统可能会直接拒绝Pod的创建。这里可以使用LimitRange机制来为没有配置资源的Pod提供默认资源配置。
场景示例:
- 集群共有32GB内存和16CPU,两个小组。A小组使用20GB内存和10CPU,B小组使用10GB内存和2CPU,剩下的2GB内存和2CPU作为预留。在名为testing的命名空间中,限制使用1CPU和1GB内存;在名为production的命名空间中,资源使用不受限制。
在使用资源配额时,需要注意以下两点。
- 如果集群中总的可用资源小于各命名空间中资源配额的总和,那么可能会导致资源竞争。资源竞争时,Kubernetes系统会遵循先到先得的原则。
- 不管是资源竞争还是配额的修改,都不会影响已经创建的资源使用对象。
5.2 开启资源配额特性
资源配额可以通过在kube-apiserver的--admission-control参数值中添加ResourceQuota参数进行开启。如果在某个命名空间的定义中存在ResourceQuota,那么对于该命名空间而言,资源配额就是开启的。一个命名空间可以有多个ResourceQuota配置项。
提示:在v 1.10后续的版本中,--admission-control 已经废弃,建议使用 --enable-admission-plugins。
[root@k8smaster01 study]# vi /etc/kubernetes/manifests/kube-apiserver.yaml
……
- --enable-admission-plugins=NodeRestriction,ResourceQuota,LimitRanger
……
[root@k8smaster01 study]# systemctl restart kubelet.service
5.3 资源配额类型
- 计算资源配额(ComputeResourceQuota)
资源配额可以限制一个命名空间中所有Pod的计算资源的总和。目前支持的计算资源类型如下表:
- 存储资源配额(Volume Count Quota)
可以在给定的命名空间中限制所使用的存储资源(StorageResources)的总量,目前支持的存储资源名称如下表:
- 对象数量配额( Object Count Quota)
指定类型的对象数量可以被限制。 如下表列出了ResourceQuota支持限制的对象类型:
通常,可以通过资源配额来限制在命名空间中能创建的Pod的最大数量,这种设置可以防止某些用户大量创建Pod而迅速耗尽整个集群的Pod IP和计算资源。
5.4 配额的作用域(QuotaScopes)
每项资源配额都可以单独配置一组作用域,配置了作用域的资源配额只会对符合其作用域的资源使用情况进行计量和限制,作用域范围内超过了资源配额的请求都会报验证错误。ResourceQuota的4种作用域如下所示:
其中,BestEffort作用域可以限定资源配额来追踪pods资源的使用,Terminating、NotTerminating和NotBestEffort这三种作用域可以限定资源配额来追踪以下资源的使用:
- cpu
- limits.cpu
- limits.memory
- memory
- pods
- requests.cpu
- requests.memory
5.5 资源配额(ResourceQuota)设置Requests和Limits
资源配额也可以设置Requests和Limits。如果在资源配额中指定了requests.cpu或requests.memory,那么它会强制要求每个容器都配置自己的CPU Requests或CPU Limits(可使用Limit Range提供的默认值)。
同理,如果在资源配额中指定了limits.cpu或limits.memory,那么它也会强制要求每个容器都配置自己的内存Requests或内存Limits(可使用LimitRange提供的默认值)。
5.6 资源配额的定义
与LimitRange相似,ResourceQuota也被设置在Namespace中。
示例1:
[root@k8smaster01 study]# kubectl create namespace myspace #创建名为myspace的Namespace
[root@k8smaster01 study]# vi compute-resources.yaml #创建ResourceQuota配置文件
1 apiVersion: v1 2 kind: ResourceQuota 3 metadata: 4 name: compute-resources 5 spec: 6 hard: 7 pods: "4" 8 requests.cpu: "1" 9 requests.memory: 1Gi 10 limits.cpu: "2" 11 limits.memory: 2Gi 12
[root@k8smaster01 study]# kubectl create -f compute-resources.yaml --namespace=myspace #创建该ResourceQuota
创建另一个名为object-counts.yaml的文件,用于设置对象数量的配额:
[root@k8smaster01 study]# vi object-counts.yaml
1 apiVersion: v1 2 kind: ResourceQuota 3 metadata: 4 name: object-counts 5 spec: 6 hard: 7 configmaps: "10" 8 persistentvolumeclaims: "4" 9 replicationcontrollers: "20" 10 secrets: "10" 11 services: "10" 12 services.loadbalancers: "2" 13
[root@k8smaster01 study]# kubectl create -f object-counts.yaml --namespace=myspace
[root@k8smaster01 study]# kubectl describe quota compute-resources --namespace=myspace
[root@k8smaster01 study]# kubectl describe quota object-counts --namespace=myspace
5.7 资源配额与集群资源总量的关系
资源配额与集群资源总量是完全独立的。资源配额是通过绝对的单位来配置的,这也就意味着如果在集群中新添加了节点,那么资源配额不会自动更新,而该资源配额所对应的命名空间中的对象也不能自动增加资源上限。在某些情况下,可能希望资源配额支持更复杂的策略,如下所述。
- 对于不同的租户,按照比例划分整个集群的资源。
- 允许每个租户都能按照需要来提高资源用量,但是有一个较宽容的限制,以防止意外的资源耗尽情况发生。
- 探测某个命名空间的需求,添加物理节点并扩大资源配额值。
这些策略可以通过将资源配额作为一个控制模块、手动编写一个控制器来监控资源使用情况,并调整命名空间上的资源配额来实现。资源配额将整个集群中的资源总量做了一个静态划分,但它并没有对集群中的节点做任何限制:不同命名空间中的Pod仍然可以运行在同一个节点上。
参考链接:https://blog.csdn.net/dkfajsldfsdfsd/article/details/81004172
作者:木二
出处:http://www.cnblogs.com/itzgr/
关于作者:云计算、虚拟化,Linux,多多交流!
本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接!如有其他问题,可邮件(xhy@itzgr.com)咨询。