Skip to content

Instantly share code, notes, and snippets.

@ParadoxGuitarist
Created May 7, 2025 21:08
Show Gist options
  • Save ParadoxGuitarist/5cfbe9baeb39e8e9324afc2899d8a163 to your computer and use it in GitHub Desktop.
Save ParadoxGuitarist/5cfbe9baeb39e8e9324afc2899d8a163 to your computer and use it in GitHub Desktop.
Bash scripts for migrating block devices Mostly for Harvester/Longhorn
#!/bin/bash
# LICENSE: MIT
# This takes a PVC name and namespace then flattens the boot disk (to get it off a backed image disk).
# Exit if there's none provided.
if [ -z $1 ] && [ -z $2 ]; then
echo "You need to provide a pvcname and namespace for the disk you want to flatten."
exit 1
fi
echo "Looking to flatten $1 from image backed $vmVolumePVC"
# Get the PVC object related stuff
diskPVC=$(kubectl -n $2 get pvc $1 -o json)
diskSC=$(echo $diskPVC | jq -r .spec.storageClassName)
diskCap=$(echo $diskPVC | jq -r .status.capacity.storage)
defaultSC=$(kubectl get storageclasses.storage.k8s.io |grep "(default)" | awk '{ print $1}')
vmns=$2
#If the disk mode permits, we might not have to shut it down, but I'd feel better about it if we did.
diskMode=$(kubectl -n $vmns get pvc $vmVolumePVC --no-headers | awk '{ print $5 }')
# Make sure we can actually flatten this and it's currently and image backed SC.
if [ $diskSC == $defaultSC ]; then
echo "Yoooo.. This disk is already the default storage class. I don't think it can be flattened. Exiting."
exit 1
fi
if [ $(kubectl get sc $diskSC -o json |jq .parameters.backingImage) == 'null' ]; then
echo "This doesn't seem to be an image backed disk so there's nothing to flatten"
exit 1
else
echo "We got a image backed disk: $(kubectl get sc $diskSC -o json |jq -r .parameters.backingImage)"
fi
if [ $(kubectl get sc $defaultSC -o json |jq .parameters.backingImage) == 'null' ]; then
echo "The default storage class ($defaultSC) looks flat."
else
echo "The default storage class - $defaultSC - seems to be backed by an image - $(kubectl get sc $defaultSC -o json |jq .parameters.backingImage)"
echo "This isn't going to work."
exit 1
fi
# Make a new PVC Root-Flat disk.
if [ -n $3 ] ; then
newDiskName="$3"
else
newDiskName="$1-flat"
fi
echo "We're going to make a new $diskCap disk called $newDiskName with the default StorageClass: $defaultSC."
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: $newDiskName
namespace: $vmns
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: $diskCap
storageClassName: $defaultSC
volumeMode: Block
EOF
echo -n "Waiting for $newDiskName to be ready..."
while [[ $(kubectl -n $vmns get pvc $newDiskName -o json |jq -r '.metadata.annotations.["pv.kubernetes.io/bind-completed"]' ) != "yes" ]]; do
echo -n "."
sleep 3
done
echo ""
echo -n "Creating a job to block copy the disks."
# Mount the PVCs in a pod so we can dd them in a job.
cat <<EOF | kubectl apply -f -
apiVersion: batch/v1
kind: Job
metadata:
name: $1-disk-flattener
namespace: $2
spec:
backoffLimit: 2
completionMode: NonIndexed
completions: 1
parallelism: 1
template:
metadata:
labels:
job-name: $1-disk-flattener
namespace: $2
spec:
dnsPolicy: ClusterFirst
restartPolicy: Never
schedulerName: default-scheduler
terminationGracePeriodSeconds: 30
containers:
- name: sak-disk-flattener
image: supporttools/swiss-army-knife
command: ["/bin/sh", "-c"]
args: [ "dd if=/dev/xvdb of=/dev/xvdc status=progress bs=4096" ]
imagePullPolicy: IfNotPresent
volumeDevices:
- name: old-disk
devicePath: /dev/xvdb
- name: new-disk
devicePath: /dev/xvdc
volumes:
- name: old-disk
persistentVolumeClaim:
claimName: $1
- name: new-disk
persistentVolumeClaim:
claimName: $newDiskName
EOF
echo ""
echo -n "Waiting for $1-disk-flattener job to complete..."
sleep 5
while [[ $(kubectl -n $vmns get job $1-disk-flattener -o json | jq -r .status.succeeded ) != 1 ]]; do
echo -n "."
sleep 5
done
echo ""
echo "The new disk $newDiskName is ready now. You'll probably need to delete the jobs and related pods in $3 before you can delete $1. Good luck!"
#!/bin/bash
# LICENSE: MIT
# This takes a PVC name and namespace then flattens the boot disk (to get it off a backed image disk).
# Exit if there's none provided.
if [ -z $1 ] && [ -z $2 ] [ -z $3 ] [ -z $4 ]; then
echo "You need to provide a pvcname, namespace, new-diskname, and storageclass for the disk you want to migrate"
exit 1
fi
echo "Looking to migrate $1 to $4"
# Get the PVC object related stuff
diskPVC=$(kubectl -n $2 get pvc $1 -o json)
diskSC=$(echo $diskPVC | jq -r .spec.storageClassName)
diskCap=$(echo $diskPVC | jq -r .status.capacity.storage)
defaultSC=$(kubectl get storageclasses.storage.k8s.io |grep "(default)" | awk '{ print $1}')
vmns=$2
#If the disk mode permits, we might not have to shut it down, but I'd feel better about it if we did.
diskMode=$(kubectl -n $vmns get pvc $vmVolumePVC --no-headers | awk '{ print $5 }')
# Make sure we can actually flatten this and it's currently and image backed SC.
if [ $diskSC == $4 ]; then
echo "Yoooo.. This disk is already the sane storage class. Exiting."
exit 1
fi
# Make a new PVC Root-Flat disk.
if [ -n $3 ] ; then
newDiskName="$3"
else
newDiskName="$1-flat"
fi
echo "We're going to make a new $diskCap disk called $newDiskName with the $4 StorageClass."
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: $newDiskName
namespace: $vmns
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: $diskCap
storageClassName: $4
volumeMode: Block
EOF
echo -n "Waiting for $newDiskName to be ready..."
while [[ $(kubectl -n $vmns get pvc $newDiskName -o json |jq -r '.metadata.annotations.["pv.kubernetes.io/bind-completed"]' ) != "yes" ]]; do
echo -n "."
sleep 3
done
echo ""
echo -n "Creating a job to block copy the disks."
# Mount the PVCs in a pod so we can dd them in a job.
cat <<EOF | kubectl apply -f -
apiVersion: batch/v1
kind: Job
metadata:
name: $1-sc-migration
namespace: $2
spec:
backoffLimit: 2
completionMode: NonIndexed
completions: 1
parallelism: 1
template:
metadata:
labels:
job-name: $1-sc-migration
namespace: $2
spec:
dnsPolicy: ClusterFirst
restartPolicy: Never
schedulerName: default-scheduler
terminationGracePeriodSeconds: 30
containers:
- name: sak-disk-flattener
image: supporttools/swiss-army-knife
command: ["/bin/sh", "-c"]
args: [ "dd if=/dev/xvdb of=/dev/xvdc status=progress bs=4096" ]
imagePullPolicy: IfNotPresent
volumeDevices:
- name: old-disk
devicePath: /dev/xvdb
- name: new-disk
devicePath: /dev/xvdc
volumes:
- name: old-disk
persistentVolumeClaim:
claimName: $1
- name: new-disk
persistentVolumeClaim:
claimName: $newDiskName
EOF
echo ""
echo -n "Waiting for $1-sc-migration job to complete..."
sleep 5
while [[ $(kubectl -n $vmns get job $1-sc-migration -o json | jq -r .status.succeeded ) != 1 ]]; do
echo -n "."
sleep 5
done
echo ""
echo "The new disk $newDiskName is ready now. You'll probably need to delete the jobs and related pods in $3 before you can delete $1. Good luck!"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment