Kubernetes PVC Clone & Snapshot 实战:基于 Csi-Driver-Nfs 的完整示例

k8s-pvc-clone-snapshot.jpeg 在 Kubernetes 里做“数据复制”通常有两条路:

  • PVC Clone:从已有 PVC 快速克隆出一个新 PVC(开发/测试回放、批量创建环境很常用)
  • VolumeSnapshot:先对 PVC 打快照,再从快照创建新 PVC(更贴近生产最佳实践,避免“边写边克隆”的不确定性)

本文以 csi-driver-nfs 为例,从 0 跑通 Clone 与 Snapshot,并给出跨命名空间场景需要的关键配置与排错点。

版本与前置条件

本文默认你已经有一个可用的 StorageClass(下文以 nfs 为例),并已安装 csi-driver-nfs

版本要求(供参考):

示例环境:Kubernetes v1.34.2,csi-driver-nfs v4.12.1,满足上述要求。

1. PVC Clone 是什么?

PVC Clone:创建 PVC 时通过 dataSourceRef 引用一个已存在的 PVC(或 VolumeSnapshot),由 CSI 侧完成数据复制,得到一个内容相同的新 PVC。

注意:必须是 CSI 驱动支持 Clone,否则新 PVC 可能会被创建出来但数据为空/不符合预期。

2. 准备源 PVC

1
2
kubectl create ns source
kubectl create ns storage-source

创建源 PVC

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
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
1
2
kubectl apply -f source-pvc.yaml
kubectl -n source get pvc source-test-pvc

创建写入数据的 Pod

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# 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
1
2
kubectl apply -f source-data-pod.yaml
kubectl -n source wait --for=condition=Ready pod/source-data-pod --timeout=300s

写入文件到源 PVC

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# 进入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

验证数据写入成功

1
kubectl -n source exec source-data-pod -- cat /data/test-file.txt

3. PVC Clone 实战(同命名空间)

创建克隆 PVC

核心就是在新 PVC 的 dataSourceRef 里引用源 PVC:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# 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
1
2
kubectl apply -f pv-clone-test.yaml
kubectl -n source get pvc test-clone-pvc
1
2
3
4
5
# 监控PVC状态变化
kubectl -n source describe pvc test-clone-pvc 

# 查看相关事件
kubectl -n source get events  --field-selector involvedObject.name=test-clone-pvc

创建验证 Pod 挂载克隆 PVC

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# 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
1
2
kubectl apply -f clone-verification-pod.yaml
kubectl -n source wait --for=condition=Ready pod/clone-verification-pod --timeout=300s

检查克隆数据

1
2
3
4
5
# 检查文件结构
kubectl exec clone-verification-pod -n source -- ls -la /data/

# 验证文件内容
kubectl exec clone-verification-pod -n source -- cat /data/test-file.txt

4. 跨命名空间 Clone(进阶)

跨 namespace clone 需要额外配置:

  • kube-apiserver & kube-controller-manager 开启 CrossNamespaceVolumeDataSource 特性

  • csi provisioner 开启 CrossNamespaceVolumeDataSource 特性

  • 安装 Gateway API CRD

  • 创建 ReferenceGrant 配置权限

如果你只需要“简单跑通”,可以先跳过本节,直接看后面的 Snapshot 实战;跨命名空间的关键点是:apiserver/controller-manager + csi-provisioner 开特性,以及 ReferenceGrant 授权

K8s 开启 FeatureGate

kube-apiserver & kube-controller-manager 需要开启以下特性:

  • AnyVolumeDataSource: 1.24及其以上版本,默认开启,之前的版本需要手动配置

  • CrossNamespaceVolumeDataSource:所有版本都默认为 false,需要手动开启

多个 master 节点都需要配置。

直接编辑 manifest 下的 yaml 即可:

1
2
vi /etc/kubernetes/manifests/kube-apiserver.yaml
vi /etc/kubernetes/manifests/kube-controller-manager.yaml

都添加下面这个配置:

1
--feature-gates=CrossNamespaceVolumeDataSource=true

