
在 Kubernetes 环境中,当你需要复制持久化存储卷中的数据时,通常有两种主流的路径:PVC Clone 与 Volume Snapshot。Clone 可以直接从已有的 PVC 快速克隆出一个全新的 PVC,这在需要批量创建测试环境或进行开发回放时非常高效;而 Snapshot 则更贴近生产环境的最佳实践——先对 PVC 创建一个时间点的快照,然后再基于快照恢复出新 PVC,这种方式避免了在源卷持续写入时进行克隆可能导致的数据不一致问题。
本文将基于 csi-driver-nfs,手把手带你从零开始,完整实践 PVC Clone 和 Volume Snapshot 的流程,并详细解释在跨命名空间这一进阶场景下所需的关键配置与常见排错点。
版本与前置条件
阅读本文,我们假设你已经拥有一个可用的 StorageClass(下文中将以 nfs 为例),并且已经安装了 csi-driver-nfs 驱动。
以下是关键的版本要求,供你在实践前参考:
- csi-driver-nfs:从 v4.3.0 版本开始支持 PVC Clone 功能。
- Kubernetes:从 v1.26 版本开始,以 Alpha 特性支持跨命名空间的存储数据源。
本文的示例环境为:Kubernetes v1.34.2,csi-driver-nfs v4.12.1,均已满足上述要求。
1. PVC Clone 是什么?
PVC Clone 是指在创建一个新的 PersistentVolumeClaim (PVC) 时,通过其 spec.dataSourceRef 字段直接引用一个已经存在的 PVC(或一个 VolumeSnapshot)。Kubernetes 会调用底层的 CSI 驱动来完成数据的复制工作,最终得到一个与源 PVC 数据内容完全相同的新 PVC。
重要提示:此功能生效的前提是您所使用的 CSI 驱动本身支持 Clone 操作。如果驱动不支持,新 PVC 可能会被成功创建出来,但其内部数据可能是空的或不符合预期。
2. 准备源 PVC
首先,我们创建两个命名空间,一个用于存放源数据,另一个用于后续的跨命名空间测试。
kubectl create ns source
kubectl create ns storage-source
创建源 PVC
编写一个 YAML 文件来定义我们的源 PVC。
cat<<'EOF'>source-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: source-test-pvc
namespace: source
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
storageClassName: nfs # 使用你的 StorageClass
EOF
应用这个配置并查看状态。
kubectl apply -f source-pvc.yaml
kubectl -n source get pvc source-test-pvc
创建写入数据的 Pod
我们需要一个 Pod 挂载这个 PVC 并写入一些测试数据。
# source-data-pod.yaml
cat<<'EOF'>source-data-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: source-data-pod
namespace: source
spec:
containers:
- name: busybox
image: registry.cn-shanghai.aliyuncs.com/kubeclipper/busybox:1.36.0
command: ["sleep", "3600"]
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
persistentVolumeClaim:
claimName: source-test-pvc
EOF
kubectl apply -f source-data-pod.yaml
kubectl -n source wait --for=condition=Ready pod/source-data-pod --timeout=300s
写入文件到源 PVC
现在,我们进入 Pod,向挂载的卷中写入一些文件。
# 进入Pod
kubectl -n source exec -it source-data-pod -- sh
# 在Pod内部执行
echo "This is a test file created at $(date)" > /data/test-file.txt
echo "Additional test data" >> /data/test-file.txt
mkdir /data/test-directory
echo "File in subdirectory" > /data/test-directory/sub-file.txt
ls -la /data/
cat /data/test-file.txt
# 退出Pod
exit
验证数据写入成功
从外部验证文件是否已成功写入。
kubectl -n source exec source-data-pod -- cat /data/test-file.txt
3. PVC Clone 实战(同命名空间)
创建克隆 PVC
克隆的核心在于新 PVC 的 dataSourceRef 字段。我们将其指向刚才创建的源 PVC。
# pvc-clone-test.yaml
cat<<'EOF'>pv-clone-test.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: test-clone-pvc
namespace: source
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi # 与源PVC相同的大小
storageClassName: nfs # 与源PVC相同的StorageClass
dataSourceRef:
kind: PersistentVolumeClaim
name: source-test-pvc
EOF
kubectl apply -f pv-clone-test.yaml
kubectl -n source get pvc test-clone-pvc
你可以监控 PVC 的状态变化和相关事件。
# 监控PVC状态变化
kubectl -n source describe pvc test-clone-pvc
# 查看相关事件
kubectl -n source get events --field-selector involvedObject.name=test-clone-pvc
创建验证 Pod 挂载克隆 PVC
创建一个新的 Pod 来挂载克隆出来的 PVC,验证数据是否一致。
# clone-verification-pod.yaml
cat<<'EOF'>clone-verification-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: clone-verification-pod
namespace: source
spec:
containers:
- name: busybox
image: registry.cn-shanghai.aliyuncs.com/kubeclipper/busybox:1.36.0
command: ["sleep", "3600"]
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
persistentVolumeClaim:
claimName: test-clone-pvc
EOF
kubectl apply -f clone-verification-pod.yaml
kubectl -n source wait --for=condition=Ready pod/clone-verification-pod --timeout=300s
检查克隆数据
现在,检查挂载的卷中的数据是否与源卷完全一致。
# 检查文件结构
kubectl exec clone-verification-pod -n source -- ls -la /data/
# 验证文件内容
kubectl exec clone-verification-pod -n source -- cat /data/test-file.txt
4. 跨命名空间 Clone(进阶)
跨命名空间的克隆需要一些额外的配置,主要包括:
- 在 kube-apiserver 和 kube-controller-manager 中开启
CrossNamespaceVolumeDataSource 特性门控。
- 在 CSI provisioner 容器中也开启同样的特性门控。
- 安装 Gateway API 的 CRD,主要是为了
ReferenceGrant。
- 创建
ReferenceGrant 资源来授权跨命名空间的访问。
如果你的目标只是快速理解并跑通基础流程,可以先跳过本节,直接阅读后面的 Snapshot 实战部分。跨命名空间克隆的关键就是:开启特性门控和配置 ReferenceGrant 授权。
K8s 开启 FeatureGate
你需要修改 kube-apiserver 和 kube-controller-manager 的静态 Pod 清单,开启以下特性(AnyVolumeDataSource 在 1.24+ 默认已开启):
CrossNamespaceVolumeDataSource:所有版本默认关闭,需手动开启。
注意:如果你的集群有多个 Master 节点,每个节点上的对应组件都需要进行配置。
直接编辑 /etc/kubernetes/manifests/ 目录下的 YAML 文件:
vi /etc/kubernetes/manifests/kube-apiserver.yaml
vi /etc/kubernetes/manifests/kube-controller-manager.yaml
在两个文件中,找到 command 字段下的参数列表,添加如下配置:
--feature-gates=CrossNamespaceVolumeDataSource=true
保存退出后,kubelet 会自动重启这些 Pod。你可以使用以下命令验证参数是否生效:
ps -ef|grep kube-apiserver|grep CrossNamespaceVolumeDataSource
ps -ef|grep kube-controller-manager|grep CrossNamespaceVolumeDataSource
CSI Provisioner 开启 FeatureGate
同样,也需要为 CSI 驱动中的 provisioner 容器开启 CrossNamespaceVolumeDataSource 特性。以 csi-driver-nfs 为例:
kubectl -n kube-system edit deploy csi-nfs-controller
在编辑器中,找到名为 csi-provisioner 的 container,在其 args 列表中加入:
- --feature-gates=CrossNamespaceVolumeDataSource=true
修改后的片段示例如下:
args:
- -v=2
- --csi-address=$(ADDRESS)
- --leader-election
- --leader-election-namespace=kube-system
- --extra-create-metadata=true
- --feature-gates=HonorPVReclaimPolicy=true
- --feature-gates=CrossNamespaceVolumeDataSource=true
- --timeout=1200s
- --retry-interval-max=30m
安装 CRD
我们需要安装 Gateway API 的 CRD,核心是需要 referencegrants 资源。
# 安装 Gateway API v1.0.0(稳定版,包含 ReferenceGrant CRD)
kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.0.0/standard-install.yaml
接着,创建 RBAC 配置,授予 CSI 驱动访问 referencegrants 对象的权限。
cat << 'EOF' > rbac.yaml
# 创建 ClusterRole(包含 referencegrants 的权限)
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: csi-nfs-referencegrant-role
rules:
- apiGroups: ["gateway.networking.k8s.io"]
resources: ["referencegrants"]
verbs: ["get", "list", "watch"] # 对应错误中的 list 权限,补充 get/watch 避免后续报错
---
# 绑定 ClusterRole 到 csi-nfs-controller-sa
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: csi-nfs-referencegrant-binding
subjects:
- kind: ServiceAccount
name: csi-nfs-controller-sa # 出错的服务账号名称
namespace: kube-system # 服务账号所在命名空间
roleRef:
kind: ClusterRole
name: csi-nfs-referencegrant-role
apiGroup: rbac.authorization.k8s.io
EOF
kubectl apply -f rbac.yaml
创建 ReferenceGrant 配置权限
跨命名空间访问 PVC 需要显式授权。我们需要在源 PVC 所在的命名空间(source)中创建一个 ReferenceGrant 资源。
你可以授权目标命名空间访问源命名空间下的所有 PVC:
apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
name: allow-cross-namespace-pvc-clone
namespace: source # 重要:必须在源 namespace 中
spec:
from:
- group: ""
kind: PersistentVolumeClaim
namespace: storage-source # 目标 namespace
to:
- group: ""
kind: PersistentVolumeClaim
效果:storage-source 命名空间可以访问 source 命名空间下的所有 PVC。
或者,更精细地控制,只授权访问特定的 PVC:
apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
name: allow-pvc-cloning
namespace: source # 必须在源 PVC 所在的命名空间
spec:
from:
- group: ""
kind: PersistentVolumeClaim
namespace: storage-source # 允许哪个命名空间来访问
to:
- group: ""
kind: PersistentVolumeClaim
name: source-test-pvc # 允许访问哪个具体的 PVC(也可以不写 name 以允许全部)
效果:storage-source 命名空间可以访问 source 命名空间下名为 source-test-pvc 的 PVC。
如果缺少授权,新创建的 PVC 将会一直处于 Pending 状态,查看事件会看到类似错误:
failed to provision volume with StorageClass "nfs": accessing source/source-test-pvc of PersistentVolumeClaim dataSource from storage-source/test-clone-pvc isn‘t allowed
创建克隆 PVC(跨命名空间)
现在,我们可以在另一个命名空间(storage-source)中创建 PVC,并引用位于 source 命名空间的源 PVC。
# ex-pv-clone-test.yaml
cat << 'EOF' > ex-pv-clone-test.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: test-clone-pvc
namespace: storage-source
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi # 与源PVC相同的大小
storageClassName: nfs # 与源PVC相同的StorageClass
dataSourceRef:
kind: PersistentVolumeClaim
name: source-test-pvc
namespace: source
EOF
kubectl apply -f ex-pv-clone-test.yaml
kubectl -n storage-source get pvc
创建验证 Pod 挂载克隆 PVC
# clone-verification-pod.yaml
cat << 'EOF' > ex-clone-verification-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: clone-verification-pod
namespace: storage-source
spec:
containers:
- name: busybox
image: registry.cn-shanghai.aliyuncs.com/kubeclipper/busybox:1.36.0
command: ["sleep", "3600"]
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
persistentVolumeClaim:
claimName: test-clone-pvc
EOF
kubectl apply -f ex-clone-verification-pod.yaml
检查克隆数据
最后,验证跨命名空间克隆的数据是否正确。
kubectl -n storage-source exec clone-verification-pod -- cat /data/test-file.txt
5. 推荐流程:Snapshot -> PVC
直接进行 PVC Clone 在生产环境中可能并非最佳选择,因为源 PVC 很可能处于持续写入状态。克隆操作的数据一致性以及“时间点”语义,高度依赖于底层存储驱动的实现和业务写入模式。
更推荐的生产级最佳实践是:源 PVC -> 创建快照 (Snapshot) -> 从快照创建新 PVC。这能确保你得到一个确定时间点的数据副本。
[可选]部署 kubernetes-csi snapshotter
这是一个提供卷快照功能的开源组件。如果你在部署 CSI 驱动时已经部署过,可以跳过此步。
git clone https://github.com/kubernetes-csi/external-snapshotter
kubectl -n kube-system kustomize external-snapshotter/deploy/kubernetes/snapshot-controller | kubectl create -f -
kubectl kustomize external-snapshotter/client/config/crd | kubectl create -f -
创建 snapshot class
我们需要创建一个针对 nfs.csi.k8s.io 驱动器的 VolumeSnapshotClass。
# nfs-snapshot-class.yaml
cat<<'EOF'>nfs-snapshot-class.yaml
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshotClass
metadata:
name: nfs-snapshot-class
annotations:
snapshot.storage.kubernetes.io/is-default-class: "true"
driver: nfs.csi.k8s.io
deletionPolicy: Delete
EOF
kubectl apply -f nfs-snapshot-class.yaml
kubectl get VolumeSnapshotClass
创建快照
现在,为我们的源 PVC 创建一个快照。
# source-snapshot.yaml
cat<<'EOF'>source-snapshot.yaml
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
name: source-test-snapshot
namespace: source # 快照与源PVC在同一命名空间
spec:
volumeSnapshotClassName: nfs-snapshot-class
source:
persistentVolumeClaimName: source-test-pvc # 源PVC名称
EOF
kubectl apply -f source-snapshot.yaml
kubectl -n source get volumesnapshot
关键步骤:等待快照状态变为 readyToUse: true。
# 等待快照就绪(重要!)
kubectl -n source wait volumesnapshot/source-test-snapshot --for=jsonpath='{.status.readyToUse}'=true --timeout=300s
# 确认快照状态
kubectl -n source get volumesnapshot source-test-snapshot -o yaml
你也可以登录到后端存储服务器,检查快照是否已成功生成。
创建 Grant 配置权限
跨命名空间使用快照作为数据源,同样需要创建 ReferenceGrant 进行授权。
# cross-ns-snapshot-grant.yaml
cat << 'EOF' > cross-ns-snapshot-grant.yaml
apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
name: allow-all-cross-namespace-snapshots
namespace: source
spec:
from:
- group: ""
kind: PersistentVolumeClaim
namespace: storage-source
to:
- group: snapshot.storage.k8s.io
kind: VolumeSnapshot
EOF
kubectl apply -f cross-ns-snapshot-grant.yaml
从快照创建 pvc
在目标命名空间中,创建一个新的 PVC,并将其数据源指向刚刚创建好的快照。
# pvc-from-snapshot.yaml
cat<<'EOF'>pvc-from-snapshot.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: test-pvc-from-snapshot
namespace: storage-source # 目标命名空间
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
storageClassName: nfs
dataSourceRef:
apiGroup: snapshot.storage.k8s.io
kind: VolumeSnapshot
name: source-test-snapshot
namespace: source # 快照所在的命名空间
EOF
kubectl apply -f pvc-from-snapshot.yaml
kubectl -n storage-source get pvc test-pvc-from-snapshot
创建验证 Pod 挂载新 PVC
最后,依然是熟悉的验证环节,通过创建一个 Pod 来检查从快照恢复的数据。
# snapshot-verification-pod.yaml
cat << 'EOF' > snapshot-verification-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: snapshot-verification-pod
namespace: storage-source
spec:
containers:
- name: busybox
image: registry.cn-shanghai.aliyuncs.com/kubeclipper/busybox:1.36.0
command: ["sleep", "3600"]
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
persistentVolumeClaim:
claimName: test-pvc-from-snapshot
EOF
kubectl apply -f snapshot-verification-pod.yaml
检查数据
# 检查文件结构
kubectl -n storage-source exec snapshot-verification-pod -- ls -la /data/
# 验证文件内容
kubectl -n storage-source exec snapshot-verification-pod -- cat /data/test-file.txt
6. 参考
希望这篇结合 csi-driver-nfs 的实战指南,能帮助你彻底掌握在 Kubernetes 中复制 PVC 数据的两种核心方法。如果你在实践过程中遇到其他问题,欢迎在云栈社区的技术板块与大家交流探讨。