Migrating CentOS system from HDD to smaller SSD on XFS filesystem

A step-by-step guide to migrate a whole CentOS Linux system from an HDD to a much smaller SSD drive when you're using XFS

Migrating CentOS system from HDD to smaller SSD on XFS filesystem

I think I am not alone who acquired an SSD drive and wanted to move the whole living Linux system to it. The problem is that it appeared to be not an easy task for me because of these reasons:

  • The SSD drive is much smaller than the old HDD (SSD is expensive).
  • I use CentOS 7 which has xfs as default filesystem (as well as RHEL 7, Oracle Linux 7). Hence, my disk partitions are on xfs.
  • System configuration, permissions and so on must be saved.

Let me explain in short why these things make everything harder.

First, if the size of the SSD drive was the same or greater than the HDD size, it would be possible to perform partition cloning. There are a lot of utilities which can do this - dd, ddrescue, partclone or clonezilla. On LVM partitions (which is also in CentOS 7 by default) moving data to another disk of actual or bigger size could be done even easier with pvmove command.

Then, if the filesystem was something different from xfs, for example, ext4, it would be possible to 'shrink' partitions to the size smaller than the new disk and after that perform an operation mentioned above. But, unfortunately, shrinking an xfs partition is not possible (due to filesystem limitations), you can only extend it.

Last, but not the least, if it was not necessary to save working system, it would be much easier just to save a couple of important files and then to reinstall the system from the scratch. It didn't fit me because of two reasons. First, I have just spent two weeks configuring all and everything, so I hated the idea to repeat this all. And second, it was kind of a challenge to me, which I didn't want to refuse.

Stage 1. Preparation

Ok, let's make a list of what we need to start migration:

  1. Working Linux system to migrate. In my case it was CentOS 7.4, but I'm sure everything would work on any Linux system with xfs on disk partitions.
  2. CentOS bootable live-cd or USB flash drive. I love the Gnome version, but there's a KDE option if preffered. I won't stop on how to burn a live-cd or make a bootable flash drive, there're loads of articles about it out there. CentOS live-cd distribution contains everything we need out-of-the-box since all that we need is xfsdump package which is preinstalled.
  3. Data on the previous drive must fit the size of the new one. In my case only 10GB were busy on the 1TB disk, so, it was not a problem for me at all.
  4. You also will need a cup of coffee to relax.

Stage 2. Moving the data

A good start will be to make a sip from your coffee cup and then boot the system from live-cd media. Then open a terminal window.

All operations should be performed by super user (root).

Step 1. Enabling remote access (optional)

For me it is more convenient to perform operations from my desktop PC, because I can just copy and paste prepared in advance commands. If this is also about you, then enable remote access to your system. Set root user password and start SSH daemon for this:

su
passwd
systemctl start sshd

Now, connect to the system using your SSH client (for example, PuTTY).

Step 2. Partitioning your new disk

You can use any tool for doing this, but in this guide I will use fdisk intentionally, since other tools like gparted seem not to support NVMe disks yet (and so my SSD as well).

We should partition the new disk the same way the old one was partitioned. I am not partition-maniac and so on my previous disk there were only two partitions: 1GB /boot standard linux partition, 4GB swap and the rest of the disk was \ (swap and root were under LVM volume group main).

So, let's do it:

lsblk # check your new disk name
fdisk /dev/nvme0n1 # nvme0n1 here is the name of my new disk
n # create new partition (for /boot)
p # primary partition
# leave default (1st partition)
# leave default 
+1G # size for /boot partition
# done!
n # create new partition (for LVM volume group)
p # primary partition
# leave default (2nd partition)
# leave default 
# leave default (all the rest of the disk)
# done!
a # set bootable flag
1 # partition 1
p # check that everyting is ok
w # write partition table to the disk

Since /boot partition should be a standard linux partition, let's create a filesystem on it:

mkfs.xfs /dev/nvme0n1p1 -f

And now we need to create LVM structure on new disk. And I will use name newmain for the new volume group:

pvcreate /dev/nvme0n1p2 # create a new physical volume in LVM
vgcreate newmain /dev/nvme0n1p2 # create a volume group and add PV into it
lvcreate -L 4G -n swap newmain # create logical volume for swap of size 4G
lvcreate -l 100%FREE -n root newmain # create logical volume for root partition with the rest of the disk
vgchange -a y newmain # make the new volume group active

Now we are ready to create a filesystem on logical volumes:

