Kubernetes 资源管理

一、ResourceQuota

首先看一下 ResourceQuota(资源配额)的使用,资源配额是限制某个命名空间对资源使用的一个总量限制,比如内存、CPU、Pod 数量等。

1. 什么是资源配额

在生产环境中,可能会有多个 Kubernetes 集群,面向开发环境、测试环境、预生产环境和生产环境等。身为 Kubernetes 管理员,必然知道每个环境的规模有多大、可调度资源有多少,并且知道如何合理地为容器分配内存和 CPU,所以一个管理员去管理整个 Kubernetes 集群时,很少会有资源分配超出集群可调度范围的情况。

但在实际使用时,Kubernetes 集群并非只有一个管理员在使用,也并非只有管理员在使用。公司可能存在多个项目组,每个项目组都有属于自己的命名空间,每个项目组可以在其所在的命名空间创建资源,而他们并不知道 Kubernetes 集群是多大规模,也不知道有多少可调度的资源,这样就很容易造成集群资源过量分配,引起集群不可用。在这种情况下,需要对每个项目组合理地分配资源用以避免超出集群的承载能力,也可以减少废弃资源没有及时清理带来的资源浪费。

为了解决上述问题,Kubernetes 引入了 ResourceQuota 的概念,以方便 Kubernetes 管理员进行资源分配,比如给 A 项目组分配 16 核 64GB 的资源,并且最多只能部署 20 个 Pod、30 个 Service 等,这样来对 Kubernetes 的各类资源进行限制。

2. 定义一个 ResourceQuota

和其他资源配置方法一样,资源配额也可以通过一个 YAML 文件进行创建,比如定义一个比较常用的 ResourceQuota 如下:

[root@k8s-master~]#vim resourcequota.yaml
apiVersion: v1
kind:ResourceQuota
metadata:
name: resource-test
labels:
app: resourcequota
spec:
hard:
pods:50 ##限制最多启动Pod的个数
requests.cpu:0.5 ##初始要求的cpu数量
requests.memory:512Mi ##初始要求的内存量
limits.cpu:5 ##所能使用的最大cpu数量
limits.memory:16Gi ##所能使用的最大内存量
configmaps:20
requests.storage:40Gi
persistentvolumeclaims:20
replicationcontrollers:20
secrets:20
services:50
services.loadbalancers:"2"
services.nodeports:"10"

3. ResourceQuota 的使用


(1)创建一个用于测试的 namespace

[root@k8s-master ~]#kubectl create ns quota-example

(2)创建一个测试 ResourceQuota


该 demo 可以限制 namespace 的 PVC 不能超过 1 个

[root@k8s-master ~]#vim quota-objects.yaml
apiversion: v1
kind:ResourceQuota
metadata:
name:object-quota-demo
spec:
hard:
persistentvolumeclaims:

(3)创建该 ResourceQuota

[root@k8s-master~]#kubectl create -f quota-objects.yaml -n quota-example

(4)查看创建的资源限制状态

[root@k8s-master ~]#kubectl get quota object-quota-demo -n quota-example
NAME AGE REQUEST LIMIT
object-quota-demc 114s persistentvolumeclaims: 0/1
[root@k8s-master ~]#kubectl get quota object-quota-demo -n quota-example -oyaml
apiVersion: v1
kind:ResourceQuota
metadata:
creationTimestamp:"2023-08-19T08:10:19Z"
name:object-quota-demo
namespace:quota-example
resourceVersion:"8831"
uid:47d2980c-bf9c-407f-a146-3f47e34372d2
spec:
hard:
persistentvolumeclaims:"1"
status:
hard:
persistentvolumeclaims:"1"
used:
persistentvolumeclaims:"0"

备注:
从 status 字段的 used 看出,当前的资源显示的使用量,并且 namespace 只有在创建了 ResourceQuota 才会启用资源使用的配额,没有创建 ResourceQuota 的 namespace 不限制资源的使用。

(5)创建一个 PVC

