Appendix C. Whole Disk Images
The main overcloud image is a flat partition image. This means it contains no partitioning information or bootloader on the images itself. The director uses a seperate kernel and ramdisk when booting and creates a basic partitioning layout when writing the overcloud image to disk. However, you can create a whole disk image, which includes a partitioning layout and bootloader.
C.1. Creating Whole Disk Images
Creating a whole disk image from the overcloud-full.qcow2
flat partition image involves the following steps:
-
Open the
overcloud-full
flat partition as a base for our whole disk image. - Create a new whole disk image with the desired size. This example uses a 10 GB image.
-
Create partitions and volumes on the whole disk image. Create as many partitions and volumes necessary for your desired whole disk image. This example creates an isolated partition for
boot
and logical volumes for the other content in the filesystem. - Create the initial filesystems on the partitions and volumes.
- Mount flat partition filesystem and copy content to the right partitions on the whole disk image.
-
Generate the
fstab
content and save it to/etc/fstab
on the whole disk image. - Unmount all the filesystems.
-
Mount the partitions on the whole disk image only. Start with the root partition mounted at
/
and mount the other partition in their respective directories. -
Install the bootloader using shell commands to execute
grub2-install
andgrub2-mkconfig
on the whole disk image. This installs thegrub2
bootloader in the whole disk image. -
Update
dracut
to provide support for logical volume management - Unmount all the filesystems and close the image
Manually Creating a Whole Disk Image
A recommended tool for creating images is guestfish
, which you install using the following command:
$ sudo yum install -y guestfish
Once installed, run the guestfish
interactive shell:
$ guestfish Welcome to guestfish, the guest filesystem shell for editing virtual machine filesystems and disk images. Type: 'help' for help on commands 'man' to read the manual 'quit' to quit the shell ><fs>
For more information on using guestfish
see "The Guestfish Shell" in the Virtualization Deployment and Administration Guide for Red Hat Enterprise Linux 7.
C.2. Automatically Creating a Whole Disk Image
The following Python script uses the guestfish
library to automatically generate the whole disk image.
#!/usr/bin/env python import guestfs import os # remove old generated drive try: os.unlink("/home/stack/images/overcloud-full-partitioned.qcow2") except: pass g = guestfs.GuestFS(python_return_dict=True) # import old and new images print("Creating new repartitioned image") g.add_drive_opts("/home/stack/images/overcloud-full.qcow2", format="qcow2", readonly=1) g.disk_create("/home/stack/images/overcloud-full-partitioned.qcow2", "qcow2", 10.2 * 1024 * 1024 * 1024) #10.2G g.add_drive_opts("/home/stack/images/overcloud-full-partitioned.qcow2", format="qcow2", readonly=0) g.launch() # create the partitions for new image print("Creating the initial partitions") g.part_init("/dev/sdb", "mbr") g.part_add("/dev/sdb", "primary", 2048, 616448) g.part_add("/dev/sdb", "primary", 616449, -1) g.pvcreate("/dev/sdb2") g.vgcreate("vg", ['/dev/sdb2', ]) g.lvcreate("var", "vg", 5 * 1024) g.lvcreate("tmp", "vg", 500) g.lvcreate("swap", "vg", 250) g.lvcreate("home", "vg", 100) g.lvcreate("root", "vg", 4 * 1024) g.part_set_bootable("/dev/sdb", 1, True) # add filesystems to volumes print("Adding filesystems") ids = {} keys = [ 'var', 'tmp', 'swap', 'home', 'root' ] volumes = ['/dev/vg/var', '/dev/vg/tmp', '/dev/vg/swap', '/dev/vg/home', '/dev/vg/root'] swap_volume = volumes[2] count = 0 for volume in volumes: if count!=2: g.mkfs('ext4', volume) ids[keys[count]] = g.vfs_uuid(volume) count +=1 # create filesystem on boot and swap g.mkfs('ext4', '/dev/sdb1') g.mkswap_opts(volumes[2]) ids['swap'] = g.vfs_uuid(volumes[2]) # mount drives and copy content print("Start copying content") g.mkmountpoint('/old') g.mkmountpoint('/root') g.mkmountpoint('/boot') g.mkmountpoint('/home') g.mkmountpoint('/var') g.mount('/dev/sda', '/old') g.mount('/dev/sdb1', '/boot') g.mount(volumes[4], '/root') g.mount(volumes[3], '/home') g.mount(volumes[0], '/var') # copy content to root results = g.ls('/old/') for result in results: if result not in ('boot', 'home', 'tmp', 'var'): print("Copying %s to root" % result) g.cp_a('/old/%s' % result, '/root/') # copy extra content folders_to_copy = ['boot', 'home', 'var'] for folder in folders_to_copy: results = g.ls('/old/%s/' % folder) for result in results: print("Copying %s to %s" % (result, folder)) g.cp_a('/old/%s/%s' % (folder, result), '/%s/' % folder) # create /etc/fstab file print("Generating fstab content") fstab_content = """ UUID={boot_id} /boot ext4 defaults 0 2 UUID={root_id} / ext4 defaults 0 1 UUID={swap_id} none swap sw 0 0 UUID={tmp_id} /tmp ext4 defaults 0 2 UUID={home_id} /home ext4 defaults 0 2 UUID={var_id} /var ext4 defaults 0 2 """.format( boot_id=g.vfs_uuid('/dev/sdb1'), root_id=ids['root'], swap_id=ids['swap'], tmp_id=ids['tmp'], home_id=ids['home'], var_id=ids['var']) g.write('/root/etc/fstab', fstab_content) # unmount filesystems g.umount('/root') g.umount('/boot') g.umount('/old') g.umount('/var') # mount in the right directories to install bootloader print("Installing bootloader") g.mount(volumes[4], '/') g.mkdir('/boot') g.mkdir('/var') g.mount('/dev/sdb1', '/boot') g.mount(volumes[0], '/var') # do a selinux relabel g.selinux_relabel('/etc/selinux/targeted/contexts/files/file_contexts', '/', force=True) g.selinux_relabel('/etc/selinux/targeted/contexts/files/file_contexts', '/var', force=True) g.sh('grub2-install --target=i386-pc /dev/sdb') g.sh('grub2-mkconfig -o /boot/grub2/grub.cfg') # create dracut.conf file dracut_content = """ add_dracutmodules+="lvm crypt" """ g.write('/etc/dracut.conf', dracut_content) # update initramfs to include lvm and crypt kernels = g.ls('/lib/modules') for kernel in kernels: print("Updating dracut to include modules in kernel %s" % kernel) g.sh('dracut -f /boot/initramfs-%s.img %s --force' % (kernel, kernel)) g.umount('/boot') g.umount('/var') g.umount('/') # close images print("Finishing image") g.shutdown() g.close()
Save this script as a executable file on the undercloud and run it as the stack
user:
$ ./whole-disk-image.py
This automatically creates the whole disk image from the flat partition image. Once the whole disk image creation completes, replace the old overcloud-full.qcow2
image:
$ mv ~/images/overcloud-full.qcow2 ~/images/overcloud-full-old.qcow2 $ cp ~/images/overcloud-full-partitioned.qcow2 ~/images/overcloud-full.qcow2
You can now upload the whole disk image along with your other images.
C.3. Encrypting Volumes on Whole Disk Images
You can also use guestfish
to encrypt volumes on your whole disk image. This requires using the luks-format
subcommand, which erases the current volume and creates an encrypted volume.
The following Python script opens the overcloud-full-partitioned.qcow2
image created previously, removes the current home
volume (which is empty), and replaces it with an encrypted home
volume:
#!/usr/bin/env python import binascii import guestfs g = guestfs.GuestFS(python_return_dict=True) g.add_drive_opts("/home/stack/images/overcloud-full-partitioned.qcow2", format="qcow2", readonly=0) g.launch() random_content = binascii.b2a_hex(os.urandom(1024)) g.luks_format('/dev/vg/home', random_content, 0) g.luks_open('/dev/vg/home', random_content, 'cryptedhome') g.vgscan() g.vg_activate_all(True) g.mkfs('ext4', '/dev/mapper/cryptedhome') g.mount('/dev/vg/root','/') volumes = lvs() volumes.remove('/dev/vg/home') volumes.remove('/dev/vg/root') volumes.remove('/dev/vg/swap') fstab_content = [] fstab_content.append('UUID=%s /boot ext4 defaults 0 2' % g.vfs_uuid('/dev/sda1')) fstab_content.append('UUID=%s / ext4 defaults 0 1' % g.vfs_uuid('/dev/vg/root')) fstab_content.append('UUID=%s none swap sw 0 0' % g.vfs_uuid('/dev/vg/swap')) fstab_content.append('/dev/mapper/cryptedhome /home ext4 defaults 0 1') for volume in volumes: volume_name = volume.replace('/dev/vg/', '') fstab_content.append('UUID=%s /%s ext4 defaults 0 2' % (g.vfs_uuid(volume), volume_name)) g.write('/etc/fstab', '\n'.join(fstab_content)) print '\n'.join(fstab_content) g.write('/root/home_keyfile', random_content) g.chmod(0400, '/root/home_keyfile') mapper = """ home UUID={home_id} /root/home_keyfile """.format(home_id=g.vfs_uuid('/dev/mapper/cryptedhome')) g.write('/etc/crypttab', mapper) g.luks_close('/dev/mapper/cryptedhome') g.selinux_relabel('/etc/selinux/targeted/contexts/files/file_contexts', '/', force=True) g.shutdown() g.close()
This script also:
-
Creates a key (
random_content
) -
Regenerates the
/etc/fstab
file with the new encrypted volume -
Saves the encryption key at
/root/home_keyfile
-
Generates a
crypttab
file to automatically decrypt the volume using the/root/home_keyfile
)
Use this script as an example to create encrypted volumes as part of your whole disk image creation process.
C.4. Uploading Whole Disk Images
To upload a whole disk image, use the --whole-disk-image
option with the image upload command. For example:
$ openstack overcloud image upload --whole-disk --image-path /home/stack/images
This command uploads the images from /home/stack/images
but treats the overcloud-full.qcow2
file as a whole disk image. This means you must rename the desired whole disk image to overcloud-full.qcow2
before running the image upload command.