Sur un de mes clusters Kubernetes de lab, j’utilise VolSync pour faire des backups de mes volumes persistants vers un bucket s3 présent… dans le même cluster (oui, c’est pas très smart, mais je veux juste éviter de perdre mes données en cas de mauvaise manip). C’est pratique et simple à mettre en place, mais je lui reproche de ne pas être compatible avec les snapshots. Si jamais j’ai un volume continuellement en écriture, les backups ne seront pas cohérentes en fonction du temps que la copie prend (puisque l’application continue d’écrire pendant le backup).

Kubernetes propose un mécanisme de snapshot pour les volumes persistants via le CSI compatible avec certains CSI, dont celui de Rook-Ceph.

Installation du CSI Snapshotter

kubectl kustomize https://github.com/kubernetes-csi/external-snapshotter/client/config/crd | kubectl create -f -
customresourcedefinition.apiextensions.k8s.io/volumegroupsnapshotclasses.groupsnapshot.storage.k8s.io created
customresourcedefinition.apiextensions.k8s.io/volumegroupsnapshotcontents.groupsnapshot.storage.k8s.io created
customresourcedefinition.apiextensions.k8s.io/volumegroupsnapshots.groupsnapshot.storage.k8s.io created
customresourcedefinition.apiextensions.k8s.io/volumesnapshotclasses.snapshot.storage.k8s.io created
customresourcedefinition.apiextensions.k8s.io/volumesnapshotcontents.snapshot.storage.k8s.io created
customresourcedefinition.apiextensions.k8s.io/volumesnapshots.snapshot.storage.k8s.io created
git clone git@github.com:kubernetes-csi/external-snapshotter
cd external-snapshotter/
kubectl create -k client/config/crd
kubectl create -k deploy/kubernetes/snapshot-controller -n kube-system

Maintenant que le CSI Snapshotter est installé, nous allons pouvoir créer une VolumeSnapshotClass qui va contenir les paramètres de snapshot (et où trouver les secrets pour accéder au cluster Rook-Ceph).

cat <<EOF | kubectl apply -f -
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshotClass
metadata:
  name: csi-cephfsplugin-snapclass
driver: rook-ceph.cephfs.csi.ceph.com 
parameters:
  clusterID: rook-ceph 
  csi.storage.k8s.io/snapshotter-secret-name: rook-csi-cephfs-provisioner
  csi.storage.k8s.io/snapshotter-secret-namespace: rook-ceph
deletionPolicy: Delete
EOF

Dans mon cas, j’utilise le driver de CephFS, mais il existe aussi un driver pour RBD. Il faut juste adapter le driver et les paramètres.

Création d’un snapshot

À présent, il faut bien quelque chose à snapshoter, donc nous allons créer un PVC et un Pod qui va l’utiliser.

cat <<EOF | kubectl apply -f -
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: cephfs-pvc
  labels:
    group: snapshot-test
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
  storageClassName: rook-ceph
---
apiVersion: v1
kind: Pod
metadata:
  name: csicephfs-demo-pod
spec:
  containers:
    - name: web-server
      image: nginx
      volumeMounts:
        - name: mypvc
          mountPath: /var/lib/www/html
  volumes:
    - name: mypvc
      persistentVolumeClaim:
        claimName: cephfs-pvc
        readOnly: false
EOF

On va créer un ptit fichier qu’on voudra snapshoter.

kubectl exec -it csicephfs-demo-pod -- sh -c "echo 'Hello World!' > /var/lib/www/html/index.html"

Il ne reste qu’à créer le snapshot du PVC.

$ cat <<EOF | kubectl apply -f -
---
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
  name: cephfs-pvc-snapshot
spec:
  volumeSnapshotClassName: csi-cephfsplugin-snapclass
  source:
    persistentVolumeClaimName: cephfs-pvc
EOF

On obtient un object VolumeSnapshot qui contient le snapshot du PVC, celui-ci est stocké directement dans Rook avec la référence d’un VolumeSnapshotContent.

kubectl get volumesnapshot
NAME                    READYTOUSE   SOURCEPVC    SOURCESNAPSHOTCONTENT   RESTORESIZE   SNAPSHOTCLASS                SNAPSHOTCONTENT                                    CREATIONTIME   AGE
cephfs-pvc-snapshot     true         cephfs-pvc                           1Gi           csi-cephfsplugin-snapclass   snapcontent-4384779d-f64b-4fa5-b373-3b16b2cb8d4a   11m            2m

Le VolumeSnapshot est l’objet qui va relier le PVC et le VolumeSnapshotContent. C’est un peu l’équivalent d’un PVC avec PV pour les snapshots.

# kubectl get volumesnapshotcontents.snapshot.storage.k8s.io snapcontent-4384779d-f64b-4fa5-b373-3b16b2cb8d4a
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshotContent
metadata:
  annotations:
    snapshot.storage.kubernetes.io/deletion-secret-name: rook-csi-cephfs-provisioner
    snapshot.storage.kubernetes.io/deletion-secret-namespace: rook-ceph
  creationTimestamp: "2025-08-05T15:36:19Z"
  finalizers:
  - snapshot.storage.kubernetes.io/volumesnapshotcontent-bound-protection
  generation: 1
  name: snapcontent-4384779d-f64b-4fa5-b373-3b16b2cb8d4a
  resourceVersion: "27480"
  uid: 1b4e3960-e420-4db9-a4b0-9aae0bd9a4c0