mkfs.xfs /dev/newmain/root # create file system on new root partition
mkswap -L swap /dev/newmain/swap # recreating swap in new place
swapon /dev/newmain/swap

Step 3. Active phase

Before we start, we need to make the old LVM volume group active:

vgchange -a y main

Now, create directories for mount points and mount old and new partitions to them:

mkdir -p /mnt/old/boot
mkdir -p /mnt/old/root
mkdir -p /mnt/new/boot
mkdir -p /mnt/new/root
mount /dev/sda1 /mnt/old/boot
mount /dev/nvme0n1p1 /mnt/new/boot
mount /dev/main/root /mnt/old/root
mount /dev/newmain/root /mnt/new/root

Let's check is everything is ok with lsblk:

lsblk
NAME             MAJ:MIN RM   SIZE RO TYPE MOUNTPOINT
sda                8:0    0 931.5G  0 disk
├─sda1             8:1    0     1G  0 part /mnt/old/boot
└─sda2             8:2    0 930.5G  0 part
  ├─main-swap    253:0    0   3.6G  0 lvm  [SWAP]
  └─main-root    253:1    0 926.9G  0 lvm  /mnt/old/root
nvme0n1          259:0    0 119.2G  0 disk
├─nvme0n1p1      259:3    0     1G  0 part /mnt/new/boot
└─nvme0n1p2      259:4    0 118.2G  0 part
  ├─newmain-swap 253:5    0     4G  0 lvm  [SWAP]
  └─newmain-root 253:6    0 114.2G  0 lvm  /mnt/new/root

If you see something like this, you're in one step to real magic - we are going to use xfsdump to move the data. This utility is very smart and knows how xfs works, that's why it copies only busy blocks and does it really fast saving all the metadata like permissions. So, let's use it to dump the data from old partitions and restore it on the fly to the new ones:

xfsdump -l0 -J - /mnt/old/boot | xfsrestore -J - /mnt/new/boot # clone boot partition
xfsdump -l0 -J - /mnt/old/root | xfsrestore -J - /mnt/new/root # clone root partition

A couple of words about the flags used:

  • -J disables verbosity
  • - tells xfsdump and xfsrestore to use stdout and stdin respectively instead of a file.

This operation can take several minutes (depends on how much data you have). So, it is a good time to finish your coffee.

If you done well, your data is copied. Now you need to fix some configs and install Grub2 on the new disk in order to make the system on new disk bootable.

Step 4. Making new disk bootable

First, find out UUIDs of your old and new /boot partitions using blkid:

blkid
...
/dev/nvme0n1p1: UUID="3055d690-7b2d-4380-a3ed-4c78cd0456ba" TYPE="xfs"
/dev/sda1: UUID="809fd5ba-3754-4c2f-941a-ca0b6fb5c86e" TYPE="xfs"
...

Assuming sda1 is the old \boot partition, and nvme0n1p1 - the new one, perform UUID replacement like this:

sed -i "s/809fd5ba-3754-4c2f-941a-ca0b6fb5c86e/3055d690-7b2d-4380-a3ed-4c78cd0456ba/g" /mnt/new/root/etc/fstab
sed -i "s/809fd5ba-3754-4c2f-941a-ca0b6fb5c86e/3055d690-7b2d-4380-a3ed-4c78cd0456ba/g" /mnt/new/boot/grub2/grub.cfg

This two commands will prepare your system configs for the new disk.

Now, it's high time to place the new LVM volume group in the place where the old was and unmount disks:

umount /mnt/{old,new}/{boot,root}
vgrename -v {,old}main
vgrename -v {new,}main

The only thing which is left is to install Grub2 to the new disk. It must be done using chroot:

mount /dev/main/root /mnt
mkdir -p /mnt/boot
mount /dev/nvme0n1p1 /mnt/boot
mount -t devtmpfs /dev /mnt/dev
mount -t proc /proc /mnt/proc
mount -t sysfs /sys /mnt/sys
chroot /mnt/ grub2-install /dev/nvme0n1

Step 5. Done!

Now you need to restart the system and use the new disk as a boot device. You can remove the old disk from the system if everything is ok:

systemctl reboot -f

If something went wrong and your system doesn't work, you are able to rollback the changes by booting from live-cd again and renaming LVM volume groups back by performing vgrename -v {,new}main and vgrename -v {old,}main


Using the old HDD as media storage