保存退出后,kubelet 会自动重启 Pod。

验证

1
2
ps -ef|grep kube-apiserver|grep CrossNamespaceVolumeDataSource
ps -ef|grep kube-controller-manager|grep CrossNamespaceVolumeDataSource

(可选)此时可以检查 apiserver / controller-manager 是否已带上对应 feature gate 参数。

CSI Provisioner 开启 FeatureGate

同时还需要为 CSI Provisioner 开启 CrossNamespaceVolumeDataSource 特性。

ps:不同的 CSI 安装方式不同,以 csi-driver-nfs 为例。

1
kubectl -n kube-system edit deploy csi-nfs-controller

里面有多个 container,搜索 csi-provisioner 找到对应的 container。在 command 中增加配置,开启特性:

1
--feature-gates=CrossNamespaceVolumeDataSource=true

就像这样:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
    spec:
      containers:
      - 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
        env:
        - name: ADDRESS
          value: /csi/csi.sock
        image: 172.16.131.171:5000/sig-storage/csi-provisioner:v5.3.0
        imagePullPolicy: IfNotPresent
        name: csi-provisioner

安装 CRD

安装 Gateway API CRD,核心是 referencegrants CRD。

1
2
# 安装 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

然后创建 ClusterRoleBinding 配置权限,让 csi 能够访问 referencegrants 对象。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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
1
kubectl apply -f rbac.yaml

创建 ReferenceGrant 配置权限

由于需要在新 namespace 创建 pvc 引用源 namespace 下的 pvc,跨 namespace 访问 pvc 需要授权。

在源 namespace 创建 进行授权

可以授权访问所有 pvc:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
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 ns 可以访问 source ns 下的所有 pvc

或者指定 name 仅授权访问某一个 pvc:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
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 ns 可以访问 source ns 下的名为 source-test-pvc 的 pvc

如果没有权限,新 pvc 会一直 pending:

1
2
3
4
kubectl -n storage-source describe pvc test-clone-pvc

# 典型报错(未授权):
# 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(跨命名空间)

在另一个 namespace 下创建 pvc,引用 source namespace 下的源 pvc

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
# 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
1
2
kubectl apply -f ex-pv-clone-test.yaml
kubectl -n storage-source get pvc

创建验证 Pod 挂载克隆 PVC

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# 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
1
kubectl apply -f ex-clone-verification-pod.yaml

检查克隆数据

1
kubectl -n storage-source exec clone-verification-pod -- cat /data/test-file.txt

5. 推荐流程:Snapshot -> PVC

直接 PVC Clone 在生产里不一定推荐,因为源 PVC 可能正在写入,Clone 的一致性/时点语义取决于底层实现与业务写入方式。

最佳实践是: 源 pvc -> snapshot -> 新 pvc。

[可选]部署 kubernetes-csi snapshotter

开源组件,用于提供快照功能,如果部署 csi 时部署过则跳过。

拉取代码

1
git clone https://github.com/kubernetes-csi/external-snapshotter  

部署

1
2
3
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 的快照类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 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
1
2
3
kubectl apply -f nfs-snapshot-class.yaml

kubectl get VolumeSnapshotClass

创建快照

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# 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
1
2
3
kubectl apply -f source-snapshot.yaml

kubectl -n source get volumesnapshot

等待快照 Ready:

1
2
3
4
5
# 等待快照就绪(重要!)
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 配置权限

跨 namespace 使用 snapshot 同样需要创建 Grant 授权

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# 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
1
kubectl apply -f cross-ns-snapshot-grant.yaml

从快照创建 pvc

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# 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
1
2
3
kubectl apply -f pvc-from-snapshot.yaml

kubectl -n storage-source get pvc test-pvc-from-snapshot

创建验证 Pod 挂载新 PVC

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# 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
1
kubectl apply -f snapshot-verification-pod.yaml

检查数据

1
2
3
4
5
# 检查文件结构
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. 参考

Kubernetes v1.26: Alpha support for cross-namespace storage data sources

CSI Volume Cloning

Volume Snapshots

0%