抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

Kubernetes 质量管理

image-20220525094307190

概述

背景

K8S 的节点上的资源会被 pod 和系统进程所使用,如果默认什么都不配置,那么节点上的全部资源都是可以分配给pod使用的,系统进程本身没有保障,这样做会存在以下问题:

集群雪崩

​ 如果节点上调度了大量pod,且pod没有合理的limit限制,节点资源将被耗尽,sshd、kubelet等进程OOM,节点变成 not ready状态,pod重新继续调度到其他节点,新节点也被打挂,引起集群雪崩。

系统进程异常

​ 就算 pod 设置了limit,但如果机器遇到资源不足,系统进程如 docker没有资源保障,会频繁 OOM,或者进程 hang 住无响应,虽然能运行,但容器会反复出问题

节点资源分类

  • 可压缩资源:如CPU,即使cpu 超配,也可以划分时间片运行,只是运行变慢,进程不会挂。
  • 不可压缩资源:Memory/Storage,内存不同于CPU,系统内存不足时,会触发 OOM杀死进程,按照oom score 来确定先kill谁,oom_score_adj值越高,被kill 的优先级越高。

QoS简介

在kubernetes中,每个POD都有个QoS标记,通过这个Qos标记来对POD进行服务质量管理。

什么是QOS

QoS 是 Quality of Service 的缩写,即服务质量,为了实现资源被有效调度和分配的同时提高资源利用率

​ QoS的英文全称为”Quality of Service”,中文名为”服务质量”,它取决于用户对服务质量的预期,也就是期望的服务质量。对于POD来说,服务质量体现在两个指标上,一个指标是CPU,另一个指标是内存。

​ 在实际运行过程中,当NODE节点上内存资源紧张的时候,kubernetes根据POD具有的不同QoS标记,采取不同的处理策略。

​ Kubernetes 针对不同服务质量的预期,通过 QoS 来对 pod 进行服务质量管理。对于一个 pod 来说,服务质量体现在两个具体的指标:CPU 和内存。当节点上内存资源紧张时,Kubernetes 会根据预先设置的不同 QoS 类别进行相应处理。

服务质量分级

在Kubernetes中,POD的QoS服务质量一共有三个级别,如下图所示:

img

在 Kubernetes 中,QoS 主要分为 GuaranteedBurstableBest-Effort 三类,优先级从高到低

