在 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 需要额外配置:
如果你只需要“简单跑通”,可以先跳过本节,直接看后面的 Snapshot 实战;跨命名空间的关键点是:apiserver/controller-manager + csi-provisioner 开特性 ,以及 ReferenceGrant 授权 。
K8s 开启 FeatureGate
kube-apiserver & kube-controller-manager 需要开启以下特性:
多个 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