深入理解本地持久化数据卷
使用 Local Persistent Volume 的应用必须具备数据备份和恢复的能力,允许把这些数据定时备份在其他位置。
Local Persistent Volume 的设计,主要面临两个难点:
第一、把本地磁盘抽象成 PV:
一个 Local Persistent Volume 对应的存储介质,一定是一块额外挂载在宿主机的磁盘或者块设备(“额外”的意思是,它不应该是宿主机根目录所使用的主硬盘)。
第二、保证 Pod 始终能被正确地调度到它所请求的 Local Persistent Volume 所在的节点上
对于常规的 PV 来说,Kubernetes 都是先调度 Pod 到某个节点上,然后,再通过“两阶段处理”来“持久化”这台机器上的 Volume 目录,进而完成 Volume 目录与容器的绑定挂载。
在调度的时候考虑 Volume 分布
对于 Local PV 来说,,节点上可以使用的磁盘是提前准备好的,因此调度器必须知道所有节点与 Local Persistent Volume 对应的磁盘的关联关系,然后根据这个信息来调度 Pod。(VolumeBindingChecker 的过滤条件专门负责这个事情)
在自己部署的私有环境中,有两种办法来完成在集群里提前配置好磁盘。
- 第一种,给宿主机挂载并格式化一个可用的本地磁盘
- 第二种,对于实验环境,可以在宿主机上挂载几个 RAM Disk(内存盘)来模拟本地磁盘
展开第二种:
在名叫 node2 的宿主机上创建一个挂载点,比如 /mnt/disks;然后,用几个 RAM Disk 来模拟本地磁盘
tmpfs:
临时性:由于tmpfs是构建在内存中的,存放在 tmpfs 中的所有数据在卸载或断电后都会丢失
快速读写能力:内存的访问速度要远快于磁盘I/O操作,即使使用了swap,性能仍然非常卓越
# 在 node2 上执行
$ mkdir /mnt/disks
$ for vol in vol1 vol2 vol3; do
mkdir /mnt/disks/$vol
mount -t tmpfs $vol /mnt/disks/$vol
done
接下来,为这些本地磁盘定义对应的 PV :
apiVersion: v1
kind: PersistentVolume
metadata:
name: example-pv
spec:
capacity:
storage: 1Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Delete
storageClassName: local-storage
local:
path: /mnt/disks/vol1
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- node2
local 字段,指定了它是一个 Local Persistent Volume
path 字段,指定的正是这个 PV 对应的本地磁盘的路径,即:/mnt/disks/vol1
如果 Pod 要想使用这个 PV,那它就必须运行在 node2 上。所以,在这个 PV 的定义里,需要有一个 nodeAffinity 字段指定 node2 这个节点的名字。这样,调度器在调度 Pod 的时候,就能够知道一个 PV 与节点的对应关系,从而做出正确的选择。这正是 Kubernetes 实现“在调度的时候就考虑 Volume 分布”的主要方法
创建PV:
$ kubectl create -f local-pv.yaml
persistentvolume/example-pv created
$ kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
example-pv 1Gi RWO Delete Available local-storage 8s
使用 PV 和 PVC 的最佳实践,要创建一个 StorageClass 来描述这个 PV,如下所示:
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: local-storage
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
这个 StorageClass 的名字,叫作 local-storage
它的 provisioner 字段,我们指定的是 no-provisioner(Local Persistent Volume 目前尚不支持 Dynamic)
volumeBindingMode=WaitForFirstConsumer 的属性:延迟绑定。
延迟绑定:
等到第一个声明使用该 PVC 的 Pod 出现在调度器之后,调度器再综合考虑所有的调度规则,当然也包括每个 PV 所在的节点位置,来统一决定,这个 Pod 声明的 PVC,到底应该跟哪个 PV 进行绑定
通过这个延迟绑定机制,原本实时发生的 PVC 和 PV 的绑定过程,就被延迟到了 Pod 第一次调度的时候在调度器中进行,从而保证了这个绑定结果不会影响 Pod 的正常调度。
创建 StorageClass ,如下所示:
$ kubectl create -f local-sc.yaml
storageclass.storage.k8s.io/local-storage created
接下来,定义一个PVC,就可以让 Pod 使用到上面定义好的 Local Persistent Volume 了,如下所示:
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: example-local-claim
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
storageClassName: local-storage
它声明的 storageClassName 是 local-storage。Kubernetes 的 Volume Controller 看到这个 PVC 的时候,不会为它进行绑定操作。
创建这个 PVC:
$ kubectl create -f local-pvc.yaml
persistentvolumeclaim/example-local-claim created
$ kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
example-local-claim Pending local-storage 10s
可以看到,此时Kubernetes 里已经存在了一个可以与 PVC 匹配的 PV,但这个 PVC 依然处于 Pending 状态,也就是等待绑定的状态
然后,我们编写一个 Pod 来声明使用这个 PVC,如下所示:
kind: Pod
apiVersion: v1
metadata:
name: example-pv-pod
spec:
volumes:
- name: example-pv-storage
persistentVolumeClaim:
claimName: example-local-claim
containers:
- name: example-pv-container
image: nginx
ports:
- containerPort: 80
name: "http-server"
volumeMounts:
- mountPath: "/usr/share/nginx/html"
name: example-pv-storage
这个 Pod 没有任何特别的地方,你只需要注意,它的 volumes 字段声明要使用前面定义的、名叫 example-local-claim 的 PVC 即可。
而我们一旦使用 kubectl create 创建这个 Pod,就会发现,我们前面定义的 PVC,会立刻变成 Bound 状态,与前面定义的 PV 绑定在了一起,如下所示:
$ kubectl create -f local-pod.yaml
pod/example-pv-pod created
$ kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
example-local-claim Bound example-pv 1Gi RWO local-storage 2m15s
也就是说,在我们创建的 Pod 进入调度器之后,“绑定”操作才开始进行。
需要注意的是,我们上面手动创建 PV 的方式,即 Static 的 PV 管理方式,在删除 PV 时需要按如下流程执行操作:
- 删除使用这个 PV 的 Pod;
- 从宿主机移除本地磁盘(比如,umount 它);
- 删除 PVC;
- 删除 PV。
如果不按照这个流程的话,这个 PV 的删除就会失败。
当然,由于上面这些创建 PV 和删除 PV 的操作比较繁琐,Kubernetes 其实提供了一个 Static Provisioner 来帮助你管理这些 PV。
思考题
正是由于需要使用“延迟绑定”这个特性,Local Persistent Volume 目前还不能支持 Dynamic Provisioning。你是否能说出,为什么“延迟绑定”会跟 Dynamic Provisioning 有冲突呢?