8、高级调度准入控制
九、资源配额、资源限制、服务质量Qos
1. 节点可用性延伸
已经从多个维度保障了服务的可用性,比如调度到不同的机器和机房、配置可靠的健康检查等。但是上述措施都是基于应用级别去做的,如果我们的 Kubernetes 集群用来运行容器的节点有了故障,带来的影响是很大的,所以在保证应用本身的前提下,也要通过一些措施保障节点的可用性。
节点故障大部分都是由于资源分配不合理、超额分配引起的,因此需要用某个技术手段保证节点的资源不会过大地超额分配。
Kubernetes 为我们提供了开箱即用的资源管理,可以通过 **ResourceQuota **和 LimitRange 的配合防止节点资源超额分配。
2. 资源配额(ResourceQuota)
2.1 什么是资源配额
在生产环境中,可能会有多个Kubernetes集群,面向开发环境、测试环境、预生产环境和生产环境等。面对多k8s集群管理员,或者多项目组自己的命名空间进行操作时,并不知道Kubernetes集群是多大规模,也不知道有多少可调度的资源,所以在这样就很容易造成集群资源过量分配,引起集群不可用。
在这种情况下,需要对每个项目组合理地分配资源,用以避免超出集群的承载能力,也可以减少废弃资源没有及时清理带来的资源浪费。
资源配额的工作方式如下:
- 不同的团队可以在不同的命名空间下工作。这可以通过 RBAC强制执行。
- 集群管理员可以为每个命名空间创建一个或多个 ResourceQuota 对象。
- 当用户在命名空间下创建资源(如 Pod、Service 等)时,Kubernetes 的配额系统会跟踪集群的资源使用情况, 以确保使用的资源用量不超过 ResourceQuota 中定义的硬性资源限额。
- 如果资源创建或者更新请求违反了配额约束,那么该请求会报错(HTTP 403 FORBIDDEN), 并在消息中给出有可能违反的约束。
- 如果命名空间下的计算资源 (如 cpu 和 memory)的配额被启用, 则用户必须为这些资源设定请求值(request)和约束值(limit),否则配额系统将拒绝 Pod 的创建。 提示: 可使用 LimitRanger 准入控制器来为没有设置计算资源需求的 Pod 设置默认值。
2.2 ResourceQuota配置
可资源配额如下:
资源名称 | 描述 |
---|---|
limits.cpu | 所有非终止状态的 Pod,其所有Pod的CPU限额总量不能超过该值。 |
limits.memory | 所有非终止状态的 Pod,其所有Pod的内存限额总量不能超过该值。 |
requests.cpu | 所有非终止状态的 Pod,其所有Pod的CPU需求总量不能超过该值。 |
requests.memory | 所有非终止状态的 Pod,其所有Pod的内存需求总量不能超过该值。 |
configmaps | 在该命名空间中允许存在的 ConfigMap 总数上限。 |
persistentvolumeclaims | 在该命名空间中允许存在的 PVC的总数上限。 |
pods | 在该命名空间中允许存在的非终止状态的 Pod 总数上限。 Pod 终止状态等价于 Pod 的 .status.phase in (Failed, Succeeded) 为真。 |
replicationcontrollers | 在该命名空间中允许存在的 ReplicationController 总数上限。 |
resourcequotas | 在该命名空间中允许存在的 ResourceQuota 总数上限。 |
services | 在该命名空间中允许存在的 Service 总数上限。 |
services.loadbalancers | 在该命名空间中允许存在的 LoadBalancer 类型的 Service 总数上限。 |
services.nodeports | 在该命名空间中允许存在的 NodePort 类型的 Service 总数上限。 |
secrets | 在该命名空间中允许存在的 Secret 总数上限。 |
[root@k8s-master01 resource]# cat resourceQuota.yaml
apiVersion: v1
kind: ResourceQuota
metadata:
name: resource-test
labels:
app: resourcequota
spec:
hard:
pods: 2
requests.cpu: 0.5
requests.memory: 512Mi
limits.cpu: 5
limits.memory: 16Gi
configmaps: 2
requests.storage: 40Gi
persistentvolumeclaims: 20
replicationcontrollers: 20
secrets: 20
services: 50
services.loadbalancers: "2"
services.nodeports: "10"
创建命名空间,并创建ResourceQuota限额
kubectl create namespace rq-test
kubectl create -f resourceQuota.yaml -n rq-test
查看该命名空间的限额与使用情况。
从 Kubernetes v1.9 开始,支持使用如下格式的限定名称空间中标准类型对象的总数量:
- count/persistentvolumeclaims
- count/services
- count/secrets
- count/configmaps
- count/replicationcontrollers
- count/deployments.apps
- count/replicasets.apps
- count/statefulsets.apps
- count/jobs.batch
- count/cronjobs.batch
- count/deployments.extensions
#示例
apiVersion: v1
kind: ResourceQuota
metadata:
name: resource-test
labels:
app: resourcequota
spec:
hard:
count/services: 2
count/deployments.apps: 2
Tips:ResourceQuota作用于Pod,并且有命名空间限制!
3. 资源限制(LimitRange)
3.1 什么是资源限制
和 ResourceQuota 不同的是,LimitRange 用来配置默认值,也就是一个 Pod 如果没有配置要用多少内存、CPU,LimitRange会在创建Pod时添加一个默认值。
可能出现的情况:
- 如果创建了一个 Pod 或 Deployment 没有指定 requests 和 limits 字段,是不是就意味着资源配额对内存和CPU的限制变成了一个摆设,在CPU和内存为0时无限制地创建 Pod,从而造成无法统计的情况。
- 假如一个 Namespace 分配了16核、64GB的空间,之后创建一个申请了requests.cpu为16、requests.memory为64GB的容器,那么单个Pod就能把整个Namespace的资源全部占用。
为了防止这类情况发生,Kubernetes 又引出了另一个概念:LimitRange,用于针对没有配置 requests 和 limits 的资源设置一个默认值,同时配置单个资源最大的 requests 和 limits,这样就能解决上述问题。
Tips:LimitRange不会影响已经创建的资源!
3.2 LimitRange配置
可以通过LimitRange配置默认的requests和limits值,用来解决创建的资源没有配置或配置过小的requests和limits带来的问题。
一个 LimitRange(限制范围) 对象提供的限制能够做到:
- 在一个命名空间中实施对每个Pod最小和最大的资源使用量的限制。
- 在一个命名空间中实施对每个 PersistentVolumeClaim能申请的最小和最大的存储空间大小的限制。
比如创建一个requests.cpu默认为0.5(0.5为半颗CPU,1个CPU等于1000m)、requests.memory为256MB、limits.cpu为1、limits.memory为512MB,限制PVC申请空间的最小值为1GB、最大值为2GB的LimitRange。
apiVersion: v1
kind: LimitRange
metadata:
name: cpu-mem-limit-range
spec:
limits:
- default: #默认limits配置
cpu: 1
memory: 512Mi
defaultRequest: #默认Request配置
cpu: 0.5
memory: 256Mi
type: Container
- type: PersistentVolumeClaim #默认PVC设置
max:
storage: 2Gi
min:
storage: 1Gi
Tips:LimitRange作用于Pod,并且有命名空间限制!
4. 服务质量
QoS
是 Quality of Service
的缩写,即服务质量,为了实现资源被有效调度和分配的同时提高资源利用率,Kubernetes 针对不同服务质量的预期,通过 QoS 来对 pod 进行服务质量管理,对于一个 pod 来说,服务质量体现在两个具体的指标:CPU 和内存。当节点上内存资源紧张时,Kubernetes 会根据预先设置的不同 QoS 类别进行相应处理。
4.1 资源限制
如果未做过节点 nodeSelector、亲和性(node affinity)或 pod 亲和、反亲和性等高级调度策略设置,我们没有办法指定服务部署到指定节点上,这样就可能会造成 CPU 或内存等密集型的 pod 同时分配到相同节点上,造成资源竞争。另一方面,如果未对资源进行限制,一些关键的服务可能会因为资源竞争因 OOM (Out Of Memory) 等原因被 kill 掉,或者被限制 CPU 使用。
我们知道对于每一个资源,container 可以指定具体的资源需求(requests)和限制(limits),requests 申请范围是0到节点的最大配置,而 limits 申请范围是 requests 到无限,即 0 <= requests <= Node Allocatable
, requests <= limits <= Infinity
。
对于 CPU,如果 pod 中服务使用的 CPU 超过设置的 limits,pod 不会被 kill 掉但会被限制,因为 CPU 是可压缩资源,如果没有设置 limits,pod 可以使用全部空闲的 CPU 资源。
对于内存,当一个 pod 使用内存超过了设置的 limits,pod 中容器的进程会被 kernel 因 OOM kill 掉,当 container 因为 OOM 被 kill 掉时,系统倾向于在其原所在的机器上重启该 container 或本机或其他重新创建一个 pod。
4.2 QoS 分类
Kubelet 提供 QoS 服务质量管理,支持系统级别的 OOM 控制。在 Kubernetes 中,QoS 主要分为 Guaranteed
、Burstable
和 Best-Effort
三类,优先级从高到低。
QoS 分类并不是通过一个配置项来直接配置的,而是通过配置 CPU/内存的 limits 与 requests 值的大小来确认服务质量等级的,我们通过使用 kubectl get pod xxx -o yaml
可以看到 pod 的配置输出中有 qosClass
一项,该配置的作用是为了给资源调度提供策略支持,调度算法根据不同的服务质量等级可以确定将 pod 调度到哪些节点上。
4.2.1 Guaranteed(有保证的)
系统用完了全部内存,且没有其他类型的容器可以被 kill 时,该类型的 pods 会被 kill 掉,也就是说最后才会被考虑 kill 掉,
- pod 中的所有容器都且仅设置了 CPU 和内存的 limits
- pod 中的所有容器都设置了 CPU 和内存的 requests 和 limits ,且单个容器内的
requests==limits
(requests不等于0)
pod 中的所有容器都且仅设置了 limits:
apiVersion: v1
kind: Pod
metadata:
name: qos-demo
namespace: qos-example
spec:
containers:
- name: foo
image: nginx
resources:
limits:
cpu: 100m
memory: 100Mi
- name: bar
image: nginx
resources:
limits:
cpu: 100m
memory: 100Mi
因为如果一个容器只指明 limit 而未设定 requests,则 requests 的值等于 limit 值,所以上面 pod 的 QoS 级别属于 Guaranteed。
另外一个就是 pod 中的所有容器都明确设置了 requests 和 limits,且单个容器内的 requests==limits
:
apiVersion: v1
kind: Pod
metadata:
name: qos-demo
namespace: qos-example
spec:
containers:
- name: foo
image: nginx
resources:
limits:
cpu: 10m
memory: 1Gi
requests:
cpu: 10m
memory: 1Gi
- name: bar
resources:
image: nginx
limits:
cpu: 100m
memory: 100Mi
requests:
cpu: 100m
memory: 100Mi
容器 foo 和 bar 内 resources 的 requests 和 limits 均相等,该 pod 的 QoS 级别属于 Guaranteed。
4.2.2 Burstable(不稳定的)
系统用完了全部内存,且没有 Best-Effort 类型的容器可以被 kill 时,该类型的 pods 会被 kill 掉。pod 中只要有一个容器的 requests 和 limits 的设置不相同,该 pod 的 QoS 即为 Burstable。
比如容器 foo 指定了 resource,而容器 bar 未指定:
containers:
name: foo
resources:
limits:
cpu: 10m
memory: 1Gi
requests:
cpu: 10m
memory: 1Gi
name: bar
或者容器 foo 设置了内存 limits,而容器 bar 设置了 CPU limits:
containers:
name: foo
resources:
limits:
memory: 1Gi
name: bar
resources:
limits:
cpu: 100m
上面两种情况定义的 pod 都属于 Burstable 类别的 QoS。另外需要注意若容器指定了 requests 而未指定 limits,则 limits 的值等于节点资源的最大值;若容器指定了 limits 而未指定 requests,则 requests 的值等于 limits。
4.2.3 Best-Effort(尽最大努力)
系统用完了全部内存时,该类型 pods 会最先被 kill 掉。如果 pod 中所有容器的 resources 均未设置 requests 与 limits,那么该 pod 的 QoS 即为 Best-Effort。
比如容器 foo 和容器 bar 均未设置 requests 和 limits:
containers:
name: foo
resources:
name: bar
resources:
4.3 QoS 解析
首先我们要明确在调度时调度器只会根据 requests 值进行调度。当系统 OOM 上时对于处理不同 OOMScore 的进程表现不同,OOMScore 是针对 memory 的,当宿主上 memory 不足时系统会优先 kill 掉 OOMScore 值高的进程,可以使用 cat /proc/$PID/oom_score
命令查看进程的 OOMScore。OOMScore 的取值范围为 [-1000, 1000]
,Guaranteed 类型的 pod 的默认值为 -998,Burstable pod 的值为 2~999
,BestEffort pod 的值为 1000,也就是说当系统 OOM 时,首先会 kill 掉 BestEffort pod 的进程,若系统依然处于 OOM 状态,然后才会 kill 掉 Burstable pod,最后是 Guaranteed pod。
Kubernetes 是通过 cgroup 给 pod 设置 QoS 级别的,kubelet 中有一个 --cgroups-per-qos
参数(默认启用),启用后 kubelet 会为不同 QoS 创建对应的 level cgroups,在 Qos level cgroups 下也会为 pod 下的容器创建对应的 level cgroups,从 Qos –> pod –> container
,层层限制每个 level cgroups 的资源使用量。由于我们这里使用的是 containerd 这种容器运行时,则 cgroup 的路径与之前的 docker 不太一样:
- Guaranteed 类型的 cgroup level 会直接创建在
RootCgroup/system.slice/containerd.service/kubepods-pod<uid>.slice:cri-containerd:<container-id>
下 - Burstable 的创建在
RootCgroup/system.slice/containerd.service/kubepods-burstable-pod<uid>.slice:cri-containerd:<container-id>
下 - BestEffort 类型的创建在
RootCgroup/system.slice/containerd.service/kubepods-besteffort-pod<uid>.slice:cri-containerd:<container-id>
下
我们可以通过 mount | grep cgroup
命令查看 RootCgroup:
➜ mount | grep cgroup
tmpfs on /sys/fs/cgroup type tmpfs (ro,nosuid,nodev,noexec,mode=755)
cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,xattr,release_agent=/usr/lib/systemd/systemd-cgroups-agent,name=systemd)
cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio)
cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)
cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpuacct,cpu)
cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)
cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,nosuid,nodev,noexec,relatime,perf_event)
cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices)
cgroup on /sys/fs/cgroup/pids type cgroup (rw,nosuid,nodev,noexec,relatime,pids)
cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,nosuid,nodev,noexec,relatime,hugetlb)
cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (rw,nosuid,nodev,noexec,relatime,net_prio,net_cls)
cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer)
在 cgroup 的每个子系统下都会创建 QoS level cgroups, 此外在对应的 QoS level cgroups 还会为 pod 创建 Pod level cgroups。比如我们创建一个如下所示的 Pod:
# qos-demo.yaml
apiVersion: v1
kind: Pod
metadata:
name: qos-demo
spec:
containers:
- name: nginx
image: nginx:latest
resources:
requests:
cpu: 250m
memory: 1Gi
limits:
cpu: 500m
memory: 2Gi
直接创建上面的资源对象即可:
➜ kubectl apply -f qos-demo.yaml
➜ kubectl get pods qos-demo -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
qos-demo 1/1 Running 0 2m49s 10.244.1.29 node1 <none> <none>
➜ kubectl get pods qos-demo -o yaml |grep uid
uid: 489a19f2-8d75-474c-988f-5854b61b839f
➜ kubectl get pods qos-demo -o yaml |grep qosClass
qosClass: Burstable
由于该 pod 的设置的资源 requests != limits,所以其属于 Burstable 类别的 pod,kubelet 会在其所属 QoS 下创建 RootCgroup/system.slice/containerd.service/kubepods-burstable-pod<uid>.slice:cri-containerd:<container-id>
这个 cgroup level,比如我们查看内存这个子系统的 cgroup:
# 还有一个 pause 容器的 cgroup level
➜ ls /sys/fs/cgroup/memory/system.slice/containerd.service/kubepods-burstable-pod489a19f2_8d75_474c_988f_5854b61b839f.slice:cri-containerd:4782243ba3260125513af20689fcea31b52eae1cbabeafeb1f7a52bcdcd5b44b
cgroup.clone_children memory.kmem.tcp.max_usage_in_bytes memory.oom_control
cgroup.event_control memory.kmem.tcp.usage_in_bytes memory.pressure_level
cgroup.procs memory.kmem.usage_in_bytes memory.soft_limit_in_bytes
memory.failcnt memory.limit_in_bytes memory.stat
memory.force_empty memory.max_usage_in_bytes memory.swappiness
memory.kmem.failcnt memory.memsw.failcnt memory.usage_in_bytes
memory.kmem.limit_in_bytes memory.memsw.limit_in_bytes memory.use_hierarchy
memory.kmem.max_usage_in_bytes memory.memsw.max_usage_in_bytes notify_on_release
memory.kmem.slabinfo memory.memsw.usage_in_bytes tasks
memory.kmem.tcp.failcnt memory.move_charge_at_immigrate
memory.kmem.tcp.limit_in_bytes memory.numa_stat
上面创建的应用容器进程 ID 会被写入到上面的 tasks 文件中:
➜ cat tasks
64133
64170
64171
64172
64173
➜ ps -aux |grep nginx
root 64133 0.0 0.0 8840 3488 ? Ss 15:56 0:00 nginx: master process nginx -g daemon off;
101 64170 0.0 0.0 9228 1532 ? S 15:56 0:00 nginx: worker process
101 64171 0.0 0.0 9228 1532 ? S 15:56 0:00 nginx: worker process
101 64172 0.0 0.0 9228 1532 ? S 15:56 0:00 nginx: worker process
101 64173 0.0 0.0 9228 1532 ? S 15:56 0:00 nginx: worker process
这样我们的容器进程就会受到该 cgroup 的限制了,在 pod 的资源清单中我们设置了 memory 的 limits 值为 2Gi,kubelet 则会将该限制值写入到 memory.limit_in_bytes
中去:
➜ cat memory.limit_in_bytes
2147483648 # 2147483648 / 1024 / 1024 / 1024 = 2
同样对于 cpu 资源一样可以在对应的子系统中找到创建的对应 cgroup:
➜ ls /sys/fs/cgroup/cpu/system.slice/containerd.service/kubepods-burstable-pod489a19f2_8d75_474c_988f_5854b61b839f.slice:cri-containerd:4782243ba3260125513af20689fcea31b52eae1cbabeafeb1f7a52bcdcd5b44b
cgroup.clone_children cpuacct.stat cpu.cfs_period_us cpu.rt_runtime_us notify_on_release
cgroup.event_control cpuacct.usage cpu.cfs_quota_us cpu.shares tasks
cgroup.procs cpuacct.usage_percpu cpu.rt_period_us cpu.stat
➜ cat tasks
64133
64170
64171
64172
64173
➜ cat cpu.cfs_quota_us
50000 # 500m
最后关于 QoS 还有一点建议,如果资源充足,可将 QoS pods 类型均设置为 Guaranteed。用计算资源换业务性能和稳定性,减少排查问题时间和成本。如果想更好的提高资源利用率,业务服务可以设置为 Guaranteed,而其他服务根据重要程度可分别设置为 Burstable 或 Best-Effort。
注:本篇学习笔记内容参考杜宽的《云原生Kubernetes全栈架构师》,视频、资料文档等,大家可以多多支持!还有YinJayChen语雀、k8s训练营、“我为什么这么菜”知乎博主等资料文档,感谢无私奉献!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?