Snapshotting Filesystem Image using LVM

This will break down how to use LVM and thin-provisioned LVs in front of a disk image file allowing new writes to go to a separate LV from the original image.  This will function similarly to something like UnionFS but using the LVM layer instead and using an thin provisioned LV for the new data itself.

This might be useful when you already have a filesystem image (maybe from a backup or similar) and it’s big enough that copying it before using it is not feasible, but you still want to be able to revert to the previous state if something blows up.  This will handle all of that at the block layer.

My main use-case for this is on an older system where some of the modern union or overlay filesystems (unionfs, overlayfs, aufs, etc) are not available or stable.  I’m sure there may be other uses as well, but let’s get to it.

Prep

First, let’s dummy up a filesystem image.  Normally, you’d already have an image to work with, but for our purposes, i’ll just make a small 50MB image for testing.  We’ll put a filesystem on it, mount it, touch a couple files, etc.  Then unmount and we’re ready to go.

root@server:~# dd if=/dev/zero of=fs.img bs=1M count=50

50+0 records in

50+0 records out

52428800 bytes (52 MB) copied, 0.155694 s, 337 MB/s

root@server:~# mkfs.xfs fs.img

root@server:~# mount -o loop fs.img /mnt/test

root@server:~# cd /mnt/test

root@server:/mnt/test# mkdir foo

root@server:/mnt/test# mkdir bar

root@server:/mnt/test# touch foo/baz

root@server:/mnt/test# touch bar/quux

root@server:/mnt/test# echo "hello world" > myfile

root@server:/mnt/test# cd

root@server:~# umount /mnt/test

Set Up LVM Devices

For this example, I am going to use 2 LVs for data and metadata for my thin pool.  To properly size the metadata volume, we’ll use the thin_metadata_size command.  It is available from the thin-provisioning-tools package (on Ubuntu.  for others, YMMV).  I specify a value of 500MB for the pool size.  This would allow me 10 full copies, which i’ll never have.  I also used a max of 100 thin snapshots.  Obviously, tune these to your liking.  The 128sectors indicates a 64K block; this is actually the default.  Again, tune this as necessary.

root@server:~# thin_metadata_size -b128s -s500m -m100

thin_metadata_size - 1312 sectors estimated metadata area size for "--block-size=128sectors --pool-size=500megabytes --max-thins=100"

1312 sectors =~ 650K, so I’ll use  a 1MB LV for this.  (You’ll see below this gets rounded up to 4MB anyway due to the LV extent size in my VG)

Let’s create our LVs:

root@server:~# lvcreate -L500M -n thin-data vg0

 Logical volume "thin-data" created.

root@server:~# lvcreate -L1M -n thin-metadata vg0

 Rounding up size to full physical extent 4.00 MiB

 Logical volume "thin-metadata" created.

Setting up the DM devices

These steps turn our LVs into DM devices that can be used in our thin pool

root@server:~# dmsetup create test-thin-metadata --table '0 8192 linear /dev/vg0/thin-metadata 0'

root@server:~# dmsetup create test-thin-data --table '0 1024000 linear /dev/vg0/thin-data 0'

Set up the Thin Pool

root@server:~# dmsetup create test-thin-pool --table '0 1024000 thin-pool /dev/mapper/test-thin-metadata /dev/mapper/test-thin-data 128 0'

And now for the real work:

First we turn the image into a block device using losetup.  you can pick any loopx that’s not in use:

root@server:~# losetup /dev/loop0 /root/fs.img

Then we allocate an ordinal.  This creates a dm device we can use, with no storage allocated.  The ordinal is a numeric identifier and must not be already in use.

dmsetup message /dev/mapper/test-thin-pool 0 "create_thin 438"

Then create a thin snapshot, specifying the ordinal as well as the origin device (/dev/loop0) as an argument.

dmsetup create test-thin-snap --table '0 102400 thin /dev/mapper/test-thin-pool 438 /dev/loop0'

Mount It!

That’s it.  Now we can mount it up:

root@server:~# mount /dev/mapper/test-thin-snap /mnt/cowtest

root@server:~# cd /mnt/cowtest

root@server:/mnt/cowtest# ls -R

.:

bar foo myfile

./bar:

quux

./foo:

baz

root@server:/mnt/cowtest# cat myfile

hello world

Test

Ok, Let’s mess it up!

root@server:/mnt/cowtest# rm -rf *

root@server:/mnt/cowtest# ls

root@server:/mnt/cowtest# touch OH_NOES_ITS_ALL_GONE

root@server:/mnt/cowtest# ls -l

total 0

-rw-r--r-- 1 root root 0 Aug 16 09:20 OH_NOES_ITS_ALL_GONE

Revert to Original

Now let’s blow that away:

root@server:/mnt/cowtest# cd ..

root@server:/mnt# umount /mnt/cowtest

root@server:/mnt# dmsetup remove test-thin-snap

root@server:/mnt# mount /dev/loop0 /mnt/test

… and we’re still ok 🙂

root@server:/mnt# cd /mnt/test

root@server:/mnt/test# ls -R

.:

bar foo myfile

./bar:

quux

./foo:

baz

 

References:

Kernel Thin-Provisioning Documentation

Gentoo Wiki: Device Mapper

Leave a Reply

Your email address will not be published.