【摘要】
最近解决了一个K8S系统与对象存储挂载的问题,对K8S的持久化和动态存储有了更深的理解,作为这个维度的一个补充:
K8S云原生系统架构优化(四)无服务器化_云原生无服务化-CSDN博客https://blog.csdn.net/weixin_53439529/article/details/132670791总结一下K8S架构的存储方式,为了实现存算分离,K8S做了什么
【K8S存储方式】
K8S初期为存算分离所做的解耦设计Volume存储卷,采用强耦合、灵活性差的in-tree模式。如果用Yaml文件创建K8S负载,需要指定volumn存储目录,这个目录指的是数据存储路径,是容器内的,所以不能实现持久化存储。这个阶段K8S虽然已经让用户专注于业务功能设计,但这类存储卷的生命周期是跟随pod的,只能用作存储临时数据,无法做为最终数据使用。
随着Kubernetes的不断成熟,为支持更多的应用场景,提供了一种脱离pod生命周期的、用户可管理的存储抽象设计低耦合、灵活性更强的PersistentVolume/PersistentVolumeClaim。这种方式就变成了持久化的,同时还能灵活挂载到pod,不跟随pod的删除而消失。
【存储卷类型】
Kubernetes支持的卷可分为以下几类:
EmptyDir:是临时生成的一个目录,emptyDir生命周期和POD保持一致,pod删除后,emptyDir中的数据也会被清除。
HostPath:挂载宿主机的目录,将节点本地文件系统的路径映射到pod容器中。pod删除后,HostPath中的数据是否清除,依赖于用户的pod配置。
网络存储:网络存储跟随pod的生命周期,通过in-tree的存储插件对接不同类型存储。
以上三种都可以视为是临时存储方式,并不能作为持久化存储使用。
PersistentVolumeClaim:持久存储声明,调用已经创建的持久卷,存储具有独立的生命周期。可以通过存储提供商提供的out-tree插件,对接其存储,比如文件存储、对象存储等,但是需要这些存储有对接插件,并且提供了访问方式、AK/SK等。
ConfigMap/secret:存储少量配置或敏感数据,挂载宿主机本地的ConfigmMap或Secrect文件到容器内,目的是把一些配置文件和密码/token等文件拿出来,在本地存储,然后映射到容器内,这样就避免了某些配置要修改的时候需要进容器或者敏感数据随容器消亡的问题。
以上两种都是持久化的存储方式,而且实现动态挂载,只要负载的配置正确,pod的重启不影响存储,在pod启动的时候就会自动挂载到容器内。
【持久化存储原理】
K8S的持久化存储是通过一个复杂的体系实现,包括:
PersistentVolume:简称 PV,持久化存储卷,是Kubernetes为云原生应用提供一种拥有独立生命周期的、用户可管理的存储抽象设计。通常可以把各种共享的存储方式以PV的形式托管给K8S,包括文件存储(共享的,单节点挂载的存储盘不适合)、对象存储、共享存储目录等。
PersistentVolumeClaim:简称PVC,持久化存储声明,是Kubernetes为解耦云原生应用和数据存储而设计的,通过PVC可以让资源管控更细更灵活、团队职责分离、应用模板更通用,进一步解除了用户被云平台锁定的顾虑。PVC是实现存储资源请求,是与pod紧密关联的,在pod中配置的就是PVC。
StorageClass:简称SC,存储类,是Kubernetes平台为存储提供商提供存储接入的一种声明,通过sc和相应的存储插件(CSI)为容器应用提供动态分配存储卷的能力。
Driver Plugin:存储驱驱动插件,由存储提供商提供,能够对接网络存储,并管理持久存储卷的生命周期。
这个过程是这样的:K8S不是声明式运维吗,pod如果需要把一些容器内的文件持久化存储下来,就通过PVC发出一个声明,SC就相当于一个代理商,SC对应的不同存储类通过存储插件去连接对应的存储驱动,驱动就相当于厂商的销售,负责验证SC给的URL、AK/SK、存储路径,最终呈现出来的就是一个持久化存储卷PV,相当于最终的产品。而这个过程PVC-SC-driver都是无绑定的,能看到绑定关系的只有PVC和PV。
PVC可能绑定多个PV,它会按一定的步骤筛选最合适的PV:
通过size刷选恰当的PV;
通过volumeMode刷选一致的PV;
通过Label刷选合适的PV;
通过SC刷选符合的PV;
通过AccessMode(ReadWriteOnce/ ReadOnlyMany/ ReadWriteMany)刷选符合条件的PV;
返回并绑定符合PVC条件,且size最小的PV。
【如何使用】
一般的云厂商和开源的K8S都支持用控制台和直接操作yaml两种方式,用控制台根据各家云平台对K8S封装和魔改的程度不同,操作步骤和难易程度不同。
【使用PV】
apiVersion: v1
kind: PersistentVolume
metadata:
name: static-volume
spec:
accessModes:
- ReadWriteOnce
capacity:
storage: 10Gi
csi:
driver: disk.csi.everest.io
fsType: ext4
volumeAttributes:
everest.io/disk-mode: SCSI
everest.io/disk-volume-type: SAS
storage.kubernetes.io/csiProvisionerIdentity:
everest-csi-provisioner
volumeHandle: 9a074a5b-a67e-4fae-860e-
07c5307594ea
persistentVolumeReclaimPolicy: Retain
storageClassName: csi-disk
volumeMode: Filesystem
【使用PVC】
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
annotations:
everest.io/disk-volume-type: sas
volume.beta.kubernetes.io/storageprovisioner: everest-csi-provisioner
labels:
app: mysql-adv-mgmd
release: mysql-adv
name: mysql-data-mysql-adv-mgmd-0
namespace: default
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
volumeName/storageClassName: csi-disk
volumeMode: Filesystem
selector:
matchLabels:
release: "stable"
【使用SC】
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: csi-disk-ssd
parameters:
csi.storage.k8s.io/csi-driver-name: disk.csi.everest.io
csi.storage.k8s.io/fstype: ext4
everest.io/disk-volume-type: SSD
everest.io/passthrough: "true"
provisioner: everest-csi-provisioner
reclaimPolicy: Delete
volumeBindingMode: Immediate
allowVolumeExpansion: true
实际中用过开源的、华为的K8S实现持久化存储,用控制台和用yaml的方式都很好实现,按步骤操作就行,最近在用浪潮云的容器引擎ICP挂载对象存储,遇到一堆问题,还是头一回碰见控制台比yaml问题还多的云。
在ICP里面的一个无状态负载,挂载了对象存储创建的PV,该PV由PVC声明配置到容器。但是突发的虚拟机所在的宿主服务器宕机,然后虚拟机上面运行的负载就挂了,神奇的是无状态负载并没有自动故障转移到可用的worker上,然后看了一下对象存储中也没有文件,说明这个PV其实并没有挂载成功。
测试空间重新创建一个PV和PVC,然后负载挂载启动,还是没有文件推送到对象存储,然后联系云服务商的运维人员,检查发现是配置SC的AK/SK不对,问题是这个AK是系统自动加上的,并不是用户修改的,然后在对象存储发现生成过一个API的AK,跟这里的AK一样,那就说明SC的AK对应的是对象存储的API是没问题的,有问题的是服务商给我们的SK。查了之后发现是对象存储对外提供的AK/SK和给ICP的AK/SK不是同一个(合理,API不一样),但是服务商只给了直接访问的SK,另一个不知道是忘了还是怎么滴。
改了之后还是不能推送文件,然后进入容器查看,发现挂载的容器/data目录居然跟worker的本地目录对上了,离谱
等于是我配置了对象/data目录挂载到对象存储,但是给挂载到node上去了,难怪无法故障转移呢,等于绑定在这个node上了。
换了一种存储方式,换成本地文件存储,结果就是正常的
找服务商研发排查了一下,说是SC的访问域名应该由内网域名改成外网域名,问题是这个你是不是该说明一下?这俩服务都是你们服务商内部看到的,不在一个内网你不说谁知道啊?
容器内的文件挂载,/data目录挂载变成了s3fs就说明挂载成功了,挂载成功之后生成的文件可以推送到对象存储。其实也没啥必要,推送了才发现存储的是一些备份还原的持久化数据,但是这个负载是个缓存用的,本来也用不到持久化。