我们可以使用一些方法从多个维度保障了服务的可用性,比如调度到不同的机器和机房、配置可靠的健康检查等。但是上述措施都是基于应用级别去做的,如果我们的Kubernetes集群用来运行容器的节点有了故障,带来的影响是很大的,所以在保证应用本身的前提下,也要通过一些措施保障节点的可用性
节点故障大部分都是由于资源分配不合理、超额分配引起的,因此需要用某个技术手段保证节点的资源不会过大地超额分配,Kubernetes为我们提供了开箱即用的资源管理,可以通过ResourceQuota和LimitRange的配合防止节点资源超额分配。
首先看一下ResourceQuota(资源配额)的使用,资源配额是限制某个命名空间对资源使用的一个总量限制,比如内存,CPU,Pod数量等。
在生产环境中,可能会有多个Kubernetes集群,面向开发环境、测试环境、预生产环境和生产环境等。身为Kubernetes管理员,必然知道每个环境的规模有多大、可调度资源有多少,并且知道如何合理地为容器分配内存和CPU,所以一个管理员去管理整个Kubernetes集群时,很少会有资源分配超出集群可调度范围的情况。
但在实际使用时,Kubernetes集群并非只有一个管理员在使用,也并非只有管理员在使用。公司可能存在多个项目组,每个项目组都有属于自己的命名空间,每个项目组可以在其所在的命名空间创建资源而他们并不知道Kubernetes集群是多大规模,也不知道有多少可调度的资源,这样就很容易造成集群资源过量分配,引起集群不可用。在这种情况下,需要对每个项目组合理地分配资源用以避免超出集群的承能力,也可以减少废弃资源没有及时清理带来的资源浪费
为了解决上述问题,Kubernetes引入了ResourceQuota的概念,以方便Kubernetes管理员进行资源分配,比如给A项目组分配16核64GB的资源,并且最多只能部署20个Pod、30个Service等,这羊来对Kubernetes的各类资源进行限制
如果在一个命名空间内配置了ResourceQuota这个命名空间内的所有Pod都会受其影响
和其他资源配置方法一样,资源配额也可以通过yaml文件进行创建,比如定义一个比较常用的ResourceQuota如下:
apiVersion: v1
kind: ResourceQuota
metadata:
name: resource-test
labels:
app: resourcequota
spec:
hard:
pods: 50 #限制最多启动50个Pod
requests.cpu: 0.5 #初始化要求的最低CPU数量
requests.memory: 512Mi #初始化要求的最低内存数量
limits.cpu: 5 #最高可用的CPU数量
limits.memory: 16Gi #最高可用的内存数量
configmaps: 20 #限制最多20个ConfigMap
requests.storage: 40Gi #请求 40 GiB 的存储空间,用于持久化存储
persistentvolumeclaims: 20 #集群允许最多 20 个持久化卷声明
replicationcontrollers: 20 #集群允许最多 20 个副本控制器
secrets: 20 #集群允许最多 20 个机密
services: 50 #集群允许最多 50 个服务(Services)。
services.loadbalancers: "2" #集群允许最多 2 个负载均衡服务(LoadBalancers)
services.nodeports: "10" #集群允许最多 10 个 NodePort 服务
(1)创建命名空间
[root@k8s-master ~]# ku create ns quota-example
namespace/quota-example created
(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: "1"
(3)创建该ResourceQuota
[root@k8s-master ~]# ku create -f quota-objects.yaml -n quota-example
resourcequota/object-quota-demo created
(4)查看创建的资源限制状态
[root@k8s-master ~]# ku get quota object-quota-demo -n quota-example
NAME AGE REQUEST LIMIT
object-quota-demo 69s persistentvolumeclaims: 0/1
[root@k8s-master ~]# ku get quota object-quota-demo -n quota-example -oyaml
apiVersion: v1
kind: ResourceQuota
metadata:
creationTimestamp: "2024-08-30T03:18:00Z"
name: object-quota-demo
namespace: quota-example
resourceVersion: "11752"
uid: 2176d59b-b777-4745-8a0e-1fc08462986e
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 ~]# ku create -f pvc.yaml -n quota-example
persistentvolumeclaim/pvc-quota-demo created
(6)查看当前资源的使用情况
[root@k8s-master ~]# ku get quota object-quota-demo -n quota-example
NAME AGE REQUEST LIMIT
object-quota-demo 8m35s persistentvolumeclaims: 1/1
[root@k8s-master ~]# ku get quota object-quota-demo -n quota-example -oyaml
apiVersion: v1
kind: ResourceQuota
metadata:
creationTimestamp: "2024-08-30T03:18:00Z"
name: object-quota-demo
namespace: quota-example
resourceVersion: "12412"
uid: 2176d59b-b777-4745-8a0e-1fc08462986e
spec:
hard:
persistentvolumeclaims: "1"
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 ~]# ku 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
因为ResourceQuota配置了此命名空间内只能创建一个PVC,在前面已经创建了一个,used的值已经变为:1;那一个PVC已经被使用过了,所以在创建PVC的时候会报上述错误。
(8)清理
[root@k8s-master ~]# ku delete -f pvc.yaml -n quota-example
persistentvolumeclaim "pvc-quota-demo" deleted
[root@k8s-master ~]# ku delete ns quota-example
namespace "quota-example" deleted
和ResourceQuota不同的是,LimiRange用来配置默认值,也就是一个Pod如果没有配置要用多少内存,CPU,LimiRange会创建Pod时添加一个默认值。
可以通过LimiRange配置默认的Request和limits值,用来解决创建的资源没有配置或配置过小的Requests和Limits带来的问题,比如创建一个Requests.cpu默认为0.5(0.5为半个CPU,1个人CPU等于1000m),Requests.memory为256MB,Limits.cpu为1,Limits.memory为512MB的LimiRange:
(1)创建一个LimitRange
[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 ~]# ku create -f limitrange01.yaml
limitrange/cpu-mem-limit-range created
pod/default-cpu-demo created
(2)查看pod信息
[root@k8s-master ~]# ku get pod default-cpu-demo -oyaml
··········
containers:
- image: nginx:1.7.9
imagePullPolicy: IfNotPresent
name: default-cpu-demo-ctr
resources:
limits:
cpu: "1"
memory: 512Mi
requests:
cpu: 500m
memory: 256Mi
··········
(3)清除资源
[root@k8s-master ~]# ku delete -f limitrange01.yaml
limitrange "cpu-mem-limit-range" deleted
pod "default-cpu-demo" deleted
[root@k8s-master ~]# ku get pod
No resources found in default namespace.
上述针对没有设置requests和limits字段的资源添加了默认值,但是并没有限制requests和limits的最大值和最小值,这样同样会给集群带来风险,所以在管理资源分配时,对request和limits的最大值和最小值也需要进行管控。
requests和limits的最大值和最小值的配置方式和默认值的配置差别不大。比如创建一个内存最大为1G,最小为500M和CPU最大为800m,最小为200m的LimiRange
(1)创建LimitRange的yaml文件
[root@k8s-master ~]# vim 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"
(2)创建这个LimitRange和pod
[root@k8s-master ~]# ku create -f limitrange02.yaml
limitrange/cpu-min-max-demo-lr created
pod/constraints-mem-demo-2 created
(3)测试
[root@k8s-master ~]# ku get pod constraints-mem-demo-2 -oyaml
·······
containers:
- image: nginx:1.7.9
imagePullPolicy: IfNotPresent
name: constraints-mem-demo-2-ctr
resources:
limits:
cpu: 800m
memory: 500Mi
requests:
cpu: 800m
memory: 500Mi
·······
将containers的内存参数memory调整为大于800。
[root@k8s-master ~]# ku create -f limitrange02.yaml
limitrange/cpu-min-max-demo-lr created
Error from server (Forbidden): error when creating "limitrange02.yaml": pods "constraints-mem-demo-2" is forbidden: maximum memory usage per Container is 1Gi, but limit is 1200Mi
(4)清除
LimitRange也可以对存储申请的大小进行限制,比如限制PVC申请空间的最小值为1GB,最大值为2GB(结合ResourceQuota可以同时限制最多存储使用量和最大PVC创建的数量)
(1) 编写限制请存储(PVC)的的配置文件
[root@k8s-master ~]# vim limitrange03.yaml
apiVersion: v1
kind: LimitRange
metadata:
name: storagelimits
spec:
limits:
- type: PersistentVolumeClaim
max:
storage: 2Gi
min:
storage: 1Gi
(2)创建该LimitRange
[root@k8s-master ~]# ku create -f limitrange03.yaml
limitrange/storagelimits created
(3)创建测试用的pv yaml文件,并创建pv
[root@k8s-master ~]# vim hostpath-pv.yaml
kind: PersistentVolume
apiVersion: v1
metadata:
name: mypv-hostpath
labels:
type: local
spec:
storageClassName: pv-hostpath
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
hostPath:
path: "/mnt/data"
[root@k8s-master ~]# ku create -f hostpath-pv.yaml
persistentvolume/mypv-hostpath created
(4)创建测试用的请求存储PVC yaml文件
[root@k8s-master ~]# vim pvc-hostpath.yaml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: mypvc-hostpath
spec:
storageClassName: pv-hostpath
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 3Gi
创建pvc
[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
备注:可以看出pvc请求pv的存储空间为3Gi,但是LimitRange只让pvc请求1-2Gi的存储空间所以PVC请求失败。
虽然我们进行了资源限制,但是实际使用时依旧会造成节点资源不足,针对资源不足Kubernetes会通过重启或驱逐Pod释放资源,再重启时,难先会造成一些很重要的服务不可用。但实际情况可能是,如果重启或驱逐一些不重要的Pod可能会更好,而这种决策是通过QoS(Quality of service,服务质量)决定的,所以在生产环境中,QoS是一个非常重要的环节。
服务质量(QoS)是指当宿主机的资源不足时,按照级别驱逐或杀死Pod,以确保重要的服务资源(高级别)能够正常运行。
实现不同级别的服务质量是根据requests和limits的配置决定的在宿主机资源不够时会先杀死服务质量为BestEffort的Pod,然后杀死服务质量为Burstable的Pod,最后杀死服务质量为Guaranteed的Pod。所以在生产环境中比较重要的应用最好设置为Guaranteed,当然如果集群资源足够使用,可以都设置为Guaranteed.。
(1)创建一个用于测试的Namespace命名空间
[root@k8s-master ~]# ku create ns qos-example
namespace/qos-example created
(2)实现Qos为Guaranteed的Pod
满足条件:Pod中每个容器必须指定的limits.memory和request.memory必须相等;且limits.cpu和request.cpu也必须相等。
创建此类pod的yaml文件
[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 ~]# ku create -f qos-pod.yaml -n qos-example
pod/qos-demo created
[root@k8s-master ~]# ku get pod qos-demo -n qos-example -oyaml
·········
podIPs:
- ip: 10.244.140.72
qosClass: Guaranteed
startTime: "2024-08-30T08:29:35Z"
···
(2)实现Qos为Burstable的Pod
满足条件:
编写服务质量为Burstable的pod的yaml文件
[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 ~]# ku create -f qos-pod-2.yaml -n qos-example
pod/qos-demo-2 created
[root@k8s-master ~]# ku get pod qos-demo-2 -n qos-example -oyaml
········
podIP: 10.244.196.136
podIPs:
- ip: 10.244.196.136
qosClass: Burstable
startTime: "2024-08-30T08:35:57Z"
(3)实现Qos为BestEffort的Pod
条件:Pod中所有容器都没有设置requests和limits字段即可。
创建服务质量为BestEffort的Pod的yaml文件
[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
创建该Pod,并查看服务质量
[root@k8s-master ~]# ku create -f qos-pod-3.yaml -n qos-example
pod/qos-demo-3 created
[root@k8s-master ~]# ku get pod qos-demo-3 -n qos-example -oyaml
··········
podIP: 10.244.196.137
podIPs:
- ip: 10.244.196.137
qosClass: BestEffort
startTime: "2024-08-30T08:39:20Z"
4.总结
提升服务可用性只从应用程序级别考虑是远远不够的,也需要考虑服务器的稳定性。在使用Kubernetes部署程序时,很容易造成资源的过量分配,作为Kubernetes管理员,必须要去考虑服务器的可用性,防止服务器岩机带来的雪。
通常情况下,依赖的底层中间件的QoS会配置为Guaranteed,其他服务可能并不需要那么高的QoS。在实际使用时。最有可能超额分配的是内存,而CPU通常使用率不高,只有在高频工作时,CPU才会处于忙碌状态。所以在设置resources参数时,内存的request需要按需配置,比如一个程序的运行内存最少为2GB,那么内存的request就要设置为2GB,而不能低于2GB。对于CPU,Limit数很重要,防止Pod的CPU过高,从而引起宿主机的问题
所以在生产环境部署一个程序时,需要从应用的健康检查、平滑退出、亲和力、QoS和ResourceQuota等多个维度去考虑应用的健壮性,这些都是不可省略的配置