In this article we’re going to see how to easily migrate Longhorn volumes to a CSI driver that provides storage directly from the cloud provider.
In this example, we’re using Exoscale as the cloud provider but the concept is the same whatever cloud provider you’re using, as long as they provide block storage through a Kubernetes CSI driver.
This is for Exoscale SKS only, see your cloud provider documentation to find out how to do the equivalent with them.
On your existing Exoscale SKS cluster, update the cluster to add the Exoscale Container Storage Interface addon:
exo compute sks update \
--zone ch-gva-2 \
--enable-csi-addon \
<cluster name>
We should now have at least two CSI drivers:
$ k get csidrivers
NAME ATTACHREQUIRED PODINFOONMOUNT STORAGECAPACITY TOKENREQUESTS REQUIRESREPUBLISH MODES AGE
csi.exoscale.com true true false <unset> false Persistent 6m20s
driver.longhorn.io true true false <unset> false Persistent 19d
and we should have at least two storage classes:
$ k get storageclasses
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
exoscale-sbs csi.exoscale.com Delete Immediate true 6m58s
longhorn (default) driver.longhorn.io Retain Immediate true 301d
We’re going to migrate volumes from the longhorn storage class to the exoscale-sbs storage class.
As a real-world example, let’s say we have a WordPress installation deployed with Helm. It might be difficult to change its PVC name so we want to try and find a transparent way of migrating the data, keeping the same PVC name so that the application doesn’t need any YAML changes.
We’re going to follow the steps below. For ease of use, we’ve created a script that has some additional error control and validation features. You can find it at https://github.com/creativemoods/movestorageclass.
First, let’s define some variables that we’ll need later:
NAMESPACE=mynamespace
STORAGECLASS=exoscale-sbs # Change this with the name of the storage class from your cloud provider
DEPLOY=mydeployment # The name of your deployment or statefulset
PVC=wordpress-data # The name of the PVC we're migrating
SIZE=10Gi # The size of the new PV, should be enough to fit all the data
First, we’re scaling the deployment to 0 because we don’t want the application to write data to the PV while we’re migrating
kubectl scale --replicas=0 deploy/$DEPLOY -n $NAMESPACE
then we create a new PVC and PV using the cloud provider’s CSI
cat > /tmp/newpvc.yaml <<EOF
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: tmppvc
namespace: $NAMESPACE
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: $SIZE
storageClassName: $STORAGECLASS
EOF
kubectl apply -f /tmp/newpvc.yaml
Then we use a pod to mount both volumes (code from https://medium.com/@smathew.35/copying-data-between-persistent-volumes-in-kubernetes-a-simple-guide-acc8f79c2d40)
cat > /tmp/datamover.yaml <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: data-mover
name: data-mover
namespace: $NAMESPACE
spec:
replicas: 1
selector:
matchLabels:
app: data-mover
template:
metadata:
labels:
app: data-mover
spec:
containers:
- args:
- -c
- while true; do ping localhost; sleep 60;done
command:
- /bin/sh
image: quay.io/quay/busybox
name: data-mover
volumeMounts:
- mountPath: /source
name: source
- mountPath: /destination
name: destination
restartPolicy: Always
volumes:
- name: source
persistentVolumeClaim:
claimName: $PVC
- name: destination
persistentVolumeClaim:
claimName: tmppvc
EOF
kubectl apply -f /tmp/datamover.yaml
We then copy contents from the source to the destination
kubectl exec -n $NAMESPACE deploy/data-mover -- sh -c "cp -aR /source/* /destination/"
And we destroy data-mover as we don’t need it anymore
kubectl -n $NAMESPACE delete deploy/data-mover
We can now delete the original PVC. This might delete the PV but that’s OK because we’ve copied the data. If the PV was not deleted because its retention policy was Retain, delete the PV also.
kubectl -n $NAMESPACE delete pvc/$PVC
We change the Retain Policy on the new PV so that we can delete its PVC while keeping the PV
PV=$(kubectl get pvc tmppvc -n $NAMESPACE -o=json | jq -r '.spec.volumeName')
kubectl patch pv $PV -p '{"spec":{"persistentVolumeReclaimPolicy":"Retain"}}'
and we delete the new PVC tmppvc
kubectl -n $NAMESPACE delete pvc/tmppvc
We remove the claimref from the new PV, otherwise we won’t be able to reassociate the PV and PVC
kubectl patch pv $PV --type json -p '[{"op": "remove", "path": "/spec/claimRef"}]'
and we recreate a new PVC with the same name as the original PVC and we specify that we want to bind to the new PV
cat > /tmp/bindpvc.yaml <<EOF
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: $PVC
namespace: $NAMESPACE
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: $SIZE
volumeName: $PV
storageClassName: $STORAGECLASS
EOF
kubectl apply -f /tmp/bindpvc.yaml
Now we wait until the PVC has status Bound. This should happen automatically. Finally, we start our application back up
kubectl scale --replicas=1 deploy/$DEPLOY -n $NAMESPACE
Our application should now be up and running and using data from our CSI instead of Longhorn.