小阿轩yx-Kubernetes资源管理
前言
前面的学习从多个维度保障了服务的可用性
- 调度到不同的机器和机房
- 配置可靠的健康检查等
上述措施都是基于应用级别做的,如果 Kubernetes 集群用来运行容器的节点有了故障,带来的影响是很大的,在保证应用本身的前提下,也要通过一些措施保障节点的可用性
节点故障大部分引起的原因
- 资源分配不合理
- 超额分配
因此需要用到某个技术手段保证节点的资源不会过大的超额分配,Kubernetes 为我们提供了开箱即用的资源管理,可通过 ResourceQuota 和 LimitRange 的配合防止节点资源超额分配
ResourceQuota
(资源配额)的使用
- 是限制某个命名空间对资源使用的一个总量限制,比如内存、CPU、Pod数量
什么是资源配额
生产环境中
- 可能会有多个 Kubernetes 集群,面向开发环境、测试环境、预生产环境和生产环境
- Kubernetes 管理员知道每个环境的规模有多大、可调度资源有多大、可调度资源有多少,并知道如何合理为容器分配内存和CPU,所以管理员管理整个 Kubernetes 集群时,很少会有资源分配超出集群可调度范围的情况
实际使用时
- Kubernetes 集群并非只有一个管理员在使用,也并非只有管理员在使用
- 有多个项目组,每个项目组都有属于自己的命名空间,每个项目组可在所在的命名空间创建资源但不知道 Kubernetes 集群是多大规模,也不知道有多少可调度的资源,这就很容易造成集群过量分配引起不可用。这种情况需要对每个项目组合理分配资源避免超出集群承载能力,也可以减少废弃资源没有及时清理带来的资源浪费
为解决上述问题,Kubernetes 引入了 ResourceQuota 的概念,方便 Kubernetes 管理员进行资源分配
首先将 images 镜像文件通过 Xftp 上传至 master、node01、node02(101、102、103)
将资源清单单独上传至主节点 master(101)
这一步开启会话同步
进入镜像文件三个节点同时导入镜像
主机一
[root@k8s-master ~]# cd images/
[root@k8s-master images]# bash imp_docker_img.sh
主机二
[root@k8s-node01 ~]# cd images/
[root@k8s-node01 images]# bash imp_docker_img.sh
主机三
[root@k8s-node02 ~]# cd images/
[root@k8s-node02 images]# bash imp_docker_img.sh
比如
给 A 项目组分配16核64GB资源,最多只能部署20Pod、30个Service,这样来对 Kubernetes 各类资源进行限制
定义一个 ResourceQuota
- 和其它资源配置方法一样,资源配额也可通过一个 YAML 文件进行创建
[root@k8s-master ~]# vim resourcequota.yaml
apiVersion: v1
kind: ResourceQuota
metadata:
name: resource-test
labels:
app: resourcequota
spec:
hard:
#限制最多启动Pod的个数
pods: 50
#初始要求的cpu数量
requests.cpu: 0.5
#初始要求的内存量
requests.memory: 512Mi
#所能使用最大cpu数量
limits.cpu: 5
#所能使用的最大内存量
limits.memory: 16Gi
configmaps: 20
requests.storage: 40Gi
persistentvolumeclaims: 20
replicationcontrollers: 20
secrets: 20
services: 50
services.loadbalancers: "2"
services.nodeports: "10"
ResourceQuota 的使用
- ResourceQuota 具体使用方法
创建一个用于测试的 namespace
[root@k8s-master ~]# kubectl create ns quota-example
创建一个测试 ResourceQuota
- 该 demo 可以限制 namespace 的 PVC 不能超过1个
[root@k8s-master ~]# vim quota-objects.yaml
apiVersion: v1
kind: ResourceQuota
metadata:
name: object-quota-demo
spec:
hard:
persistentvolumeclaims: "1"
创建该 ResourceQuota
[root@k8s-master ~]# kubectl create -f quota-objects.yaml -n quota-example
查看创建的资源限制状态
[root@k8s-master ~]# kubectl get quota object-quota-demo -n quota-example
NAME AGE REQUEST LIMIT
object-quota-demo 114s persistenvolumeclaims: 0/1
获取object-quota-demo文件详细信息
[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:
status:
hard:
persistentvolumeclaims: "1"
used:
persistentvolumeclaims: "0"
- 从 status 字段的 userd 看出,当前的资源显示的使用量,并且namespace 只有在创建了ResourceQuota 才会启用资源使用的配额,没有创建 ResourceQuota 的 namespace 不限制资源的使用。
创建一个 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
查看当前资源情况
[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: "1"
status:
hard:
persistentvolumeclaims: "1"
used:
persistentvolumeclaims: "1"
再次创建一个 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"pvc-quota-demo2"is forbidden:exceeded quota:object-quota-demo,requested:persistentvolumeclaims=1,used:persistentvolumeclaims=1.limited:
persistentvolumeclaims=1
- 此时已经无法在这个 namespace 中创建 PVC,其他资源的限制与之类似。
环境清理
[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 时添加一个默认值。
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 不会影响已经创建的资源)。
配置默认的 request 和 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
[root@k8s-master ~]# vim limitrange01.yaml
apiVersion: v1
kind: LimitRange
metadata:
name: cpu-mem-limit-range
spec:
limits:
- default:
cpu: 1
memory: 512Mi
defaultRequest:
cpu: 0.5
memory: 256Mi
type: Container
---
apiVersion: v1
kind: Pod
metadata:
name: default-cpu-demo
spec:
containers:
- name: default-cpu-demo-ctr
image: nginx:1.7.9
创建文件
[root@k8s-master ~]# kubectl create -f limitrange01.yaml
查看 pod 信息
[root@k8s-master ~]# kubectl get 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
- limits :
- cpu: "1"
- memory: 512Mi
- requests:
- cpu: 500m
- memory: 256Mi
清除资源
[root@k8s-master ~]# kubectl delete -f limitrange01.yaml
配置 requests 和 limits 的范围
- 上述针对没有设置 requests 和 limits 字段的资源添加了默认值,但是并没有限制 requests 和 limits 的最大值和最小值,这样同样会给集群带来风险
- 所以在管理资源分配时,对 requests 和 limits 的最大值和最小值也需要进行管控。
- requests 和 limits 的最大值和最小值的配置方式和默认值的配置差别不大
创建一个内存最小值为 500MB、最大值为 1GB 和 CPU 最小值为 200MB、最大值为 800MB 的 LimitRanger
[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:1.7.9
resources:
limits:
memory: "500Mi"
requests:
memory: "500Mi"
- 内存最小值为 500M,最大为1G
- CPU 最小为 200m,最大为800m
- limits:最高分配给 pod 的资源上限
- requests:最高分配给 pod 的请求
- - max:
- cpu: "800m"
- memory: 1Gi
- min:
- cpu: "200m"
- memory: "500Mi"
- type: Container
- limits:
- memory: "500Mi"
- requests:
- memory: "500Mi"
创建这个 LimitRange 和 Pod
[root@k8s-master ~]# kubectl create -f limitrange02.yaml
测试
[root@k8s-master ~]# kubectl get pod constraints-mem-demo-2 -oyaml
- 可以调整 Pod 中的内存大小,重新创建这个资源,观察效果
清除资源
[root@k8s-master ~]# kubectl delete -f limitrange02.yaml
限制申请存储空间的大小
- 上述讲解的是对 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 ~]# kubectl create -f limitrange03.yaml
[root@k8s-master ~]# kubectl create -f hostpath-pv.yaml
[root@k8s-master ~]# kubectl create -f pvc-hostpath.yaml
Error from server (Forbidden): error when creating "pvc-hostpath.yaml":
persistentvolumeclaims "mypvc-hostpath" is forbidden:
maximum storage usage perPersistentVolumeclaim is 2Gi, but request is 3Gi
- 超出存储范围报错
Qos
- 虽然我们进行了资源限制,但是实际使用时依旧会造成节点资源不足
- 针对资源不足 Kubernetes 会通过重启或驱逐 Pod 释放资源,再重启时,难免会造成一些很重要的服务不可用。
实际情况可能是
- 如果重启或驱逐一些不重要的 Pod 可能会更好,而这种决策是通过 Qos(ouality of Service,服务质量)决定的,所以在生产环境中,Qos 是一个非常重要的环节。
什么事服务质量保证
- 假设有一个 java 进程,需要的 CPU 最低为1核,内存最低为 26 才能正常启动,并且需要 10 个副本才能支撑业务量。此时我们需要创建一个 Deployment 并设置副本为 10、requests.cpu 为1和 requests.memory 为 2GB。
- 创建资源时,Scheduler 会根据一系列算法将该 Pod 部署到合适的节点上,当然需要被调度节点最少有 1核、2GB 的空间资源。
看似一个再简单不过的部署,如果不设置 requests 字段会如何呢?
- Scheduler 在调度时将不再检査 Node 节点上的资源是否满足该应用程序的最低需求,Scheduler 将随机部署至其他节点,包括已经没有资源的机器。
- 如果部署到了一个没有资源的宿主机上,在该应用程序启动时可能就会引起容器一直无法启用,从而进入恶性循环。
- 如果此时10个副本中有3个副本都部署到了一个没有多少资源的宿主机上,3个容器就会引起资源竞争,到最后一个也无法启动,并且在启动过程中程序会占用非常高的 CPU,可能会导致上面正在运行的 Pod 和宿主机不可用,此时很大程度上会引起雪崩,所以设置合理的 requests 和 limits 是非常重要的事情。
我们设置了 requests 和 limits 就能万无一失吗?
- 是不可能的。
- 使用 Kubernetes 部署应用的部署和更新都会经过一系列的调度策略将应用部署在最合适的节点上,随着时间的推移,当时 “最优” 的节点可能已经不再是最佳选择,在该服务器上别的应用或者其他管理员部署的应用可能忘记了配置资源限制,所以在日积月累的消耗中,宿主机一些不可压缩的资源(比如内存、磁盘)的使用率将达到最高峰。
- 假如内存达到最高峰时会引起 00MKilled 故障(00MKilled 即为内存杀手),此时Kubelet 会根据某些策略重启上面的容器用来避免宿主机宕机引来的风险,但是重启容器难免会带来服务中断的现象,如果重启的是比较重要的业务应用,这将是一个非常不好的体验。
那么我们如何在系统资源不够的情况下尽量保证一些比较重要的 Pod 不被杀死呢?
- 我们可以用 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 一般知道最小资源使用量,但是当机器资源充足时,还是想尽可能使用更多的资源,即 1imits 字段的 cpu 和 memory 大于 requests 字段的 cpu 和 memory 的配置。
- BestEffort:尽力而为,当宿主机内存不够时,首先杀死的就是该 Qos 的 Pod,用以保证 Burstable和 Guaranteed 级别的 Pod 正常运行。
实现不同级别的服务质量是根据两个配置诀定
- requests
- ljmits
在宿主机资源不够时会先杀死服务质呈为 BestEffort 的 Pod,然后杀死服务质量为Burstable 的 Pod,最后杀死服务质量为Guaranteed 的 Pod。
所以在生产环境中比较重要的应用最好设置为 Guaranteed,当然如果集群资源足够使用,可以都设置为 Guaranteed。接下来看一下如何实现这 3种 QoS。
首先创建一个用于测试的 Namespace
[root@k8s-master ~]# kubectl create namespace qos-example
实现 Qos 为 Guaranted 的 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"
- 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 -o yaml
......
podIPs :
- ip: 10.244.85.203
qosClass: Guaranteed
startTime: "2023-08-26T03:31:48Z'
实现 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"
- 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 -o yaml
......
podIPs:
- ip: 10.244.58.195
qosClass: Burstable
startTime: "2023-08-26T04:12:39Z'
实现 Qos 为 BestEffort 的 Pod
- 创建一个 Qos 为 Burstable 的 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 等多个维度去考虑应用的健壮性,这些都是不可省略的配置。
小阿轩yx-Kubernetes资源管理