[root@k8s-master ~]#vim pvc.yaml
apiVersion:v1
kind: PersistentVolumeClaim
metadata:
name:pvc-quota-demo
spec:
storageClassName:manual
accessModes:
ReadWriteOnce
resources:
requests:
storage:3Gi
[root@k8s-master~]#kubectl create -f pvc.yaml -n quota-example

(6)查看当前资源使用情况

[root@k8s-master~]#kubectl get quota object-quota-demo -n quota-example -oyaml
apiVersion: v1
kind:ResourceQuota
metadata:
creationTimestamp:"2023-08-19T08:10:19Z"
name:object-quota-demo
namespace:quota-example
resourceVersion:"9423"
uid: 47d2980c-bf9c-407f-a146-3f47e34372d2
spec:
hard:
persistentvolumeclaims:
status:
hard:
persistentvolumeclaims:"1"
used:
persistentvolumeclaims:"1"

(7)再次创建一个 PVC

[root@k8s-master ~]#vim pvc2.yaml
apiVersion:v1
kind:PersistentVolumeClaim
metadata:
name:pvc-quota-demo2
spec:
storageClassName: manual
accessModes:
ReadWriteOnce
resources:
requests:
storage:3Gi
[root@k8s-master ~]#kubectl create-f pvc2.yaml -n quota-example
Error from server (Forbidden):error when creating"pvc2.yaml":persistentvolumeclaims
"pc-quota-emo2is forbidden:exceededquota: object-quota-demo,requested:
persistentvolumeclaims=1, used: persistentvolumeclaims=1, limited:
persistentvolumeclaims=1

此时已经无法在这个 namespace 中创建 PVC,其他资源的限制与之类似。

(8)环境清理

[root@k8s-master ~]#kubectl delete-f pvc.yaml -n quota-example 
[root@k8s-master~]#kubectl delete ns quota-example

二、LimitRange

和 ResourceQuota 不同的是,LimitRange 用来配置默认值,也就是一个 Pod 如果没有配置要用多少内存、CPU,那么 LimitRange 会在创建 Pod 时添加一个默认值。

1. LimitRange 的用途

上一节讲解了每个命名空间的最大资源使用量,细心的话我们可能会发现,如果创建了一个 Pod 或 Deployment 没有指定 requests 和 limits 字段,是不是就意味着资源配额对内存和 CPU 的限制变成了一个摆设?答案是肯定的,这时 CPU 和内存永远不会被限制。还有另一种情况,假如一个 Namespace 分配了 16 核、64GB 的空间,之后创建一个申请了 requests.cpu 为 16、requests.memory 为 64GB 的容器,那么单个 Pod 就能把整个 Namespace 的资源全部占用。

为了防止这类情况发生,Kubernetes 又引出了另一个概念:LimitRanger,用于针对没有配置 requests 和 limits 的资源设置一个默认值,同时配置单个资源最大的 requests 和 Limits,这样就能解决上述问题(注意:LimitRanger 不会影响已经创建的资源)。

2. 示例 1:配置默认的 requests 和 limits

可以通过 LimitRanger 配置默认的 requests 和 limits 值,用来解决创建的资源没有配置或配置过小的 requests 和 Limits 带来的问题,比如创建一个 requests.cpu 默认为 0.5(0.5 为半颗 CPU,1 个 CPU 等于 1000m)、requests.memory 为 256MB、limits.cpu 为 1、limits.memory 为 512MB 的 LimitRanger:

(1)创建一个 limitRange

[root@k8s-master ~]#vim limitrange01.yaml
apiVersion:v1
kind:LimitRange
metadata:
name:cpu-mem-limit-range
spec:
limits:
default:#当没有显式指定资源限制(limit最大值)时,为新创建的对象设置的默认资源限制
cpu:1#默认的CPU限制为1个单位。
memory: 512Mi #默认的内存限制为512MiB。
defaultRequest: #当没有明确指定资源请求时,为新创建的对象设置的默认资源请求值
cpu:0.5 #默认的CPU请求为0.5个单位。
memory:256Mi #默认的内存请求为256MiB
type:Container
apiVersion: v1
kind:Pod
metadata:
name:default-cpu-demo
spec:
containers:
name:default-cpu-demo-ctr
image: nginx
[root@k8s-master ~]#kubectl create-flimitrange01.yaml

