Kubernetes实战系列(4)
这一篇主要讲讲K8S集群中的资源相关的内容,包括使用资源配额quota,还有资源紧张的时候使用优先级类PriorityClass,使用动态缩放器(HPA和VPA)来让pod在繁忙的时候能够应对。
资源配额Quota
准确来说,资源配额是用来限制命名空间内pod等工作负载对资源的使用的,可以限制pod的数量,cpu的总使用量,内存的总使用量等。
这里限制默认的命名空间只能够使用10个pod,cpu设置为1000m,也就是一个逻辑处理器。一个逻辑处理器也就是1000m,至于内存的话,注意使用Mi而不是MiB。
可以通过kubectl get quota来查看默认命名空间的资源配额的使用情况,可以通过kubectl describe quota myquota来查看详细信息,这里就不概述了。
可以尝试创建10个以上的pod试试,然后查看资源配额的使用情况,发现还是3个。这是因为deployment的复制集指定了要10个pod,但是不能满足,所以整个deployment都不能调度。
如下是通过describe查看到的具体信息,可以看到创建失败这个复制集,描述的是整个复制集的状态。这就是资源配额能够起到的作用。
Pod资源调度
上面的quota是站在命名空间的角度来看的,那么对于pod来说能否设置资源的需求呢?答案是可以的,并且设置pod的资源需求会影响pod本身的调度,因为调度器会将能够满足pod需求的节点上。
准确来说是在spec.resources.requests字段和spec.resources.limits字段来设置cpu和memory的使用量,节点需要满足requests的请求才能够调度该pod(该requests数值×容器数量),至于limits是限制pod内容器使用过多资源,当超过limits的时候就会杀死对应的容器。
不过这个Pod的资源还是和资源配额quota有关系的,上面我们只通过--hard参数指定了cpu和memory的使用量,实际上这默认是requests的限制,如果要设置limits的限制,需要使用--hard=limits.cpu=来设置。
前后的区别就是多一列,仔细看是多了LIMIT这一列。如果总的Pod的requests或者limits资源(pod数×数值×容器数)已经超过了资源配额的限制,则会创建失败。
优先级类
虽然上面确实设置了命名空间或者pod的资源占用情况,但是还是有时候资源会不足。这时候集群里面的各个工作负载就会出现竞争情况。可能是pod过度调度导致的,也有可能是pod实际使用的资源比它声明的要多。
反正就是资源紧张的时候就会出现竞争,那么肯定不能当核心业务受损呀。所以给核心业务一个更高的优先级,而且在k8s中默认是采用抢占式的,优先级高的pod能够抢占别人的资源。不过可以通过设置--preemption-policy=Nerver来禁止抢占。
我们通过预定义优先级类,然后让pod使用该优先级达到效果,通过spec.priorityClassName指定。优先级需要指定value,范围是0~2的32次方-1,也就是差不多10亿以上。
自动缩放
主要指的是Pod的自动缩放,包括水平自动缩放和垂直自动缩放。前者通过增加pod的数量,后者则是增加pod的资源。两者都需要metrics server服务,不过VPA更加复杂,还需要安装三个组件。
使用kubectl autoscale deployment name --cpu-percent=50 --min=1 --max=5就可以创建一个HPV了,这比较简单。默认是达到条件(cpu繁忙)立刻增加pod数量,但是cpu不繁忙的时候要等五分钟才能够看到pod减少。
configMap和Secret
使用命令行创建configMap也简单,主要是通过--from-literal和--from-file两个参数来设置的。不过注意的是,通过describe查看是明文的,意味着不适合存储敏感数据。
不过使用secret则可以很好的解决这个问题,创建secret的方法和configMap类似,第一个docker-registry用于存储docker仓库的认证信息,generic则是通用型secret,而tls则是存储tsl的公私钥和证书。其中只有generic是自定义字段,可以通过--from-file和--from-literal来设置。
kubectl create secret (docker-registry | generic | tls) [options]
并且通过describe查看的时候就看不到数据了,不过实际上数据并不是加密的,而是通过base64编码的。
configMap和secret一般作为环境变量和挂载卷给Pod使用,如何作为环境变量呢?是通过spec.containers.env.valueFrom字段设置的(指定configMap的名称和键名),然后在spec.containers.env.name设置键名。
至于挂载,则是在spec.volumes.configMap字段设置,指定名称就好了。不过说到挂载,可以提一提emptyDir,是通过spec.volumes.emptyDir指定,value可以是{},也可以是sizeLimit: 2Gi这样的键值对。不过它实质上是hostPath类型,只不过用的是临时目录。
配置持久存储
虽然主要是使用PV和PVC,但是hostPath类型也能够实现持久存储,只不过挂载目录在pod所在的节点上,当pod迁移的时候就会很麻烦。
可以通过spec.volumes.hostPath指定path和type指定,type主要是用于校验path的,确保file是file,目录是目录,错误则会报错。如果type为空,则可能会导致安全问题。
但是重点是如何创建PV和PVC,不过这两个都不能够通过命令行创建,并且这属于集群资源。使用PVC的时候不但可以通过spec.volumeName指定PV,还可以通过spec.storageClassName指定存储类,这样Kubernetes 会检查该 PV 的容量和访问模式是否满足 PVC 的要求。如果不满足,绑定会失败,并 fallback 到存储类动态供给。
apiVersion: v1
kind: PersistentVolume
metadata:name: my-static-pv
spec:storageClassName: fast-ssd # 手动指定该PV属于'fast-ssd'存储类capacity:storage: 10GiaccessModes:- ReadWriteOncenfs:server: nfs-server.example.compath: /exports/data
要注意的是如果PV指定了存储类,PVC也要指定同样的存储类,否则无法绑定。或者说都不指定存储类,不过不利于分类。
存储类不一定需要有动态供给能力,可以是自定义的。如下是一个虚拟的 provisioner 名称,实际不存在对应的驱动。Kubernetes 看到这个 provisioner 时,不会尝试动态创建 PV,但会允许 PVC 绑定到手动创建的、同名的 PV。
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:name: my-nfs-class
provisioner: example.com/no-provisioner # 关键:使用一个假的后端驱动
reclaimPolicy: Retain # 统一回收策略
volumeBindingMode: Immediate # 绑定模式