In case you, like me, want to use your old disk as media storage, perform these operations.

First, repartition the disk:

fdisk /dev/sda
d # delete partition 2
d # delete partition 1
n # new partition
p # primary
# default
# default
# 100%
# done!
p # check if everything is ok
w # write partition

We won't create a filesystem on the new partition. Instead, we will create a new LVM volume group and add this disk to it. After that we will create a logical volume within the group which is going to occupate all the available space. And only after that we will create filesystem on the logical volume:

pvcreate /dev/sda1 # new LVM physical volume
vgcreate media /dev/sda1 # new LVM volume group
lvcreate -l 100%FREE -n media1 media # new logical volume
vgchange -a y media # make the volume group active

mkfs.xfs /dev/media/media1 # create filesystem on new logical volume

mkdir -p /var/media # dir for mount point
mount /dev/media/media1 /var/media # mount new disk to the system

Using LVM volume group allows you to extend this partition in the future on-the-fly very easily (for example, when you run out of free space).

In order to save changes after reboot, you need to add a record about new mount point to /etc/fstab:

/dev/mapper/media-media1 /var/media                       xfs     defaults        0 0

And finally we can say that we successfully moved the working system on another physical drive, which was smaller than the original one. As a bonus, we started to use the original disk as media storage.

Extending the media storage with an additional physical drive

It happened, that I ran out of free space of my media storage really fast. And I decided to obtain another hard drive as an extention. So, I installed it on my system and powered up the server.

At this point I had several options of 'how' to use a new drive. It could be a standard partition, a new logical volume in my LVM volume group media or, which I actually wanted to do the most, this disk could be a true extention of the existing logical volume media1, which we previously created.

So, is this chapter I'd like to share with you how easy it is if you use xfs filesystem on a LVM volume group.

First, let's check what we have after we installed a physical drive.

lsblk
NAME             MAJ:MIN RM   SIZE RO TYPE MOUNTPOINT
sda                8:0    0 931.5G  0 disk
sdb                8:16   0 931.5G  0 disk
└─sdb1             8:17   0 931.5G  0 part
  └─media-media1 253:2    0 931.5G  0 lvm  /var/media
nvme0n1          259:0    0 119.2G  0 disk
├─nvme0n1p1      259:1    0     1G  0 part /boot
└─nvme0n1p2      259:2    0 118.2G  0 part
  ├─main-root    253:0    0 114.2G  0 lvm  /
  └─main-swap    253:1    0     4G  0 lvm  [SWAP]

As you can see, the new disk became sda because of some reason, and the old one is now sdb. So, let's create a partition table on the new disk, we're going to use fdisk again for this purpose (though I'm not sure if this step is mandatory, so, do not hesitate to comment on this).

fdisk /dev/sda
# DOS partition table is created automatically on a new disk after running `fdisk`
n # new partition
p # primary
# default
# default
# 100%
# done!
p # check if everything is ok
w # write partition

Let's check.

lsblk
NAME             MAJ:MIN RM   SIZE RO TYPE MOUNTPOINT
sda                8:0    0 931.5G  0 disk
└─sda1             8:1    0 931.5G  0 part
sdb                8:16   0 931.5G  0 disk
└─sdb1             8:17   0 931.5G  0 part
  └─media-media1 253:2    0 931.5G  0 lvm  /var/media
nvme0n1          259:0    0 119.2G  0 disk
├─nvme0n1p1      259:1    0     1G  0 part /boot
└─nvme0n1p2      259:2    0 118.2G  0 part
  ├─main-root    253:0    0 114.2G  0 lvm  /
  └─main-swap    253:1    0     4G  0 lvm  [SWAP]

So, now we have a partition sda1, which we're ready to add to our LVM volume group. Then, we need to extend our logical volume inside the volume group by all unallocated space. And after that, grow the filesystem on the volume in order for it to occupy the free space. Sounds scarier than it actually is.

pvcreate /dev/sda1 # create a new LVM physical volume for the new partition
vgextend media /dev/sda1 # extend the LVM volume group with the physical volume
lvextend -l +100%FREE /dev/media/media1 # extend the logical volume
xfs_growfs /dev/media/media1 # grow the filesystem

That's it! The new hard drive is now a part of our old media storage, and we don't need to think about where to put our new media - we still can save it in the old place. What a wonderful thing LVM!

Hope this post will be helpful for somebody else besides myself. Do not hesitate to express your thoughts in comments if any.