再探共享存储
文章 介绍了 pv,pvc 和 storageClass 以及其中的关系。这里将进一步深入探讨共享存储。
1. 默认 storageClass
创建 pvc 时不指定 storageClassName, 那么 kubernetes 将使用默认 storageClass 创建 pv。注意这里的不指定,是没有 storageClassName 字段,而不是 storageClassName 设为空。
通过如下命令设置默认 storageClass:
[root@chunqiu pv (Master)]# kubectl patch storageclasses.storage.k8s.io ocs-storagecluster-ceph-rbd -p \
'{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'
[root@chunqiu pv (Master)]# kubectl get storageclasses.storage.k8s.io
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
csi-cephfs cephfs.csi.ceph.com Delete Immediate true 142d
csi-cephrbd (default) rbd.csi.ceph.com Delete Immediate true 142d
storageClass 是集群级别资源,创建不指定 storageClassName 的 pvc 如下:
[root@chunqiu pv (Master)]# cat pvcWithoutStorageClass.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: chunqiu-pvc-no-storageclass
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
[root@chunqiu pv (Master)]# kubectl apply -f pvcWithoutStorageClass.yaml -n chunqiu
[root@chunqiu pv (Master)]# kubectl get pvc -n chunqiu
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
chunqiu-no-storageclass Bound pvc-d4207818-1d6a-4953-bc3f-d1dda5358239 1Gi RWO csi-cephrbd 5s
可见,创建的 pvc 使用了默认 storageClass cephrbd。
2. pv 资源回收
查看前一节创建的默认共享存储 pvc-d4207818-1d6a-4953-bc3f-d1dda5358239:
[root@chunqiu pv (Master)]# kubectl get pv pvc-d4207818-1d6a-4953-bc3f-d1dda5358239
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pvc-d4207818-1d6a-4953-bc3f-d1dda5358239 1Gi RWO Delete Bound chunqiu/chunqiu-no-storageclass csi-cephrbd 47s
需要重点介绍的是 pv 的 reclaim policy,它定义了回收 pv 的方式:
- Delete: 删除 pvc 时,绑定到 pvc 上的 pv 会被删除;
- Recycle:删除 pvc 时,pv 中存储的资源将被清理掉,此时状态为 Available 以供下一个 pvc 使用;
- Retain: 删除 pvc 时,Retain 模式下的 pv 将会保留,其状态将置为 Released。
问题在于 Released 状态下的 pv 并不能被下一个 pvc 使用,这是 Kubernetes 对资源的一种保护,防止不明用户使用 volume 的资源。那么,如果是同一个用户需要访问前一个 Released 的 pv 该怎么做呢?
可以通过命令将 pv 的状态从 Released 更改为 Available,此时 pv 就可以同下一个 pvc 绑定在一起了,而且 pv 中存储的资源也并未被清楚。完整示例如下:
# 更新 pv reclaim policy
[root@chunqiu pv (Master)]# kubectl patch pv pvc-d4207818-1d6a-4953-bc3f-d1dda5358239 -p '{"spec":{"persistentVolumeReclaimPolicy":"Retain"}}'
persistentvolume/pvc-d4207818-1d6a-4953-bc3f-d1dda5358239 patched
[root@chunqiu pv (Master)]# kubectl get pv pvc-d4207818-1d6a-4953-bc3f-d1dda5358239
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pvc-d4207818-1d6a-4953-bc3f-d1dda5358239 1Gi RWO Retain Bound chunqiu/chunqiu-no-storageclass csi-cephrbd 11m
# 删除 pvc 查看 pv 状态
[root@chunqiu pv (Master)]# kubectl delete pvc chunqiu-no-storageclass -n chunqiu
persistentvolumeclaim "chunqiu-no-storageclass" deleted
[root@chunqiu pv (Master)]# kubectl get pv pvc-d4207818-1d6a-4953-bc3f-d1dda5358239
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pvc-d4207818-1d6a-4953-bc3f-d1dda5358239 1Gi RWO Retain Released chunqiu/chunqiu-no-storageclass csi-cephrbd 13m
删除 pvc 后,pv 状态更新为 Released,且 CLAIM 中绑定的 pvc 信息还在,即使 pvc 已经不在了!
在这样状态下 pv 是不能再被下一个 pvc 绑定的,需要将其状态置为 Available 才行:
[root@chunqiu pv (Master)]# kubectl edit pv pvc-d4207818-1d6a-4953-bc3f-d1dda5358239
apiVersion: v1
kind: PersistentVolume
...
spec:
accessModes:
- ReadWriteOnce
capacity:
storage: 1Gi
claimRef:
apiVersion: v1
kind: PersistentVolumeClaim
name: chunqiu-no-storageclass
namespace: chunqiu
resourceVersion: "98551723"
uid: d4207818-1d6a-4953-bc3f-d1dda5358239
使用 edit 命令查看 pv manifest 信息,可以看到 claimRef 下定义了绑定的 pvc 信息,将 pvc 信息删掉即可将 pv 从 Released 更新为 Available。如果下一个绑定的 pvc 和上一个是同名同 namespace,也可以只将 uid 删除即可。查看两种情况下的 pv 表现:
# edit 删除 uid,注意这里的删除,实际做的是将 uid 置成 null
[root@chunqiu pv (Master)]# kubectl edit pv pvc-d4207818-1d6a-4953-bc3f-d1dda5358239
claimRef:
apiVersion: v1
kind: PersistentVolumeClaim
name: chunqiu-no-storageclass
namespace: chunqiu
resourceVersion: "98551723"
uid: null
# 查看状态已更新,且 ClAIM 信息任保留
[root@chunqiu pv (Master)]# kubectl get pv pvc-d4207818-1d6a-4953-bc3f-d1dda5358239
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pvc-d4207818-1d6a-4953-bc3f-d1dda5358239 1Gi RWO Retain Available chunqiu/chunqiu-no-storageclass csi-cephrbd 20m
# edit 删除 claimRef,同样的实际操作是将 claimRef 置为 null
[root@chunqiu pv (Master)]# kubectl edit pv pvc-d4207818-1d6a-4953-bc3f-d1dda5358239
claimRef: null
[root@chunqiu pv (Master)]# kubectl get pv pvc-d4207818-1d6a-4953-bc3f-d1dda5358239
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pvc-d4207818-1d6a-4953-bc3f-d1dda5358239 1Gi RWO Retain Available csi-cephrbd 23m
再次创建 pvc:
[root@chunqiu pv (Master)]# kubectl apply -f pvcWithoutStorageClass.yaml -n chunqiu
persistentvolumeclaim/chunqiu-no-storageclass created
[root@chunqiu pv (Master)]# kubectl get pvc -n chunqiu
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
chunqiu-no-storageclass Bound pvc-d4207818-1d6a-4953-bc3f-d1dda5358239 1Gi RWO csi-cephrbd 11s
可以看到 pvc 重新绑定到 pv pvc-d4207818-1d6a-4953-bc3f-d1dda5358239 上!
这里有一个小细节是 pvc 中使用的是默认 storageClass,storageClass 在创 pv 时,会查找集群中是否有满足条件的 pv,如果有则绑定到满足条件的 pv,而不是动态创建 pv!
3. “升级”场景下的 pv
前面说到“pvc 中使用的是默认 storageClass,storageClass 在创 pv 时,会查找集群中是否有满足条件的 pv,如果有则绑定到满足条件的 pv,而不是动态创建 pv”,这种查找机制会有什么问题吗?
有的。
设想这样一种情况,pod 和 pvc 关联,pvc 绑定到 pv 上。此时,需要做“升级”,升级方式是先将 pod 和 pvc 删除,然后更新 pod 中 container image 为最新版本,重新创建 pod 完成“升级”。示例如下:
# 创建 pod
[root@chunqiu pv (Master)]# cat pod-pvc.yaml
apiVersion: v1
kind: Pod
metadata:
name: chunqiu
labels:
name: chunqiu
spec:
containers:
- name: chunqiu
image: chunqiu:18.4.0
ports:
- containerPort: 6379
volumes:
- name: update
persistentVolumeClaim:
claimName: chunqiu-no-storageclass
[root@chunqiu pv (Master)]# kubectl apply -f pod-pvc.yaml -n chunqiu
pod/chunqiu created
[root@chunqiu pv (Master)]# kubectl get pods -n chunqiu
NAME READY STATUS RESTARTS AGE
chunqiu 1/1 Running 0 4s
[root@chunqiu pv (Master)]# kubectl describe pvc chunqiu-no-storageclass -n chunqiu
Name: chunqiu-no-storageclass
Namespace: chunqiu
StorageClass: csi-cephrbd
Status: Bound
Volume: pvc-d4207818-1d6a-4953-bc3f-d1dda5358239
Labels: <none>
Annotations: pv.kubernetes.io/bind-completed: yes
pv.kubernetes.io/bound-by-controller: yes
Finalizers: [kubernetes.io/pvc-protection]
Capacity: 1Gi
Access Modes: RWO
VolumeMode: Filesystem
Mounted By: chunqiu
Events: <none>
可以看出,pvc 是以 pod 为单位 mount 的,如果 pod 内 container 需要访问 pv,需使用 volumeMounts 指定 pvc。
删除 pod 和 pvc,更新 pod 内 container image:
[root@chunqiu pv (Master)]# kubectl delete pods chunqiu -n chunqiu
pod "chunqiu" deleted
[root@chunqiu pv (Master)]# kubectl delete pvc chunqiu-no-storageclass -n chunqiu
persistentvolumeclaim "chunqiu-no-storageclass" deleted
# 更新 pv 状态
[root@chunqiu pv (Master)]# kubectl get pv pvc-d4207818-1d6a-4953-bc3f-d1dda5358239 -n chunqiu
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pvc-d4207818-1d6a-4953-bc3f-d1dda5358239 1Gi RWO Retain Released chunqiu/chunqiu-no-storageclass csi-cephrbd 74m
[root@chunqiu pv (Master)]# kubectl edit pv pvc-d4207818-1d6a-4953-bc3f-d1dda5358239
persistentvolume/pvc-d4207818-1d6a-4953-bc3f-d1dda5358239 edited
[root@chunqiu pv (Master)]# kubectl get pv pvc-d4207818-1d6a-4953-bc3f-d1dda5358239
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pvc-d4207818-1d6a-4953-bc3f-d1dda5358239 1Gi RWO Retain Available csi-cephrbd 75m
# 更新 pod 内 container image(略)
# 重新创建 pvc
[root@chunqiu pv (Master)]# kubectl apply -f pvcWithoutStorageClass.yaml -n chunqiu
persistentvolumeclaim/chunqiu-no-storageclass created
[root@chunqiu pv (Master)]# kubectl get pvc -n chunqiu
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
chunqiu-no-storageclass Bound chunqiu-pv-volume 1Gi RWO csi-cephrbd 5s
这里问题出现了,重新创建的 pvc 没有和 pv pvc-d4207818-1d6a-4953-bc3f-d1dda5358239 绑定,而是和集群中的另一个 Available pv chunqiu-pv-volume 绑定在一起。这对于“升级”来说是不可接受的,因为数据还存在原来的 pv pvc-d4207818-1d6a-4953-bc3f-d1dda5358239 上呢!
这是在实际做“升级”及类似操作需要注意的地方,pvc 可能会绑定到集群中的其它符合条件的 pv 上,而不是前一个已经释放的 pv!
4. storageClassName 为空 pvc
前面说了 pvc 可以不加 storageClassName,那么使用的就是 Kubernetes 的默认 storageClassName。如果 storageClassName 置为"" 会出现什么情况呢?
首先查看两个 pv 信息:
[root@chunqiu pv (Master)]# kubectl describe pv chunqiu-pv-volume
Name: chunqiu-pv-volume
Labels: type=local
Annotations: pv.kubernetes.io/bound-by-controller: yes
Finalizers: [kubernetes.io/pv-protection]
StorageClass: csi-cephrbd
...
[root@chunqiu pv (Master)]# kubectl describe pv chunqiu-pv-volume-empty
Name: chunqiu-pv-volume-empty
Labels: type=local
Annotations: pv.kubernetes.io/bound-by-controller: yes
Finalizers: [kubernetes.io/pv-protection]
StorageClass:
...
两个 pv 主要的不同是 storageClass,一个指定,一个未指定。当建 pvc 时,如果 storageClassName 置为空,那么 pvc 将使用已经创建的 storageClass 同样为空的 pvc,如果没有,则 pvc 创建失败。示例如下:
[root@chunqiu pv (Master)]# kubectl get pv chunqiu-pv-volume
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
chunqiu-pv-volume 1Gi RWO Retain Available csi-cephrbd 21m
[root@chunqiu pv (Master)]# kubectl get pv chunqiu-pv-volume-empty
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
chunqiu-pv-volume-empty 1Gi RWO Retain Available 3h14m
# 创建 storageClass 为空的 pvc
[root@chunqiu pv (Master)]# cat pvcWithoutStorageClassEmpty.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: chunqiu-storageclass-empty
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
storageClassName: ""
[root@chunqiu pv (Master)]# kubectl apply -f pvcWithoutStorageClassEmpty.yaml -n chunqiu
persistentvolumeclaim/chunqiu-storageclass-empty created
[root@chunqiu pv (Master)]# kubectl get pvc -n chunqiu
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
chunqiu-storageclass-empty Bound chunqiu-pv-volume-empty 1Gi RWO 4s
可以看到,绑定到先前创建的同样为 storageClass 为空的 pv chunqiu-pv-volume-empty 上。相反地,删除 storageClass 为空的 pv,查看 pvc 能否绑定:
[root@chunqiu pv (Master)]# kubectl delete pvc chunqiu-storageclass-empty -n chunqiu
persistentvolumeclaim "chunqiu-storageclass-empty" deleted
[root@chunqiu pv (Master)]# kubectl delete pv chunqiu-pv-volume-empty
persistentvolume "chunqiu-pv-volume-empty" deleted
[root@chunqiu pv (Master)]# kubectl apply -f pvcWithoutStorageClassEmpty.yaml -n chunqiu
persistentvolumeclaim/chunqiu-storageclass-empty created
[root@chunqiu pv (Master)]# kubectl get pvc -n chunqiu
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
chunqiu-storageclass-empty Pending
4s
[root@chunqiu pv (Master)]# kubectl describe pvc chunqiu-storageclass-empty -n chunqiu
Name: chunqiu-storageclass-empty
...
Type Reason Age From Message
---- ------ ---- ---- -------
Normal FailedBinding 13s (x2 over 13s) persistentvolume-controller no persistent volumes available for this claim and no storage class is set
5. “deployment” 和 pvc
这里结合 pvc 讨论 deployment 的创建和升级场景。
deployment 下有多个 pods,多个 pods 可以绑定到一个 pvc 上,多个 pvc 也可以绑定到一个 pod 上。那么在滚动升级时由于 deployment 的 pod 会交替升级,那么中间和 pvc 的绑定,解绑就要考虑到,不过升级的最终结果是一样的,原来几个 pods 绑定的 pvc,升级之后也是一样的。
有一种情况是使用重建的方式更新 pods,如果重建前后 pvc 有变动那么很有可能更新会失败。譬如,重建前 pod 绑定在一个 pvc 上,重建后 pod 需绑定到两个 pvc,且多出来的 pvc 并没有创建。这样的场景下重建更新是会失败的。