在kubernetes中,每个Pod都有个QoS标记,QoS的英文全称为"Quality of Service",中文名为"服务质量",它取决于用户对服务质量的预期,也就是期望的服务质量。对于Pod来说,服务质量体现在两个指标上,一个指标是CPU,另一个指标是内存。在实际运行过程中,当NODE节点
上内存资源紧张的时候,kubernetes根据Pod具有的不同QoS标记,采取不同调度和驱逐策略。
本文介绍怎样配置 Pod 让其获得特定的服务质量(QoS)类。Kubernetes 使用 QoS 类来决定 Pod 的调度和驱逐策略。
QoS 类
Kubernetes 创建 Pod 时就给它指定了下列一种 QoS 类:
- Guaranteed
- Burstable
- BestEffort
创建命名空间
创建一个命名空间,以便将本练习所创建的资源与集群的其余资源相隔离。
kubectl create namespace qos-example
创建一个 QoS 类为 Guaranteed 的 Pod
对于 QoS 类为 Guaranteed 的 Pod:
- Pod 中的每个容器必须指定内存请求和内存限制,并且两者要相等。
- Pod 中的每个容器必须指定 CPU 请求和 CPU 限制,并且两者要相等。
下面是包含一个容器的 Pod 配置文件。 容器设置了内存请求和内存限制,值都是 200 MiB。 容器设置了 CPU 请求和 CPU 限制,值都是 700 milliCPU:
apiVersion: v1
kind: Pod
metadata:
name: qos-demo
namespace: qos-example
spec:
containers:
- name: qos-demo-ctr
image: nginx
resources:
limits:
memory: "200Mi"
cpu: "700m"
requests:
memory: "200Mi"
cpu: "700m"
创建 Pod:
kubectl create -f https://k8s.io/examples/pods/qos/qos-pod.yaml --namespace=qos-example
查看 Pod 详情:
kubectl get pod qos-demo --namespace=qos-example --output=yaml
结果表明 Kubernetes 为 Pod 配置的 QoS 类为 Guaranteed。 结果也确认了 Pod 容器设置了与内存限制匹配的内存请求,设置了与 CPU 限制匹配的 CPU 请求。
spec:
containers:
...
resources:
limits:
cpu: 700m
memory: 200Mi
requests:
cpu: 700m
memory: 200Mi
...
qosClass: Guaranteed
注意:
如果容器指定了自己的内存限制,但没有指定内存请求,Kubernetes 会自动为它指定与内存限制匹配的内存请求。 同样,如果容器指定了自己的 CPU 限制,但没有指定 CPU 请求,Kubernetes 会自动为它指定与 CPU 限制匹配的 CPU 请求。
删除 Pod:
kubectl delete pod qos-demo --namespace=qos-example
创建一个 QoS 类为 Burstable 的 Pod
如果满足下面条件,将会指定 Pod 的 QoS 类为 Burstable:
- Pod 不符合 Guaranteed QoS 类的标准。
- Pod 中至少一个容器具有内存或 CPU 请求。
下面是包含一个容器的 Pod 配置文件。 容器设置了内存限制 200 MiB 和内存请求 100 MiB。
apiVersion: v1
kind: Pod
metadata:
name: qos-demo-2
namespace: qos-example
spec:
containers:
- name: qos-demo-2-ctr
image: nginx
resources:
limits:
memory: "200Mi"
requests:
memory: "100Mi"
创建 Pod:
kubectl create -f https://k8s.io/examples/pods/qos/qos-pod-2.yaml --namespace=qos-example
查看 Pod 详情:
kubectl get pod qos-demo-2 --namespace=qos-example --output=yaml
结果表明 Kubernetes 为 Pod 配置的 QoS 类为 Burstable。
spec:
containers:
- image: nginx
imagePullPolicy: Always
name: qos-demo-2-ctr
resources:
limits:
memory: 200Mi
requests:
memory: 100Mi
...
qosClass: Burstable
删除 Pod:
kubectl delete pod qos-demo-2 --namespace=qos-example
创建一个 QoS 类为 BestEffort 的 Pod
- 对于 QoS 类为 BestEffort 的 Pod,Pod 中的容器必须没有设置内存和 CPU 限制或请求。
下面是包含一个容器的 Pod 配置文件。 容器没有设置内存和 CPU 限制或请求。
apiVersion: v1
kind: Pod
metadata:
name: qos-demo-3
namespace: qos-example
spec:
containers:
- name: qos-demo-3-ctr
image: nginx
创建 Pod:
kubectl create -f https://k8s.io/examples/pods/qos/qos-pod-3.yaml --namespace=qos-example
查看 Pod 详情:
kubectl get pod qos-demo-3 --namespace=qos-example --output=yaml
结果表明 Kubernetes 为 Pod 配置的 QoS 类为 BestEffort。
spec:
containers:
...
resources: {}
...
qosClass: BestEffort
删除 Pod:
kubectl delete pod qos-demo-3 --namespace=qos-example
创建包含两个容器的 Pod
下面是包含两个容器的 Pod 配置文件。 一个容器指定了内存请求 200 MiB。 另外一个容器没有指定任何请求和限制。
apiVersion: v1
kind: Pod
metadata:
name: qos-demo-4
namespace: qos-example
spec:
containers:
- name: qos-demo-4-ctr-1
image: nginx
resources:
requests:
memory: "200Mi"
- name: qos-demo-4-ctr-2
image: redis
注意此 Pod 满足 Burstable QoS 类的标准。 也就是说它不满足 Guaranteed QoS 类标准,因为它的一个容器设有内存请求。
创建 Pod:
kubectl create -f https://k8s.io/examples/pods/qos/qos-pod-4.yaml --namespace=qos-example
查看 Pod 详情:
kubectl get pod qos-demo-4 --namespace=qos-example --output=yaml
结果表明 Kubernetes 为 Pod 配置的 QoS 类为 Burstable:
spec:
containers:
...
name: qos-demo-4-ctr-1
resources:
requests:
memory: 200Mi
...
name: qos-demo-4-ctr-2
resources: {}
...
qosClass: Burstable
删除 Pod:
kubectl delete pod qos-demo-4 --namespace=qos-example
环境清理
删除命名空间:
kubectl delete namespace qos-example
QoS级别决定了kubernetes处理这些POD的方式,我们以内存资源为例:
当 NODE节点上内存资源不够的时候,QoS级别是BestEffort的POD会最先被kill掉;当NODE节点上内存资源充足的时候,QoS级别是BestEffort的POD可以使用NODE节点上剩余的所有内存资源。
当NODE节点上内存资源不够的时候,如果QoS级别是BestEffort的POD已经都被kill掉了,那么会查找QoS级别是Burstable的POD,并且这些POD使用的内存已经超出了requests设置的内存值,这些被找到的POD会被kill掉;当NODE节点上内存资源充足的时候,QoS级别是Burstable的POD会按照requests和limits的设置来使用。
当NODE节点上内存资源不够的时候,如果QoS级别是BestEffort和Burstable的POD都已经被kill掉了,那么会查找QoS级别是Guaranteed的POD,并且这些POD使用的内存已经超出了limits设置的内存值,这些被找到的POD会被kill掉;当NODE节点上内存资源充足的时候,QoS级别是Burstable的POD会按照requests和limits的设置来使用。
从容器的角度出发,为了限制容器使用的CPU和内存,是通过cgroup来实现的,目前kubernetes的QoS只能管理CPU和内存,所以kubernetes现在也是通过对cgroup的配置来实现QoS管理的。
我们简单来介绍下cgroup:
1、 来源:
cgroups(Control Groups)
最初叫ProcessContainer
,由Google工程师(Paul Menage和Rohit Seth)于2006年提出,后来因为Container有多重含义容易引起误解,就在2007年更名为Control Groups,并被整合进Linux内核。顾名思义就是把进程放到一个组里面统一加以控制。
2、官方定义:
cgroups是Linux内核提供的一种机制,这种机制可以根据特定的行为,把一系列系统任务及其子任务整合(或分隔)到按资源划分等级的不同组内,从而为系统资源管理提供一个统一的框架。
3、作用:
资源限制(Resource Limitation):cgroups可以对进程组使用的资源总额进行限制。如设定应用运行时使用内存的上限,一旦超过这个配额就发出OOM(Out of Memory)。
优先级分配(Prioritization):通过分配的CPU时间片数量及硬盘IO带宽大小,实际上就相当于控制了进程运行的优先级。
资源统计(Accounting): cgroups可以统计系统的资源使用量,如CPU使用时长、内存用量等等,这个功能非常适用于计费。
进程控制(Control):cgroups可以对进程组执行挂起、恢复等操作。
4、特点:
cgroups的API以一个伪文件系统的方式实现,即用户可以通过文件操作实现cgroups的组织管理。
cgroups的组织管理操作单元可以细粒度到线程级别,用户态代码也可以针对系统分配的资源创建和销毁cgroups,从而实现资源再分配和管理。
所有资源管理的功能都以“ subsystem(子系统)”的方式实现,接口统一。
子进程创建之初与其父进程处于同一个cgroups的控制组。
5、本质:
本质上来说,cgroups是内核附加在程序上的一系列钩子(hooks),通过程序运行时对资源的调度触发相应的钩子以达到资源追踪和限制的目的。
6、cgroups术语
task(任务):cgroups的术语中,task就表示系统的一个进程。
cgroup(控制组):cgroups 中的资源控制都以cgroup为单位实现。cgroup表示按某种资源控制标准划分而成的任务组,包含一个或多个子系统。一个任务可以加入某个cgroup,也可以从某个cgroup迁移到另外一个cgroup。
subsystem(子系统):cgroups中的subsystem就是一个资源调度控制器(ResourceController)。比如CPU子系统可以控制CPU时间分配,内存子系统可以限制cgroup内存使用量。
hierarchy(层级树):hierarchy由一系列cgroup以一个树状结构排列而成,每个hierarchy通过绑定对应的subsystem进行资源调度。hierarchy中的cgroup节点可以包含零或多个子节点,子节点继承父节点的属性。整个系统可以有多个hierarchy。
在kubernetes中,我们可以看看POD配置中的requests和limits参数是如何同cgroup建立关联关系,进而实现对POD的资源QoS管理的。
QoS级别 | QoS配置例子 | cgroup对应 |
---|---|---|
BestEffort | kind: Pod metadata: name: Pod5 spec: containers: name: foo resources: name: bar resources: |
/BE/cpu.shares = 2 /BE/cpu.quota= not set /BE/memory.limit_in_bytes = Allocatable - 7Gi /BE/Pod5/memory.limit_in_bytes = no limit |
Burstable | kind: Pod metadata: name: Pod3 spec: containers: name: foo resources: limits: cpu: 50m memory: 2Gi requests: cpu: 20m memory: 1Gi name: bar resources: limits: cpu: 100m memory: 1Gi kind: Pod metadata: name: Pod4 spec: containers: name: foo resources: limits: cpu: 20m memory: 2Gi requests: cpu: 10m memory: 1Gi |
/Bu/cpu.shares = 30m /Bu/Pod3/cpu.quota = 150m /Bu/Pod3/cpu.shares = 20m /Bu/Pod4/cpu.quota = 20m /Bu/Pod4/cpu.shares = 10m /Bu/memory.limit_in_bytes = Allocatable - 5Gi /Bu/Pod3/memory.limit_in_bytes = 3Gi /Bu/Pod4/memory.limit_in_bytes = 2Gi |
Guaranteed | kind: Pod metadata: name: Pod1 spec: containers: name: foo resources: limits: cpu: 10m memory: 1Gi name: bar resources: limits: cpu: 100m memory: 2Gi kind: Pod metadata: name: Pod2 spec: containers: name: foo resources: limits: cpu: 20m memory: 2Gi |
/Pod1/cpu.quota = 110m /Pod1/cpu.shares = 110m /Pod2/cpu.quota = 20m /Pod2/cpu.shares = 20m /Pod1/memory.limit_in_bytes = 3Gi /Pod2/memory.limit_in_bytes = 2Gi |
我们知道了cgroups里面有hierarchy(层级树)
的概念,那么我们来看在kubernetes里面是如何把POD放在hierarchy(层级树)上面的:
对于kubernetes来说,通过cgroup就可以给POD设置QoS级别,当资源不够使用时,先kill优先级低的POD,在实际使用时,是通过OOM(Out of Memory)分数值来实现的,OOM分数值从0到1000。OOM分数值是根据OOM_ADJ参数计算出来的,对于Guaranteed级别的POD,OOM_ADJ参数设置成了-998,对于BestEffort级别的POD,OOM_ADJ参数设置成了1000,对于Burstable级别的POD,OOM_ADJ参数取值从2到999,对于kube保留的资源,比如kubelet,OOM_ADJ参数设置成了-999。OOM_ADJ参数设置的越大,通过OOM_ADJ参数计算出来的OOM分数越高,OOM分数越高,这个POD的优先级就越低,在出现资源竞争的时候,就越早被kill掉,对于OOM_ADJ参数是-999的代表kubernetes永远不会因为OOM而被kill掉。如下图所示: