Kubernetes的持久化存储
PV、PVC介绍
Kubernetes使用PV和PVC持久化体系管理存储状态。PV描述的是持久化存储数据卷。这个API对象主要定义的是一个持久化存储在宿主机上的目录。通常,PV是由运维人员事先创建在Kubernetes集群中待用的。而PVC描述的,则是Pod所希望存储的属性。比如volume的存储大小、可读写权限等。
PV和PVC的设计模式,类似于接口与实现,开发人员无需关心持久化存储数据卷的详细实现信息,只需要声明自己需要的存储大小、可读写权限即可。
比如,开发人员实现在Kubernetes中定义如下一个NFS类型的PV,如下所示:
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs
spec:
storageClassName: manual
capacity:
storage: 1Gi
accessModes:
- ReadWriteMany
nfs:
server: 10.244.1.4
path: "/"
然后,开发人员可以声明如下一个PVC,就可以和上面的PV完成绑定了
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nfs
spec:
accessModes:
- ReadWriteMany
storageClassName: manual
resources:
request:
storage: 1Gi
上述PV和PVC完成绑定需要两个条件:
- PV和PVC的spec字段相符
- PV和PVC的StorageClassName字段相同
PV和PVC完成绑定后,Pod就能像使用hostPath等常规类型的Volume一样,在自己的YAML中声明使用这个PVC了,如下所示:
apiVersion: v1
kind: Pod
metadata:
labels:
roles: web-frontend
spec:
containers:
- name: web
image: nginx
ports:
- name: web
containerPort: 80
volumeMounts:
- name: nfs
mountPath: "/usr/share/nginx/html"
volumes:
- name: nfs
persistentVolumeClaim:
claimName: nfs
Volume Controller
Kubernetes中,Volume Controller专门处理持久化存储的控制器。
Volume Controller中维护着多个控制循环,其中有一个循环叫做PersistentVolumeController,完成PV和PVC的绑定工作
PersistentVolumeController会不断查看当前每一个PVC,是不是已经处于绑定状态。如果不是,它会遍历所有的、可用的PV,并尝试将它与PVC进行绑定。这样,Kubernetes就可以保证用户提交的每一个PVC,只要有合适的PV出现,就很快能够进入绑定状态。
持久化宿主机目录的过程
所谓Persistent Volume(持久化数据卷)指的就是这个宿主机上的目录,具备“持久性”。即这个目录中的内容,既不会因为容器的删除而被清理,也不会和当前的宿主机进行绑定。这样,当容器被重启或者在其他节点上重建后,仍然能够挂载这个volume而访问到这些内容。
多数情况下,持久化Volume的实现,往往依赖一个远程存储服务,比如:远程文件存储(NFS、GlusterFS)、远程块存储(公有云提供的远程磁盘)等。
Kubernetes要做的,就是使用这些存储服务,来为容器准备一个持久化宿主机目录,以供将来进行绑定挂载时使用。
准备“持久化”宿主机目录的过程,我们可以形象称为"两阶段处理"
当一个Pod被调度到一个节点之后,kubelet就要负责为这个Pod创建它的Volume目录。默认情况下,kubelet为Volume创建的目录是如下所示的一个宿主机目录:
/var/lib/kubelet/pods/<Pod 的ID>/volume/kubernetes.io~<Volume 类型>/<Volume 名字>
接下来,kubelet要做的操作就取决于你的Volume类型了。
如果你的Volume类型是远程块存储(块存储可以简单理解为一块磁盘),比如Google Cloud的Persistent Disk,那么kubelet就会调用Google Cloud的API,将它所提供的Persistent Disk挂载到Pod所在的宿主机上。这一步为虚拟机挂载远程磁盘的操作,对应两阶段的第一个阶段,即Attach。
Attach完成后,为了能够使用这个远程磁盘,kubelet还要进行第二个操作,即:格式化这个磁盘设备,然后将它挂载到宿主机指定的挂载点上。这个挂载点就是前面提到的Volume的宿主机目录。这个阶段为第二阶段,即Mount。
如果Volume类型是远程文件存储的话,kubelet可以跳过Attach阶段,因为一般远程文件存储没有一个存储设备需要挂载在宿主机上。
StorageClass
PV的创建,是由运维人员完成的,但是不断添加新的满足条件的PV很麻烦。所以Kubernetes为我们提供了一套可以自动创建PV的机制:Dynamic Provisioning。前面人工管理PV的方式称作Static Provisioning
Dynamic Provisioning机制工作的核心,在于一个名叫StorageClass的API对象。这个对象的作用就是创建PV的模板。
StorageClass对象会定义如下两部分内容:
- PV的属性,比如存储类型,Volume的大小
- 创建这个PV需要用到的存储插件
有了这两个信息,Kubernetes就能根据用户提交的PVC,找到一个对应的StorageClass,然后调用StroageClass声明的存储插件,创建需要的PV
比如,如果使用Rook存储服务的话
apiVersion: ceph.rook.io/v1beta1
kind: pool
metadata:
name: replicapool
namespace: rook-ceph
spec:
replicated:
size: 3
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: block-service
provisioner: ceph.rook.io/block
parameters:
pool: replicapool
clusterNamespace: rook-ceph
rook的部署以及使用详见 rook.io/docs/rook/v… 以及 juejin.cn/post/684490…
作为应用开发者,我们只需要在PVC中指定的StorageClass名字即可。
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: claim1
spec:
accessModes:
- ReadWriteOnce
storageClssName: block-service
resources:
requests:
storage: 30Gi
用例解读
.spec.volumes声明Pod的volumes信息
.spec.containers.volumeMounts声明container如何使用Pod的volumes
多个container共享同一个volume时,可以通过.spec.container.volumeMounts.subPath隔离不同容器再同一个volume上数据存储的路径
emptyDir:{}在宿主机上的路径是/var/lib/kubelet/pods//volumes/kubernetes.io~empty-dir/cache-volume,由于上面两个容器都通过subpath使用该volume,所以该目录下还有两个子目录cache1和cache2,pod删除之后该目录也会被清除。
hostPath:指的是宿主机上的目录,Pod删除之后,该目录仍然存在。
PV Spec重要字段解析
Capacity:存储总空间
AccessModes:PV访问策略控制列表,必须同PVC的访问控制列表匹配才能绑定
- ReadWriteOnce只允许单node访问
- ReadOnlyMany允许多个node只读访问
- ReadWriteMany允许多个node读写访问
一个PV可以放置多个访问策略,PVC与PV 绑定时,PV Controller会优先找到AccessModes列表最短并且匹配PVC AccessModes列表的PV集合,然后从该集合中找到Capacity最小且符合PVC size需求的PV对象
PersistentVolumeReclaimPolicy:PV被release之后(与之绑定的PVC被删除后)回收再利用策略
- Delete:volume被released之后直接delete,需要volume plugin支持
- Reatin:默认策略,由系统管理员手动管理该volume
StorageClassName:PVC可以通过该字段找到相同值的PV,也可以通过该字段对应的storageclass从而动态provisioning新PV对象
NodeAffinity:限制可以访问该volume的nodes,对使用该volume的pod调度有影响。
PV状态流转
说明:到达released状态的PV无法根据Reclaim Policy回到available状态从而再次bound新的PVC。此时,如果想要复用原来PV对应的存储中的数据,只有两种方式:
- 复用old PV中记录的存储信息新建PV对象
- 直接从PVC对象复用,即不 unboundPVC和PV(Stateful set处理存储状态的原理)