本文已超过一年。较早的文章可能包含过时内容。请检查页面信息自发布以来是否已变得不正确。

引入 PersistentVolumes 的单个 Pod 访问模式

上个月发布的 Kubernetes v1.22 引入了一个新的 ReadWriteOncePod 访问模式,用于 PersistentVolumesPersistentVolumeClaims。借助此 Alpha 功能,Kubernetes 允许你将卷访问限制为集群中的单个 Pod。

什么是访问模式?为何它们很重要?

使用存储时,可以有不同的方式来建模该存储的使用方式。

例如,像网络文件共享这样的存储系统可以有许多用户同时读写数据。在其他情况下,也许所有人都被允许读取数据但不能写入。对于高度敏感的数据,也许只有一位用户被允许读写数据,而其他人都不可以。

在 Kubernetes 的世界里,访问模式是你定义持久存储如何被使用的方式。这些访问模式是 PersistentVolumes (PV) 和 PersistentVolumeClaims (PVC) 规约(spec)的一部分。

kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: shared-cache
spec:
  accessModes:
  - ReadWriteMany # Allow many nodes to access shared-cache simultaneously.
  resources:
    requests:
      storage: 1Gi

在 v1.22 之前,Kubernetes 为 PV 和 PVC 提供了三种访问模式

  • ReadWriteOnce – 卷可以被单个节点以读写模式挂载
  • ReadOnlyMany – 卷可以被多个节点以只读模式挂载
  • ReadWriteMany – 卷可以被多个节点以读写模式挂载

这些访问模式由 Kubernetes 组件(如 kube-controller-managerkubelet)强制执行,以确保只有特定的 Pod 被允许访问给定的 PersistentVolume。

这个新的访问模式是什么?它是如何工作的?

Kubernetes v1.22 为 PV 和 PVC 引入了第四种访问模式,你可以将其用于 CSI 卷

  • ReadWriteOncePod – 卷可以被单个 Pod 以读写模式挂载

如果你使用采用 ReadWriteOncePod 访问模式的 PVC 创建一个 Pod,Kubernetes 将确保该 Pod 是整个集群中唯一可以读写该 PVC 的 Pod。

如果你创建另一个 Pod,并用此访问模式引用相同的 PVC,该 Pod 将无法启动,因为该 PVC 已被另一个 Pod 使用。例如

Events:
  Type     Reason            Age   From               Message
  ----     ------            ----  ----               -------
  Warning  FailedScheduling  1s    default-scheduler  0/1 nodes are available: 1 node has pod using PersistentVolumeClaim with the same name and ReadWriteOncePod access mode.

这与 ReadWriteOnce 访问模式有何不同?

ReadWriteOnce 访问模式将卷访问限制为单个 节点,这意味着同一节点上的多个 Pod 可以读写同一个卷。对于某些应用而言,这可能是一个大问题,尤其是当它们需要至多一个写入者来保证数据安全时。

使用 ReadWriteOncePod,这些问题就迎刃而解了。在你的 PVC 上设置该访问模式,Kubernetes 将保证只有一个 Pod 可以访问。

如何使用它?

ReadWriteOncePod 访问模式在 Kubernetes v1.22 中处于 Alpha 阶段,并且仅支持 CSI 卷。首先,你需要为 kube-apiserverkube-schedulerkubelet 启用 ReadWriteOncePod 特性门控(feature gate)。你可以通过设置命令行参数来启用此特性

--feature-gates="...,ReadWriteOncePod=true"

你还需要将以下 CSI 边车(sidecar)更新到这些版本或更高版本

创建 PersistentVolumeClaim

为了对 PV 和 PVC 使用 ReadWriteOncePod 访问模式,你需要创建一个具有该访问模式的新 PVC

kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: single-writer-only
spec:
  accessModes:
  - ReadWriteOncePod # Allow only a single pod to access single-writer-only.
  resources:
    requests:
      storage: 1Gi

如果你的存储插件支持动态供应,则将创建应用了 ReadWriteOncePod 访问模式的新 PersistentVolumes。

迁移现有的 PersistentVolumes

如果你有现有的 PersistentVolumes,可以将它们迁移以使用 ReadWriteOncePod。

在此示例中,我们已经有一个名为 "cat-pictures-pvc" 的 PersistentVolumeClaim,它绑定到一个名为 "cat-pictures-pv" 的 PersistentVolume,并且有一个使用此 PersistentVolumeClaim 的 "cat-pictures-writer" Deployment。

第一步,你需要编辑 PersistentVolume 的 spec.persistentVolumeReclaimPolicy 并将其设置为 Retain。这确保当你删除相应的 PersistentVolumeClaim 时,你的 PersistentVolume 不会被删除。