(2)查看 pod 信息

[root@k8s-master ~]#kubectl get pod default-cpu-demo -oyaml
以下为部分信息
containers:
-image:nginx
imagePullPolicy: Always
name:default-cpu-demo-ctr
resources:
limits:
cpu:"1"
memory: 512Mi
requests:
cpu:500m
memory:256Mi

(3)清除资源

[root@k8s-master ~]#kubectl delete-flimitrange01.yaml

3. 示例 2:配置 requests 和 limits 的范围

上述针对没有设置 requests 和 limits 字段的资源添加了默认值,但是并没有限制 requests 和 limits 的最大值和最小值,这样同样会给集群带来风险,所以在管理资源分配时,对 requests 和 limits 的最大值和最小值也需要进行管控。

requests 和 limits 的最大值和最小值的配置方式和默认值的配置差别不大,比如创建一个内存最小值为 500MB、最大值为 1GB 和 CPU 最小值为 200MB、最大值为 800MB 的 LimitRanger。

(1)创建 LimitRanger 的 yaml 文件

[root@k8s-master ~]#cat limitrange02.yaml
apiVersion:v1
kind:LimitRange
metadata:
name:cpu-min-max-demo-lr
spec:
limits:
max:
cpu:"800m"
memory:1Gi
min:
cpu:"200m"
memory:"500Mi"
type: Container
apiVersion: v1
kind:Pod
metadata:
name:constraints-mem-demo-2
spec:
containers:
-name:constraints-mem-demo-2-ctr
image: nginx
resources:
limits:
memory:"500Mi
requests:
memory:"500Mi"

备注:
内存最小值为 500M,最大为 1G
CPU 最小为 200m,最大为 800m
limits:分配给 pod 的资源上限
requests:分配给 pod 的初始量

(2)创建这个 LimitRange 和 Pod

[root@k8s-master ~]#kubectl create-flimitrange02.yaml

(3)测试

[root@k8s-master ~]#kubectl get pod constraints -mem-demo-2 -oyaml
可以调整Pod中的内存大小,重新创建这个资源,观察效果

(4)清除资源

[root@k8s-master~]#kubectl delete -flimitrange02.yaml

4. 示例 3:限制申请存储空间的大小

上述讲解的是对 CPU 和内存的限制,同样 LimitRanger 也可以对存储申请的大小进行限制,比如限制 PVC 申请空间的最小值为 1GB、最大值为 2GB(结合 ResourceQuota 可以同时限制最多存储使用量和最大 PVC 创建的数量):

[root@k8s-master~]#cat limitrange03.yaml
apiVersion:v1
kind:LimitRange
metadata:
name: storagelimits
spec:
limits:
type:PersistentVolumeClaim
max:
storage:2Gi
min:
storage:1Gi
[root@k8s-master ~]#ku create -f limitrange03.yaml
[root@k8s-master ~]#ku create-f hostpath-pv.yaml
[root@k8s-master ~]#ku create -f pvc-hostpath.yaml
Error from server (Forbidden):error when creating"pvc-hostpath.yaml":
persistentvolumeclaims "mypvc-hostpath"is forbidden: maximum storage usage per
PersistentVolumeClaim is 2Gi,but request is 3Gi

三、QoS

虽然我们进行了资源限制,但是实际使用时依旧会造成节点资源不足,针对资源不足 Kubernetes 会通过重启或驱逐 Pod 释放资源,再重启时,难免会造成一些很重要的服务不可用。但实际情况可能是,如果重启或驱逐一些不重要的 Pod 可能会更好,而这种决策是通过 QoS(Quality of Service,服务质量)决定的,所以在生产环境中,QoS 是一个非常重要的环节。

1. 什么是服务质量保证