QoS级别 QoS介绍
BestEffort(尽力而为、不太可靠的) POD中的所有容器都没有指定CPU和内存的requests和limits,那么这个POD的QoS就是BestEffort级别。
Burstable(弹性波动、较可靠的 POD中只要有一个容器,这个容器requests和limits的设置同其他容器设置的不一致,那么这个POD的QoS就是Burstable级别 。
Guaranteed(完全可靠的) POD中所有容器都必须统一设置了limits,并且设置参数都一致,如果有一个容器要设置requests,那么所有容器都要设置,并设置参数同limits一致,那么这个POD的QoS就是Guaranteed级别 。

资源限制

​ 如果未做过节点 nodeSelector、亲和性(node affinity)或 pod 亲和、反亲和性等高级调度策略设置,我们没有办法指定服务部署到指定节点上,这样就可能会造成 CPU 或内存等密集型的 pod 同时分配到相同节点上,造成资源竞争。

​ 另一方面,如果未对资源进行限制,一些关键的服务可能会因为资源竞争因 OOM 等原因被 kill 掉,或者被限制 CPU 使用。

资源限制范围

​ 我们知道对于每一个资源,container 可以指定具体的资源需求(requests)和限制(limits),requests 申请范围是0到节点的最大配置,而 limits 申请范围是 requests 到无限,即 0 <= requests <= Node Allocatable, requests <= limits <= Infinity。

CPU资源限制

​ 对于 CPU,如果 pod 中服务使用的 CPU 超过设置的 limits,pod 不会被 kill 掉但会被限制,因为 CPU 是可压缩资源,如果没有设置 limits,pod 可以使用全部空闲的 CPU 资源。

内存资源限制

​ 对于内存,当一个 pod 使用内存超过了设置的 limits,pod 中容器的进程会被 kernel 因 OOM kill 掉,当 container 因为 OOM 被 kill 掉时,系统倾向于在其原所在的机器上重启该 container 或本机或其他重新创建一个 pod。(包括我们的磁盘也是一个不可压缩资源)

资源分配

主机cpu资源利用率满时,机器出现的现象–>卡顿。

​ 我们知道,tomcat一般是比较耗费资源的,起码需要1c以上;(java应用特别消耗cpu资源、内存资源),如果你的pod里的requests配置为0.5c,而limits就配置为了0.8c,那么有可能你的容器都启动不起来;

​ 默认情况下,宿主机和容器都没做资源限制的话,容器理论上是可以无限制获取到主机的计算资源(如cpu和内存)。如果某个容器应用特别耗cpu,当它把主机cpu耗尽时(资源利用率超高,达到98,99%以上),问主机会出现什么现象?会出现卡顿现象,执行一条命令,老半天才出来,像老年痴呆一样;

​ 是因为cpu是按时间片来分配的,cpu的分配原则:为了尽可能地公平地给大家一个分配,但cpu没有特别硬性的限制

注意事项
  1. 宿主机只有2c,这里的limits可以设置为100c,但是毫无意义,只是可以这样设置。limits是最大可用资源,一般是requests的20%左右,不太超出太多,否则limits限制就没多少意义了;
  2. limits不能小于requests;
  3. reqeusts只是一个预留性质,不是pod配置写多少,宿主机就会占用多少资源;
  4. k8s根据request来统计每个节点预分配资源,来判断下一个pod能不能分配我这个节点;
  5. 所以,我们工作中,一定要配置requests和limits,来解决资源争抢问题;
  6. limits不配置的话,默认和requests配置一样;

服务质量配置

Kubelet 提供 QoS 服务质量管理,支持系统级别的 OOM 控制

​ QoS 分类并不是通过一个配置项来直接配置的,而是通过配置 CPU/内存的 limits 与 requests 值的大小来确认服务质量等级的,我们通过使用 kubectl get pod xxx -o yaml 可以看到 pod 的配置输出中有 qosClass 一项,该配置的作用是为了给资源调度提供策略支持,调度算法根据不同的服务质量等级可以确定将 pod 调度到哪些节点上。

Guaranteed(有保证的)

系统用完了全部内存,且没有其他类型的容器可以被 kill 时,该类型的 pods 会被 kill 掉,也就是说最后才会被考虑 kill 掉,属于该级别的 pod 有以下两种情况:

  • pod 中的所有容器都且仅设置了 CPU 和内存的 limits
  • pod 中的所有容器都设置了 CPU 和内存的 requests 和 limits ,且单个容器内的 requests==limits(requests不等于0)
仅设置了limits

pod 中的所有容器都且仅设置了 limits:

1
2
3
4
5
6
7
8
9
10
11
containers:
name: foo
resources:
limits:
cpu: 10m
memory: 1Gi
name: bar
resources:
limits:
cpu: 100m
memory: 100Mi

​ 因为如果一个容器只指明 limit 而未设定 requests,则 requests 的值等于 limit 值,所以上面 pod 的 QoS 级别属于 Guaranteed。

另外需要注意若容器指定了 requests 而未指定 limits,则 limits 的值等于节点资源的最大值;若容器指定了 limits 而未指定 requests,则 requests 的值等于 limits。

明确设置

另外一个就是 pod 中的所有容器都明确设置了 requests 和 limits,且单个容器内的 requests==limits

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
containers:
name: foo
resources:
limits:
cpu: 10m
memory: 1Gi
requests:
cpu: 10m
memory: 1Gi
name: bar
resources:
limits:
cpu: 100m
memory: 100Mi
requests:
cpu: 100m
memory: 100Mi

容器 foo 和 bar 内 resources 的 requests 和 limits 均相等,该 pod 的 QoS 级别属于 Guaranteed。

Burstable(不稳定的)

系统用完了全部内存,且没有 Best-Effort 类型的容器可以被 kill 时,该类型的 pods 会被 kill 掉,pod 中只要有一个容器的 requests 和 limits 的设置不相同,该 pod 的 QoS 即为 Burstable。

情况1

比如容器 foo 指定了 resource,而容器 bar 未指定:

1
2
3
4
5
6
7
8
9
10
containers:
name: foo
resources:
limits:
cpu: 10m
memory: 1Gi
requests:
cpu: 10m
memory: 1Gi
name: bar
情况2

或者容器 foo 设置了内存 limits,而容器 bar 设置了 CPU limits

1
2
3
4
5
6
7
8
9
containers:
name: foo
resources:
limits:
memory: 1Gi
name: bar
resources:
limits:
cpu: 100m
Best-Effort(尽最大努力)

系统用完了全部内存时,该类型 pods 会最先被 kill 掉

如果 pod 中所有容器的 resources 均未设置 requests 与 limits,那么该 pod 的 QoS 即为 Best-Effort

情况1

比如容器 foo 和容器 bar 均未设置 requests 和 limits:

1
2
3
4
5
containers:
name: foo
resources:
name: bar
resources:
使用建议
  • 如果资源充足,可将 QoS pods 类型均设置为 Guaranteed,用计算资源换业务性能和稳定性,减少排查问题时间和成本。

  • 如果想更好的提高资源利用率,业务服务可以设置为 Guaranteed,而其他服务根据重要程度可分别设置为 Burstable 或 Best-Effort。

Pod资源限制

资源限制的实现

Kubernetes对资源的限制实际上是通过cgroup来控制的,cgroup是容器的一组用来控制内核如何运行进程的相关属性集合,针对内存、CPU和各种设备都有对应的cgroup

​ 默认情况下,Pod运行没有CPU和内存的限额,这意味着系统中的任何Pod将能够像执行Pod所在节点机器一样,可以消耗足够多的CPU和内存,一般会针对某些应用的Pod资源进行资源限制,这个资源限制是通过resources的requests【要分配的资源】和limits【最大使用资源】来实现的。

资源的两种限制类型

Kubernetes采用request和limit两种限制类型来对资源进行分配

  1. request(资源需求):即运行Pod的节点必须满足运行Pod的最基本需求才能运行Pod
  2. limit(资源限额):即运行Pod期间,可能内存使用量会增加,那最多能使用多少内存,这就是资源限额

资源单位

CPU单位

CPU 的单位是核心数,内存的单位是字节。

​ 一个容器申请0.5个CPU,就相当于申请1个CPU的一半,你也可以加个后缀m,表示千分之一的概念。
比如说100m(100豪)的CPU等价于0.1个CPU。

内存单位
  • K、M、G、T、P、E   ##通常是以1000为换算标准的。
  • Ki、Mi、Gi、Ti、Pi、Ei   ##通常是以1024为换算标准的

pod资源限额设置

  • requests:最低配额,保证被调度的节点上至少有的资源配额
  • limits: 资源限制,容器可以分配到的最大资源配额
1
2
3
4
5
6
7
8
9
10
11
12
spec:
containers:
- name: [容器名称]
image: [容器镜像]
imagePullPolicy: IfNotPresent
resources:
requests:
cpu: 10m
memory: 10Mi
limits:
cpu: '1'
memory: 1000Mi

Memory内存限制

在容器里使用镜像stress进行压力测试,指定资源数进行压力测试

1
vi memory-demo.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
apiVersion: v1
kind: Pod
metadata:
name: memory-demo
spec:
containers:
- name: memory-demo
image: progrium/stress
args: #提供了容器启动的参数
- --vm
- "1" #模拟一个进程
- --vm-bytes
- 200M #每个进程使用200M内存
resources:
requests:
memory: 50Mi #最少需要50Mi内存
limits:
memory: 100Mi #最多100Mi
创建容器测试
1
2
kubectl apply -f memory-demo.yaml
kubectl get pod -o wide -w

可以看到状态变化如下,OMMKilled是超出内存限制的意思

image-20220526164059528

​ 我们要求启动时开一个使用200M内存的进程,可我们又限制最多100M,那么自然会超出限制,如果一个容器超过其内存请求,那么当节点内存不足时,它的 Pod 可能被逐出。

crashloopbackoff意味着pod挂掉又重启又挂掉如此往复。\
​ 如果容器超过其内存限制,则会被终止,如果可重新启动,则与所有其他类型的运行时故障一样,kubelet 将重新启动它。

CPU限制

创建一个用stress镜像的pod做cpu压测

1
vi cpu-demo.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
apiVersion: v1
kind: Pod
metadata:
name: cpu-demo
spec:
containers:
- name: cpu-demo
image: progrium/stress #使用stress镜像
resources:
limits:
cpu: "10" #至多使用10个CPU
requests:
cpu: "5" #最少用5个CPU
args: #提供了容器启动的参数
- -c #CPU
- "2" #指定容器启动时使用2个CPU
创建容器测试
1
2
kubectl apply -f cpu-demo.yaml
kubectl get pod -o wide -w

​ 应用文件出现如下状态,待调度,这是因为我们设置容器至少需要5个CPU,但我们根本没有5个CPU(我只给了虚拟机一个CPU)即没有符合条件的node,自然无法成功调度pod

image-20220526164708525

LimitRange

LimitRange 在 namespace 中施加的最小和最大内存限制只有在创建和更新 Pod 时才会被应用,改变 LimitRange 不会对之前创建的 Pod 造成影响

namespace设置资源限制

定义default这个namespaces下的资源限制,文件内容如下

1
vi limitrange-memory.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
apiVersion: v1
kind: LimitRange #资源类型
metadata:
name: limitrange-memory
spec:
limits:
- default: #当pod定义文件里没有指定资源数,在这个namespaces下最多使用0.5个cpu,512Mi内存
cpu: 0.5
memory: 512Mi
defaultRequest: #当pod定义文件里没有指定资源数,在这个namespaces下最少需要0.1个cpu,256Mi内存
cpu: 0.1
memory: 256Mi
max: #不管pod定义文件里有没有指定资源数,在这个namespaces下最多用1个cpu,1Gi内存
cpu: 1
memory: 1Gi
min: #不管pod定义文件里有没有指定资源数,在这个namespaces下最少需要0.1个cpu,100Mi内存
cpu: 0.1
memory: 100Mi
type: Container
创建LimitRange

应用文件创建出了属于default这个namespaces的资源限制

1
2
kubectl apply -f limitrange-memory.yaml # 生效limitrange配置
kubectl describe limitranges limitrange-memory # 查看配置详情

image-20220526165411060

编写内存限制测试POD

编写一个限制内存的创建pod的定义文件

1
vi nginx-pod.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.12
ports:
- containerPort: 80
resources:
requests:
memory: 50Mi #最少需要50Mi内存
limits:
memory: 100Mi #最多100Mi
运行POD

运行POD并查看状态

1
kubectl apply -f nginx-pod.yml

应用文件创建pod失败,是因为我们上面的limitrange的作用,最少要100Mi,而我们设定50Mi,所以出错了

image-20220526170028374

将文件中如下图部分注释掉即不指定资源限制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.12
ports:
- containerPort: 80
# resources:
# requests:
# memory: 50Mi #最少需要50Mi内存
# limits:
# memory: 100Mi #最多100Mi

继续创建POD,这次我们发现已经创建成功了

image-20220526170228972

查看POD详情

1
kubectl describe pod nginx

可以看到因为LimitRange的缘故自动加上了资源限制

image-20220526170402603

ResourceQuota

LimitRange是针对单个pod设置资源限制,ResourceQuota是针对所有pod设置的资源总额

​ 创建的ResourceQuota对象将在default名字空间中添加以下限制:每个容器必须设置内存请求(memory request),内存限额(memory limit),cpu请求(cpu request)和cpu限额(cpu limit)

namespace设置资源配额

定义default这个namespaces下的资源配额

1
vi resourcequota-mem-cpu-demo.yaml
1
2
3
4
5
6
7
8
9
10
apiVersion: v1
kind: ResourceQuota
metadata:
name: mem-cpu-demo
spec:
hard:
requests.cpu: "1" #所有容器的CPU请求总额不得超过1 CPU
requests.memory: 1Gi #所有容器的内存请求总额不得超过1 GiB
limits.cpu: "2" #所有容器的CPU限额总额不得超过2 CPU
limits.memory: 2Gi #所有容器的内存限额总额不得超过2 GiB
创建ResourceQuota

应用文件创建出了属于default这个namespaces的资源限制

1
2
kubectl apply -f resourcequota-mem-cpu-demo.yaml # 生效ResourceQuota配置
kubectl describe resourcequotas mem-cpu-demo # 查看配置详情

image-20220526180435164

编写限制测试POD

编写一个限制内存的创建pod的定义文件

1
vi nginx-pod.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.12
ports:
- containerPort: 80
resources:
requests:
cpu: 0.2
memory: 100Mi
limits:
cpu: 1
memory: 300Mi
运行POD

应用创建pod,可以看到资源配额已经用了多少

1
kubectl apply -f nginx-pod.yml

我们发现启动POD后已经消耗了一部分资源

image-20220526180806366

运行Deployment

我们启动多个POD来查看下全局消耗

1
vi nginx-pod.yml
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
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.12
ports:
- containerPort: 80
resources:
requests:
cpu: 0.2
memory: 100Mi
limits:
cpu: 1
memory: 300Mi
运行Deployment

应用创建Deployment,可以看到资源配额已经用了多少

1
kubectl apply -f nginx-pod.yml

我们发现启动Deployment有两个实例,正好消耗了两个实例的资源

image-20220526181442842

下面我们调整以下Deployment,将期望值设置为3,然后应用配置

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
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.12
ports:
- containerPort: 80
resources:
requests:
cpu: 0.2
memory: 100Mi
limits:
cpu: 1
memory: 300Mi
1
2
kubectl apply -f nginx-pod.yml
kubectl get deployment -o wide

应用配置后查看deployment的部署情况,我们发现只有两个节点,有一个没有被创建

image-20220526181832340

这是因为ResourceQuota是全局配置,我们的当前的namespace的CPU资源已经耗尽,无法在创建POD

评论