All of the actions in this exercise are done as "root", so if you are not root already type:

$ sudo -s


First, try each of the following commands to list the logical volumes you have:

# lvscan
# lvs
# lvdisplay

Compare the output - it has differing levels of detail but should show the same logical volumes.

Have a look at the device nodes:

# ls -l /dev/mapper
# ls -l /dev/ganeti

Now create a new logical volume called "foo" (or some other name you choose) inside the "ganeti" volume group:

# lvcreate --size 1G --name foo ganeti

Use these commands to check that the device node exists, and to see the size of the block device you have created:

# ls -l /dev/ganeti
# blockdev --getsize64 /dev/ganeti/foo

Now we can create a filesystem within it, and mount it. MAKE SURE YOU DO THESE OPERATIONS ON THE CORRECT DEVICE NODE - if you write to your root, var or swap volumes you will break your system!

# mkfs.ext4 /dev/ganeti/foo
# mkdir /opt                 # if /opt already exists, ignore the error
# mount /dev/ganeti/foo /opt
# df -h /opt

You should now be able to create files in /opt if you wish, and they will be stored in this logical volume.

Now let's grow the filesystem. This requires two steps: firstly add some extents to the logical volume.

# lvextend --size +1G /dev/ganeti/foo
# blockdev --getsize64 /dev/ganeti/foo
# df -h /opt

You'll see that although the logical volume is bigger, the filesystem is still the same size. You need to resize the filesystem to fill the larger space available. Fortunately, with ext4 you can do this even while the filesystem is mounted:

# resize2fs /dev/ganeti/foo
# df -h /opt

Now let's undo everything. Unmount the filesystem and delete the logical volume.

# umount /opt
# lvremove /dev/ganeti/foo
# lvscan

As you can see, creating and destroying logical volumes is a straightforward process. There is plenty of additional LVM documentation online that you can read.

From the point of view of a virtual machine, a logical volume behaves much like a raw disk image file, but avoids the overhead of going through the host filesystem.

2 Scripted installs

Scripted installs can be used to automate the creation of new VM images, and avoid most of the interactive questions asked during a CD install.

Under Ubuntu, the tool "python-vm-builder" can be used for this; under Debian the equivalent tool is "grml-debootstrap", and this is what you will use in this exercise.

2.1 Install grml-debootstrap

# apt-get install grml-debootstrap

The configuration settings are available by reading man grml-debootstrap and by looking in the file /etc/debootstrap/config

3 Build an image in a logical volume

Since you now know how to create logical volumes, you will create one for this image.

# lvcreate --size 4G --name debian1 ganeti
# grml-debootstrap --target /dev/ganeti/debian1 --hostname debian1 -v \
    --mirror http://apt.ws.nsrc.org:3142/cdn.debian.net/debian

You will be asked to confirm the settings. If it looks ok, answer y.

This will take a few minutes. You will see it installing the packages into the filesystem. After the initial confirmation to proceed, the only questions you are asked are to set a root password.

Once finished, you should see:

 * Finished execution of grml-debootstrap. Enjoy your Debian system.

3.1 Examine the image

When built this way, the logical volume contains a single filesystem (with no partition table), and it is easy to mount and examine, which is very useful.

# mount /dev/ganeti/debian1 /mnt
# ls /mnt

Let's make a small change to the filesystem: we will enable the serial console. For a Debian image it's done like this:

# vi /mnt/etc/inittab

---- Uncomment this line (remove the '#' in front of it) ----
T0:23:respawn:/sbin/getty -L ttyS0 9600 vt100

Save the change. Finally, unmount the fileystem:

# umount /mnt

NOTE: to avoid filesystem corruption, it's VERY IMPORTANT that the same block device is not mounted in two places at once. So NEVER mount a volume like this while it is attached to a running VM; and always remember to unmount it before you start a VM which uses it.

3.2 Booting the image

Now we want to boot the image as a VM.

However there is a problem. Because this image is a straight filesystem, without a partition table, it does not contain a Master Boot Record (MBR) and doesn't have the GRUB boot loader. So it won't boot in the way a normal PC does.

What we can do instead is to pass KVM two files (a kernel and init ramdisk) stored on the host. We will use the same kernel and initrd as is used to boot the Debian host you have built.

Rather than write the libvirt XML for this all by hand, you could use the virt-manager GUI, but this time you will use the command line tool "virt-install" to create the machine. You pass a number of parameters telling it the parameters of the machine to build.

Hint: if you are inside the X11 graphical environment then it's better to run this as a regular user, not root, so that it can open the graphical console window for you.

$ virt-install --name debian1 --ram 512 \
  --graphics vnc,listen=,password=xyzzy \
  --import --disk /dev/ganeti/debian1 --network=bridge:br-lan \
  --boot kernel=/vmlinuz,initrd=/initrd.img,kernel_args="root=/dev/sda"

Hint: If you don't get a console, use your laptop's VNC client to connect. virsh vncdisplay debian1 will show you which VNC screen to connect to.

kernel_args="root=/dev/sda" tells the kernel which device to mount as its root filesystem.

You should now have a running VM using the image you built. If not, ask for help. Remember to do some basic debugging first:

# virsh list
# virsh dumpxml debian1
# virsh console debian1

4 Build a VM image file

grml-debootstrap can also be used to create a familiar disk image file, containing an MBR and a partition table, so it can be booted directly.

# grml-debootstrap --vmfile --target /var/lib/libvirt/images/debian2.img \
    --hostname debian2 -v \
    --mirror http://apt.ws.nsrc.org:3142/cdn.debian.net/debian

This will take a few minutes. It builds a raw image, default size 2GB. If you watch carefuly, you will see messages about devices like /dev/mapper/loop0p1, which should become clearer in the next section.