假设我们有一个 java 进程,需要的 CPU 最低为 1 核,内存最低为 2G 才能正常启动,并且需要 10 个副本才能支撑业务量。此时我们需要创建一个 Deployment 并设置副本为 10、requests.cpu 为 1 和 requests.memory 为 2GB。创建资源时,Scheduler 会根据一系列算法将该 Pod 部署到合适的节点上,当然需要被调度节点最少有 1 核、2GB 的空间资源。看似一个再简单不过的部署,如果不设置 requests 字段会如何呢?

如果未设置 requests 字段,那么 Scheduler 在调度时将不再检查 Node 节点上的资源是否满足该应用程序的最低需求,因为未配置 requests 字段,Scheduler 将随机部署至其他节点,包括已经没有资源的机器。如果部署到了一个没有资源的宿主机上,在该应用程序启动时可能就会引起容器一直无法启用,从而进入恶性循环。而且如果此时 10 个副本中有 3 个副本都部署到了一个没有多少资源的宿主机上,3 个容器就会引起资源竞争,到最后一个也无法启动,并且在启动过程中程序会占用非常高的 CPU,可能会导致上面正在运行的 Pod 和宿主机不可用,此时很大程度上会引起雪崩,所以设置合理的 requests 和 limits 是非常重要的事情。

我们设置了 requests 和 limits 就能万无一失吗?答案当然是不可能的。在使用 Kubernetes 部署时,应用的部署和更新都会经过一系列的调度策略将应用部署在最合适的节点上,但是随着时间的推移,当时 “最优” 的节点可能已经不再是最佳选择,因为在该服务器上别的应用或者其他管理员部署的应用可能忘记了配置资源限制,所以在日积月累的消耗中,宿主机一些不可压缩的资源(比如内存、磁盘)的使用率将达到最高峰。假如内存达到最高峰时会引起 OOMKilled 故障(OOMKilled 即为内存杀手),此时 Kubelet 会根据某些策略重启上面的容器用来避免宿主机宕机引来的风险,但是重启容器难免会带来服务中断的现象,如果重启的是比较重要的业务应用,这将是一个非常不好的体验。那么我们如何在系统资源不够的情况下尽量保证一些比较重要的 Pod 不被杀死呢?

前面讲了这么多主要是为了引出本节的主角:QoS;我们可以用 QoS 来提高某些应用的服务质量,以保证宿主机资源不够时尽可能保证某些应用不被杀死。

Kubernetes 为我们提供了 3 种级别的服务质量,分别是:

  • Guaranteed:最高服务质量,当宿主机内存不够时,会先杀死 QoS 为 BestEffort 和 Burstable 的 Pod,如果内存还是不够,才会杀死 QoS 为 Guaranteed 的 Pod,该级别 Pod 的资源占用量一般比较明确,即 requests 字段的 cpu 和 memory 与 limits 字段的 cpu 和 memory 配置的一致。
  • Burstable:服务质量低于 Guaranteed,当宿主机内存不够时,会先杀死 QoS 为 BestEffort 的 Pod,如果内存还是不够,就会杀死 QoS 级别为 Burstable 的 Pod,用以保证 QoS 质量为 Guaranteed 的 Pod,该级别的 Pod 一般知道最小资源使用量,但是当机器资源充足时,还是想尽可能使用更多的资源,即 limits 字段的 cpu 和 memory 大于 requests 字段的 cpu 和 memory 的配置。
  • BestEffort:尽力而为,当宿主机内存不够时,首先杀死的就是该 QoS 的 Pod,用以保证 Burstable 和 Guaranteed 级别的 Pod 正常运行。

实现不同级别的服务质量是根据 requests 和 limits 的配置决定的,在宿主机资源不够时会先杀死服务质量为 BestEffort 的 Pod,然后杀死服务质量为 Burstable 的 Pod,最后杀死服务质量为 Guaranteed 的 Pod。所以在生产环境中比较重要的应用最好设置为 Guaranteed,当然如果集群资源足够使用,可以都设置为 Guaranteed。接下来看一下如何实现这 3 种 QoS3。

