virt-builder can throw out new virtual machines with existing operating systems in a few seconds, and you can also write these directly to a USB key or hard disk:
# virt-builder fedora-20 -o /dev/sdX
What you’ve not been able to do is create a bootable CD-ROM or ISO image.
For that I was using the awful livecd-creator program. This needs root and is incredibly fragile. You can have a kickstart that works one day, but not the next, and requires massive hacks to get working … which is the exact reason why I set off to find out how to make virt-builder create ISOs.
The background as to why this is difficult: CDs are not writable.
You can take all the files from a Fedora guest built by virt-builder and turn them into an ISO, and put ISOLINUX on it but such a guest would not be able to boot, or at least, it would fail the first time it tried to write to the disk. One day overlayfs (which just went upstream a few days ago) will solve this, but until that is widely available in upstream kernels, we’re going to need something that creates a writable overlay at boot time.
I have chosen dracut (another tool I have a love/hate, mainly hate, relationship with), which has a useful module called dmsquash-live. This implements the boot side of making a live CD writable, for Fedora and RHEL. It’s what livecd-creator uses.
dmsquash-live demands a very particular ISO layout, but it wasn’t hard to reverse engineer it by reading the code carefully and a lot of trial and error.
It requires that we have a filesystem containing a squashfs in a particular location on the CD:
That squashfs has to contain inside it a disk image with this precise name:
and the disk image is the root filesystem.
The script below creates all of this, and effectively replaces livecd-creator with something manageable that doesn’t require root, and is only 100 lines of shell (take that OO/Python!)
Update: Kashyap notes that the script will fail if you’re using tmp-on-tmpfs, so you might need to disable that or modify the script to use
Once you’ve run the script you can try booting the image using:
$ qemu-kvm -m 2048 -cdrom boot.iso -boot d
One improvement to this script would be to remove the dependency on dmsquash-live. We don’t need the baroque complexity of this script, and could write a custom dracut module (perhaps even, a tiny self-contained initramfs) which would do what we need. It could even use overlayfs to simplify things greatly.
# Make bootable ISO from virt-builder
# This requires the Fedora
# squashfs/rootfs machinery. See:
# Build the regular disk image, but also
# build a special initramfs which has
# the dmsquash-live & pollcdrom modules
# enabled. We also need to kill SELinux
# relabelling, and hence SELinux.
cat > postinstall <<'EOF'
version=` rpm -q kernel | sort -rV | head -1 | sed 's/kernel-//' `
echo installed kernel version: $version
dracut --no-hostonly --add "dmsquash-live pollcdrom" /boot/initrd0 $version
virt-builder fedora-20 \
--install kernel \
--root-password password:123456 \
--delete /.autorelabel \
# Extract the root filesystem (as an ext3/4 disk image).
guestfish --progress-bars --ro -a fedora-20.img -i \
download /dev/sda3 rootfs.img
# Update /etc/fstab in the rootfs (but NOT in the original guest)
# so it works for the CD
virt-customize -a rootfs.img \
--write '/etc/fstab:/dev/root / ext4 defaults 1 1'
# Turn the rootfs.img into a squashfs
# which must contain the layout
rm -rf CDroot
rm -f squashfs.img
mkdir -p CDroot/LiveOS
mv rootfs.img CDroot/LiveOS
mksquashfs CDroot squashfs.img
# Create the CD layout.
rm -rf CDroot
mkdir -p CDroot/LiveOS
cp squashfs.img CDroot/LiveOS/
# Get the kernel (only) from the disk
virt-builder --get-kernel ../../fedora-20.img
mv vmlinuz* vmlinuz0
# Get the special initrd that we built
guestfish --ro -a fedora-20.img -i \
download /boot/initrd0 CDroot/isolinux/initrd0
# ISOLINUX configuration.
cat > CDroot/isolinux/isolinux.cfg <<EOF
append initrd=initrd0 rd.live.image root=CDLABEL=boot rootfstype=auto rd.live.debug console=tty0 rd_NO_PLYMOUTH
# Rest of ISOLINUX installation.
cp /usr/share/syslinux/isolinux.bin CDroot/isolinux/
cp /usr/share/syslinux/ldlinux.c32 CDroot/isolinux/
cp /usr/share/syslinux/libcom32.c32 CDroot/isolinux/
cp /usr/share/syslinux/libutil.c32 CDroot/isolinux/
cp /usr/share/syslinux/vesamenu.c32 CDroot/isolinux/
# Create the ISO.
rm -f boot.iso
mkisofs -o boot.iso \
-J -r \
-V boot \
-b isolinux/isolinux.bin -c isolinux/boot.cat \
-no-emul-boot -boot-load-size 4 -boot-info-table \