kubectl patch pv cat-pictures-pv -p '{"spec":{"persistentVolumeReclaimPolicy":"Retain"}}'

接下来,你需要停止任何正在使用绑定到你想要迁移的 PersistentVolume 的 PersistentVolumeClaim 的工作负载,然后删除该 PersistentVolumeClaim。

完成后,你需要清除 PersistentVolume 的 spec.claimRef.uid,以确保在重新创建时 PersistentVolumeClaims 可以绑定到它。

kubectl scale --replicas=0 deployment cat-pictures-writer
kubectl delete pvc cat-pictures-pvc
kubectl patch pv cat-pictures-pv -p '{"spec":{"claimRef":{"uid":""}}}'

之后,你需要将 PersistentVolume 的访问模式替换为 ReadWriteOncePod

kubectl patch pv cat-pictures-pv -p '{"spec":{"accessModes":["ReadWriteOncePod"]}}'

接下来,你需要修改你的 PersistentVolumeClaim,将 ReadWriteOncePod 设置为唯一的访问模式。你还应该将 PersistentVolumeClaim 的 spec.volumeName 设置为你的 PersistentVolume 的名称。

完成此操作后,你可以重新创建 PersistentVolumeClaim 并启动你的工作负载

# IMPORTANT: Make sure to edit your PVC in cat-pictures-pvc.yaml before applying. You need to:
# - Set ReadWriteOncePod as the only access mode
# - Set spec.volumeName to "cat-pictures-pv"

kubectl apply -f cat-pictures-pvc.yaml
kubectl apply -f cat-pictures-writer-deployment.yaml

最后,如果你之前更改了 PersistentVolume 的 spec.persistentVolumeReclaimPolicy,可以将其改回 Delete

kubectl patch pv cat-pictures-pv -p '{"spec":{"persistentVolumeReclaimPolicy":"Delete"}}'

你可以阅读为 Pod 配置 PersistentVolume 存储以获取更多关于 PersistentVolumes 和 PersistentVolumeClaims 的详细信息。

哪些卷插件支持此功能?

唯一支持此功能的卷插件是 CSI 驱动。SIG Storage 不打算在树内插件中支持此功能,因为它们正作为CSI 迁移的一部分被弃用。对于喜欢在使用 CSI 迁移启用的情况下使用传统树内卷 API 的用户,可能会考虑在 Beta 版本中提供支持。

作为存储厂商,我如何为我的 CSI 驱动添加对这种访问模式的支持?

ReadWriteOncePod 访问模式无需对 CSI 驱动进行任何更新即可开箱即用,但确实需要更新 CSI 边车。话虽如此,如果你想了解 CSI 规范(v1.5.0+)的最新变化,请继续阅读。

为了区分旧的 SINGLE_NODE_WRITER 访问模式,CSI 规范引入了两种新的访问模式。它们是 SINGLE_NODE_SINGLE_WRITERSINGLE_NODE_MULTI_WRITER。为了向边车(例如 external-provisioner)表明你的驱动理解并接受这两种新的 CSI 访问模式,你的驱动还需要为 控制器服务节点服务 声明 SINGLE_NODE_MULTI_WRITER 能力。

如果你想了解这些访问模式和能力位的动机,还可以阅读 KEP-2485 (ReadWriteOncePod PersistentVolume 访问模式) 中的 CSI 规范变更,卷能力 部分。

更新你的 CSI 驱动以使用新接口

第一步,你需要将驱动的 container-storage-interface 依赖更新到 v1.5.0+ 版本,该版本包含对这些新访问模式和能力的支持。

接受新的 CSI 访问模式

如果你的 CSI 驱动包含验证请求的 CSI 访问模式的逻辑,则可能需要更新。如果它目前接受 SINGLE_NODE_WRITER,则应更新以同时接受 SINGLE_NODE_SINGLE_WRITERSINGLE_NODE_MULTI_WRITER

GCP PD CSI 驱动验证逻辑 为例,下面是如何对其进行扩展的方法。

diff --git a/pkg/gce-pd-csi-driver/utils.go b/pkg/gce-pd-csi-driver/utils.go
index 281242c..b6c5229 100644
--- a/pkg/gce-pd-csi-driver/utils.go
+++ b/pkg/gce-pd-csi-driver/utils.go
@@ -123,6 +123,8 @@ func validateAccessMode(am *csi.VolumeCapability_AccessMode) error {
        case csi.VolumeCapability_AccessMode_SINGLE_NODE_READER_ONLY:
        case csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY:
        case csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER:
+       case csi.VolumeCapability_AccessMode_SINGLE_NODE_SINGLE_WRITER:
+       case csi.VolumeCapability_AccessMode_SINGLE_NODE_MULTI_WRITER:
        default:
                return fmt.Errorf("%v access mode is not supported for for PD", am.GetMode())
        }