4.1 Check the image

First, notice that this file is "sparse" - the host operating system has only allocated space for blocks which have been written to. You can see this by comparing the size of the file with the disk space allocated to the file.

# ls -lh /var/lib/libvirt/images/debian2.img      # size of file = 2GB
# ls -sh /var/lib/libvirt/images/debian2.img      # space used < 2GB
# du -h /var/lib/libvirt/images/debian2.img       # another way to get the same info

It would be very useful if we could look inside this file to see its contents. The "loop" device lets us attach a file as if it were a block device. However it's slightly awkward because the image is not just a filesystem - it's an image of a disk containing a partition table and a filesystem, which is offset from the beginning of the disk.

Try running the file command on the debian2.img file:

# file /var/lib/libvirt/images/debian2.img

/var/lib/libvirt/images/debian2.img: x86 boot sector; partition 1: ID=0x83, starthead 0, startsector 4096, 4190208 sectors, code offset 0x31

The 'kpartx' utility is able to set up separate loopback devices for each partition contained within the image:

# kpartx -av /var/lib/libvirt/images/debian2.img
# ls /dev/mapper/loop*

You should now see you have a device /dev/mapper/loop0p1 which is a block device to access the data in partition 1 of the image, and now you can mount the filesystem and look inside:

# mount /dev/mapper/loop0p1 /mnt
# ls /mnt
... do whatever you need inside the /mnt directory

IMPORTANT!! As before, once you have finished using the mount, it's vital to unmount it and deconfigure the loopback device - otherwise you risk corruption if you start running a VM on this image at the same time as the host system has it mounted.

# umount /mnt
# kpartx -d /var/lib/libvirt/images/debian2.img

We will discuss loopback devices and the associated tools (losetup) in class, as well as the difference between the image we created using LVM initially (debian1) and the second image created as a file (debian2).

4.2 Convert the image

If you want the image in a different format, e.g. qcow2, then use qemu-img convert to convert it. This will take a little while as it has to read and rewrite the entire image.

# qemu-img convert -f raw -O qcow2 /var/lib/libvirt/images/debian2.img /var/lib/libvirt/images/debian2.qcow2

qemu-img is part of the qemu-utils package.

Once it is finished, compare the file size:

# du -sh debian2.*

qcow2 images can be mounted in a different way, using qemu-nbd (network block device):

# modprobe nbd max_part=4
# qemu-nbd -c /dev/nbd0 /var/lib/libvirt/images/debian2.qcow2
# mount /dev/nbd0p1 /mnt
# ls /mnt
... do work here
# umount /mnt
# qemu-nbd -d /dev/nbd0

4.3 Start a VM

Finally, to boot this as a VM using libvirt, you need to create a VM inside libvirt and attach the image. Again, you will use virt-install to set this up.

$ virt-install --name debian2 --ram 512 \
  --graphics vnc,listen=,password=xyzzy \
  --import --disk /var/lib/libvirt/images/debian2.qcow2,format=qcow2 \

This time there is no need to provide any kernel, because it boots using GRUB and the kernel within the filesystem. However the existence of the partition table makes this sort of image much harder to grow.

5 Optional exercises

5.1 Cloning virtual machines

Another approach to creating VMs is to clone one you have already installed. This can be much faster than doing an installation from scratch, but some work may be needed to update the clone before it can be used.

You need to duplicate the disk image or logical volume, create a new VM definition, start it, and then patch up the new VM.

5.1.1 Duplicate the image file

This part is easy! Let's say you have a VM backed by "debian2.qcow2", and we want to create "debian3.qcow2"

# cd /var/lib/libvirt/images
# cp debian2.qcow2 debian3.qcow2

5.1.2 Create a new VM definition

We will take the libvirt XML definition of debian2, and make some minimal changes to make a machine called debian3 with the same parameters.

# virsh dumpxml debian2 >debian3.xml
# vi debian3.xml

-- CHANGE the name

-- DELETE the <uuid>...</uuid> line

-- CHANGE the source file line
<source file='/var/lib/libvirt/images/debian3.qcow2'/>

-- DELETE the <mac address='52:54:xx:xx:xx:xx'/> line

Now define the new VM from this file (the missing UUID and MAC address should be assigned automatically).

# virsh define debian3.xml

If that gives no error, then start your new VM:

# virsh start debian3

5.1.3 Patch up the VM

Get a console onto your new VM, and you'll find there are some changes you need to make.

Firstly, this host still thinks its name is "debian2". So you need to edit /etc/hostname, /etc/hosts, /etc/mailname and maybe others.

Secondly, this host may still be configured with the IP address of debian2. In our case we're using DHCP so it's not a problem, but with static addressing you would have to be very careful to avoid conflicts.

Thirdly, this host may still remember the MAC address which used to be on eth0, and so call its primary interface something else (like eth1), because it doesn't recognize the MAC address assigned when we did a virsh define - and thinkg it's a new interface.

If the VM is Ubuntu, check /etc/udev/rules.d/70-persistent-net.rules. If it is CentOS, check for MAC addresses in /etc/sysconfig/network-scripts/ifcfg-eth*

For operating systems like Windows, there may be other things to do such as changing the machine ID in the registry. We do not have time in this workshop to go into details.

5.2 Further reading

Another tool to access the data inside a VM image is 'guestfish' - the shell frontend to libguestfs. Install it, read the documentation and try it out.

# apt-get install guestfish    # say "Yes" to "Install supermin appliance now"
# man guestfish

It works by building a tiny VM (the "supermin appliance") and running it with the disk image attached. It's especially helpful in complex scenarios, such as when the guest disk is a logical volume but the guest is also using LVM inside that.

The related package 'libguestfs-tools' includes further tools such as virt-resize