spec:
  deletionPolicy: Delete
  driver: rook-ceph.cephfs.csi.ceph.com
  source:
    volumeHandle: 0001-0009-rook-ceph-0000000000000001-a654bdc4-9b5d-4a86-be17-46bd74a7810a
  sourceVolumeMode: Filesystem
  volumeSnapshotClassName: csi-cephfsplugin-snapclass
  volumeSnapshotRef:
    apiVersion: snapshot.storage.k8s.io/v1
    kind: VolumeSnapshot
    name: cephfs-pvc-snapshot
    namespace: default
    resourceVersion: "27464"
    uid: 4384779d-f64b-4fa5-b373-3b16b2cb8d4a
status:
  creationTime: 1754408180236699000
  readyToUse: true
  restoreSize: 1073741824
  snapshotHandle: 0001-0009-rook-ceph-0000000000000001-80112a7f-e288-462e-81e4-70da6c579ea6

J’aime pas beaucoup l’interface web de Ceph, mais on peut bien y voir que le snapshot a bien été créé (a654bdc4-9b5d-4a86-be17-46bd74a7810a correspond à l’ID du volume dans Ceph, tandis que 80112a7f-e288-462e-81e4-70da6c579ea6 correspond à l’ID du snapshot).

alt text alt text

Si jamais je veux stocker ce snapshot vers un stockage distant (comme ce que je fais avec VolSync), il faut passer par un DataMover comme Velero ou Volume-Snapshot-Mover.

Concrèment, ce sont des opérateurs qui vont créer des PVCs à partir de VolumeSnapshots, et déployer des pods qui vont se charger de faire la copie des données via Kopia ou Restic.

Mais pour l’instant je boycotte Velero, et j’ai pas encore pris le temps de tester Volume-Snapshot-Mover, voyons plutôt comment utiliser ces snapshots directement sur le cluster !

Restauration d’un snapshot

Pour restaurer un snapshot, il faut créer un PVC qui va utiliser le VolumeSnapshot comme source. Je peux aussi en profiter pour changer l’accessMode.

cat <<EOF | kubectl apply -f -
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: cephfs-pvc
spec:
  storageClassName: rook-ceph
  dataSource:
    name: cephfs-pvc-snapshot
    kind: VolumeSnapshot
    apiGroup: snapshot.storage.k8s.io
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 1Gi
EOF

Après quelques secondes où le pod reste en Pending, le PVC est créé ! Il y a aussi des Events qui indiquent les différentes étapes du processus de restauration.

$ kubectl describe pvc cephfs-pvc-clone
# ...
Events:
  Normal   ExternalProvisioning   15s               persistentvolume-controller                                                                                       Waiting for a volume to be created either by the external provisioner 'rook-ceph.cephfs.csi.ceph.com' or manually by the system administrator. If volume creation is delayed, please verify that the provisioner is running and correctly registered.
  Warning  ProvisioningFailed     14s               rook-ceph.cephfs.csi.ceph.com_csi-cephfsplugin-provisioner-57f6db877d-tj9d8_e7403518-7e80-48da-abb1-ed35ff68fbbe  failed to provision volume with StorageClass "rook-ceph": rpc error: code = Aborted desc = clone from snapshot is pending
  Warning  ProvisioningFailed     10s               rook-ceph.cephfs.csi.ceph.com_csi-cephfsplugin-provisioner-57f6db877d-tj9d8_e7403518-7e80-48da-abb1-ed35ff68fbbe  failed to provision volume with StorageClass "rook-ceph": rpc error: code = Aborted desc = clone from snapshot is already in progress
  Normal   Provisioning           9s (x3 over 15s)  rook-ceph.cephfs.csi.ceph.com_csi-cephfsplugin-provisioner-57f6db877d-tj9d8_e7403518-7e80-48da-abb1-ed35ff68fbbe  External provisioner is provisioning volume for claim "default/cephfs-pvc-clone"
  Normal   ProvisioningSucceeded  4s                rook-ceph.cephfs.csi.ceph.com_csi-cephfsplugin-provisioner-57f6db877d-tj9d8_e7403518-7e80-48da-abb1-ed35ff68fbbe  Successfully provisioned volume pvc-54969b97-cfb5-4ec5-a318-e9b8ebe8fb28

Le PVC peut être bound dans à plusieurs pods sur des nodes différents, puisque j’ai utilisé ReadWriteMany comme accessMode.


Ce que j’aurais pu faire aussi si je voulais uniquement dupliquer le PVC, c’est de créer un PersistentVolumeClaim qui utilise directement le PVC source comme dataSource. Ça évite de passer par un VolumeSnapshot mais on ne garde pas la snapshot après clonage.

cat <<EOF | kubectl apply -f -
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: cephfs-pvc-clone
spec:
  storageClassName: rook-ceph
  dataSource:
    name: cephfs-pvc
    kind: PersistentVolumeClaim
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 1Gi
EOF

En revanche (mais ça ne me choque pas trop), il ne semble pas possible de restaurer un snapshot à chaud vers le même PVC dont le snapshot a été créé, je dois d’abord supprimer le PVC avant de le restaurer (et ce qui implique un petit downtime).

Prochaine étape pour mon lab : tester le data-mover pour backup ces snapshots !