你的 CSI 驱动还需要在 ControllerGetCapabilitiesNodeGetCapabilities RPC 中返回新的 SINGLE_NODE_MULTI_WRITER 能力。

GCP PD CSI 驱动能力声明逻辑 为例,下面是如何对其进行扩展的方法。

diff --git a/pkg/gce-pd-csi-driver/gce-pd-driver.go b/pkg/gce-pd-csi-driver/gce-pd-driver.go
index 45903f3..0d7ea26 100644
--- a/pkg/gce-pd-csi-driver/gce-pd-driver.go
+++ b/pkg/gce-pd-csi-driver/gce-pd-driver.go
@@ -56,6 +56,8 @@ func (gceDriver *GCEDriver) SetupGCEDriver(name, vendorVersion string, extraVolu
                csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER,
                csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY,
                csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER,
+               csi.VolumeCapability_AccessMode_SINGLE_NODE_SINGLE_WRITER,
+               csi.VolumeCapability_AccessMode_SINGLE_NODE_MULTI_WRITER,
        }
        gceDriver.AddVolumeCapabilityAccessModes(vcam)
        csc := []csi.ControllerServiceCapability_RPC_Type{
@@ -67,12 +69,14 @@ func (gceDriver *GCEDriver) SetupGCEDriver(name, vendorVersion string, extraVolu
                csi.ControllerServiceCapability_RPC_EXPAND_VOLUME,
                csi.ControllerServiceCapability_RPC_LIST_VOLUMES,
                csi.ControllerServiceCapability_RPC_LIST_VOLUMES_PUBLISHED_NODES,
+               csi.ControllerServiceCapability_RPC_SINGLE_NODE_MULTI_WRITER,
        }
        gceDriver.AddControllerServiceCapabilities(csc)
        ns := []csi.NodeServiceCapability_RPC_Type{
                csi.NodeServiceCapability_RPC_STAGE_UNSTAGE_VOLUME,
                csi.NodeServiceCapability_RPC_EXPAND_VOLUME,
                csi.NodeServiceCapability_RPC_GET_VOLUME_STATS,
+               csi.NodeServiceCapability_RPC_SINGLE_NODE_MULTI_WRITER,
        }
        gceDriver.AddNodeServiceCapabilities(ns)

实现 NodePublishVolume 行为

CSI 规范概述了对同一卷多次调用 NodePublishVolume RPC 但参数不同(例如目标路径)时的预期行为。有关在你驱动中实现时的预期行为的更多详细信息,请参阅 CSI 规范 NodePublishVolume 部分中的第二个表

更新你的 CSI 边车

部署 CSI 驱动时,你必须将以下 CSI 边车更新到依赖于 CSI spec v1.5.0+ 和 Kubernetes v1.22 API 的版本。最低要求的版本是

下一步是什么?

作为此功能晋升 Beta 的一部分,SIG Storage 计划更新 Kubernetes 调度器,以支持与 ReadWriteOncePod 存储相关的 Pod 抢占。这意味着如果两个 Pod 请求具有 ReadWriteOncePod 的 PersistentVolumeClaim,则优先级最高的 Pod 将获得对 PersistentVolumeClaim 的访问权,而任何优先级较低的 Pod 将被从节点中抢占,并且无法访问 PersistentVolumeClaim。

我如何了解更多信息?

有关 ReadWriteOncePod 访问模式和 CSI 规范更改动机的更多详细信息,请参阅 KEP-2485

我如何参与?

Kubernetes #csi Slack 频道以及任何 SIG Storage 标准沟通渠道 都是联系 SIG Storage 和 CSI 团队的好方式。

特别感谢以下人员提供的富有洞察力的审阅和设计考量

  • Abdullah Gharaibeh (ahg-g)
  • Aldo Culquicondor (alculquicondor)
  • Ben Swartzlander (bswartz)
  • Deep Debroy (ddebroy)
  • Hemant Kumar (gnufied)
  • Humble Devassy Chirammal (humblec)
  • James DeFelice (jdef)
  • Jan Šafránek (jsafrane)
  • Jing Xu (jingxu97)
  • Jordan Liggitt (liggitt)
  • Michelle Au (msau42)
  • Saad Ali (saad-ali)
  • Tim Hockin (thockin)
  • Xing Yang (xing-yang)

如果你对参与 CSI 或 Kubernetes 存储系统的任何部分的设计和开发感兴趣,请加入 Kubernetes 存储特别兴趣小组 (SIG)。我们正在快速发展,随时欢迎新的贡献者。