再探共享存储


文章 介绍了 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 并没有创建。这样的场景下重建更新是会失败的。

上一篇:CSI接口Camera驱动学习


下一篇:EKS 训练营-存储卷 EBS(10)