Kubernetes 质量管理
概述
背景
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服务质量一共有三个级别,如下图所示:
在 Kubernetes 中,QoS 主要分为
Guaranteed
、Burstable
和Best-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没有特别硬性的限制;
注意事项
- 宿主机只有2c,这里的limits可以设置为100c,但是毫无意义,只是可以这样设置。limits是最大可用资源,一般是requests的20%左右,不太超出太多,否则limits限制就没多少意义了;
- limits不能小于requests;
- reqeusts只是一个预留性质,不是pod配置写多少,宿主机就会占用多少资源;
- k8s根据request来统计每个节点预分配资源,来判断下一个pod能不能分配我这个节点;
- 所以,我们工作中,一定要配置requests和limits,来解决资源争抢问题;
- 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 | containers: |
因为如果一个容器只指明 limit 而未设定 requests,则 requests 的值等于 limit 值,所以上面 pod 的 QoS 级别属于 Guaranteed。
另外需要注意若容器指定了 requests 而未指定 limits,则 limits 的值等于节点资源的最大值;若容器指定了 limits 而未指定 requests,则 requests 的值等于 limits。
明确设置
另外一个就是 pod 中的所有容器都明确设置了 requests 和 limits,且单个容器内的
requests==limits
:
1 | containers: |
容器 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 | containers: |
情况2
或者容器 foo 设置了内存 limits,而容器 bar 设置了 CPU limits
1 | containers: |
Best-Effort(尽最大努力)
系统用完了全部内存时,该类型 pods 会最先被 kill 掉
如果 pod 中所有容器的 resources 均未设置 requests 与 limits,那么该 pod 的 QoS 即为 Best-Effort
情况1
比如容器 foo 和容器 bar 均未设置 requests 和 limits:
1 | containers: |
使用建议
如果资源充足,可将 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两种限制类型来对资源进行分配
- request(资源需求):即运行Pod的节点必须满足运行Pod的最基本需求才能运行Pod
- 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 | spec: |
Memory内存限制
在容器里使用镜像stress进行压力测试,指定资源数进行压力测试
1 | vi memory-demo.yaml |
1 | apiVersion: v1 |
创建容器测试
1 | kubectl apply -f memory-demo.yaml |
可以看到状态变化如下,OMMKilled是超出内存限制的意思
我们要求启动时开一个使用200M内存的进程,可我们又限制最多100M,那么自然会超出限制,如果一个容器超过其内存请求,那么当节点内存不足时,它的 Pod 可能被逐出。
crashloopbackoff
意味着pod挂掉又重启又挂掉如此往复。\
如果容器超过其内存限制,则会被终止,如果可重新启动,则与所有其他类型的运行时故障一样,kubelet 将重新启动它。
CPU限制
创建一个用stress镜像的pod做cpu压测
1 | vi cpu-demo.yaml |
1 | apiVersion: v1 |
创建容器测试
1 | kubectl apply -f cpu-demo.yaml |
应用文件出现如下状态,待调度,这是因为我们设置容器至少需要5个CPU,但我们根本没有5个CPU(我只给了虚拟机一个CPU)即没有符合条件的node,自然无法成功调度pod
LimitRange
LimitRange 在 namespace 中施加的最小和最大内存限制只有在创建和更新 Pod 时才会被应用,改变 LimitRange 不会对之前创建的 Pod 造成影响
namespace设置资源限制
定义default这个namespaces下的资源限制,文件内容如下
1 | vi limitrange-memory.yaml |
1 | apiVersion: v1 |
创建LimitRange
应用文件创建出了属于default这个namespaces的资源限制
1 | kubectl apply -f limitrange-memory.yaml # 生效limitrange配置 |
编写内存限制测试POD
编写一个限制内存的创建pod的定义文件
1 | vi nginx-pod.yml |
1 | apiVersion: v1 |
运行POD
运行POD并查看状态
1 | kubectl apply -f nginx-pod.yml |
应用文件创建pod失败,是因为我们上面的limitrange的作用,最少要100Mi,而我们设定50Mi,所以出错了
将文件中如下图部分注释掉即不指定资源限制
1 | apiVersion: v1 |
继续创建POD,这次我们发现已经创建成功了
查看POD详情
1 | kubectl describe pod nginx |
可以看到因为LimitRange的缘故自动加上了资源限制
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 | apiVersion: v1 |
创建ResourceQuota
应用文件创建出了属于default这个namespaces的资源限制
1 | kubectl apply -f resourcequota-mem-cpu-demo.yaml # 生效ResourceQuota配置 |
编写限制测试POD
编写一个限制内存的创建pod的定义文件
1 | vi nginx-pod.yml |
1 | apiVersion: v1 |
运行POD
应用创建pod,可以看到资源配额已经用了多少
1 | kubectl apply -f nginx-pod.yml |
我们发现启动POD后已经消耗了一部分资源
运行Deployment
我们启动多个POD来查看下全局消耗
1 | vi nginx-pod.yml |
1 | apiVersion: apps/v1 |
运行Deployment
应用创建Deployment,可以看到资源配额已经用了多少
1 | kubectl apply -f nginx-pod.yml |
我们发现启动Deployment有两个实例,正好消耗了两个实例的资源
下面我们调整以下
Deployment
,将期望值设置为3
,然后应用配置
1 | apiVersion: apps/v1 |
1 | kubectl apply -f nginx-pod.yml |
应用配置后查看deployment的部署情况,我们发现只有两个节点,有一个没有被创建
这是因为ResourceQuota是全局配置,我们的当前的namespace的CPU资源已经耗尽,无法在创建POD