2: 首先创建一个用于测试的 Namespace

[root@k8s-master~]#kubectl create namespace qos-example

(1) 实现 QoS 为 Guaranteed 的 Pod


创建此类型的 Pod 需要满足的条件如下:

  • Pod 中的每个容器必须指定 limits.memory 和 requests.memory,并且两者需要相等
  • Pod 中的每个容器必须指定 limits.cpu 和 requests.cpu,并且两者需要相等
[root@k8s-master~]#vim qos-pod.yaml
apiVersion:v1
kind:Pod
metadata:
name:qos-demo 
namespace:qos-example 
spec: 
containers: 
name:qos-demo-ctr 
image:nginx:1.7.9 
resources: 
limits: 
memory:"200Mi" 
cpu:"700m" 
requests: 
memory:"200Mi" 
cpu: "700m"
[root@k8s-master ~]#kubectl create-f qos-pod.yaml -n qos-example
[root@k8s-master~]#kubectl get pod qos-demo -n qos-example -oyaml
….
podIPs:
-ip:10.244.85.203
qosClass: Guaranteed
startTime:"2023-08-26T03:31:48Z"

(2) 实现 QoS 为 Burstable 的 Pod


创建一个 QoS 为 Burstable 的 Pod 需要满足以下条件:

  • Pod 不符合 Guaranteed 的配置要求
  • Pod 中至少有一个容器配置了 requests.cpu 或者 requests.memory
[root@k8s-master ~]#vim qos-pod-2.yaml
apiVersion: v1
kind:Pod
metadata:
name:qos-demo-2
namespace:qos-example
spec:
containers:
name:qos-demo-2-ctr
image:nginx:1.7.9
resources:
limits:
memory:"200Mi"
requests:
memory:"100Mi"
[root@k8s-master ~]# kubectl create -f qos-pod-2.yaml-n qos-example
[root@k8s-master ~]#kubectl get pod qos-demo-2 -n qos-example -oyaml
….
podIPs:
-ip:10.244.58.195
qosClass:Burstable
startTime:"2023-08-26T04:12:39Z"

(3) 实现 QoS 为 BestEffort 的 Pod


创建一个 QoS 为 BestEffort 的 Pod 更为简单,Pod 中的所有容器都没有设置 requests 和 limits 字段即可

[root@k8s-master~]#vim qos-pod-3.yaml
apiVersion:v1
kind:Pod
metadata:
name:qos-demo-3
namespace:qos-example
spec:
containers:
-name:qos-demo-3-ctr
image: nginx:1.7.9
[root@k8s-master~]#kubectl create -f qos-pod-3.yaml-n qos-example
[root@k8s-master~]#kubectl get pod qos-demo-3-n qos-example -o yaml
podIPs:
-ip:10.244.58.196
qosClass: BestEffort
startTime:"2023-08-26T04:24:13Z"

四:小结

提升服务可用性只从应用程序级别考虑是远远不够的,也需要考虑服务器的稳定性。在使用 Kubernetes 部署程序时,很容易造成资源的过量分配,作为 Kubernetes 管理员,必须要去考虑服务器的可用性,防止服务器宕机带来的雪崩。

通常情况下,依赖的底层中间件的 QoS 会配置为 Guaranteed,其他服务可能并不需要那么高的 QoS。在实际使用时,最有可能超额分配的是内存,而 CPU 通常使用率不高,只有在高频工作时,CPU 才会处于忙碌状态。所以在设置 resources 参数时,内存的 request 需要按需配置,比如一个程序的运行内存最少为 2GB,那么内存的 request 就要设置为 2GB,而不能低于 2GB。对于 CPU,Limit 参数很重要,防止 Pod 的 CPU 过高,从而引起宿主机的问题。

所以在生产环境部署一个程序时,需要从应用的健康检查、平滑退出、亲和力、QoS 和 ResourceQuota 等多个维度去考虑应用的健壮性,这些都是不可省略的配置。

你可能感兴趣的:(Kubernetes 资源管理)