ZFS on Virtual Machines

I’ve grown to love ZFS. But using on Virtual Machines did cause me to learn a new trick. Virtual Machines allow you lously allocate disk space which can be very helpful but managing this doesn’t always play nice with ZFS.

Virtual Machines and Thin Provisioning

Virtual Machine products usually allow a feature that called “Thin Provisioning” under the VMware family of products. In detail, when you provision a “disk drive” for your VM, you can just designate the size of the drive without actually claiming any space for it. So, you can thin provision a 120GB drive and only pull a few kilobytes from the filesystem for the hypervisor to manage things. This works because the drive will return a set of zeros when you read a sector that hasn’t been previously written. This is all built on a Unix feature called Sparse files. In Unix, a sparse file behaves exactly this way. If you read a block in the file that hasn’t ever been written to, you get zeros without the Unix having to do anything. When you write that sector, the block is actually saved to the disk and the next time you do a read, you’ll get what you wrote last time. In VMware this is handy because your VM’s disk drive only stores the stuff that you’ve written.

Day to day use

As you use this thinly provisioned drive you’ll write data and the hypervisor will store it and all work work great. What’s interesting is what happens when you delete a file. Deleting a file won’t zero the sectors that it use, and that wouldn’t matter any how because a sparse file in Unix differentiates between a block of zeros in an unwritten portion of the file and a block that you filled with zeros through a write operation. The second block of zeros actually takes up space. It’s important to note that as far as the hypervisor is concerned, a block of zeros in a thin provisioned drive never needs to take up space.

Reclaiming space

After a period of use, a normal filesystem will have a bunch of block that are written, and filled with contents that used to be a part of files that have since been deleted. If the disk drive that we’re talking about is a sparse file on a VMware server this file will take up more and more space up until you actually “fill up the drive”. A standard method for reclaiming this space on older filesystems was to overwrite the entire unallocated part of the drive with zeros and ask VMware to re-examine the virtual drive, punching holes where any large segment of zeros is found.

Where ZFS fits in

Older filesystems like UFS and EXT4 generally don’t support advanced operations like automatic filesystem object compression and deduplication. On these systems writing a block of zeros to a file will generally actually write a block of zeros to the disk drive. On ZFS, where compression and deduplication are available, writing a block of zeros need not write any data at all. In effect you can’t write a block of zeros to a file on a host using zfs.

Reclaiming space normally

To reclaim space from EXT4 or UFS the general procedure is simple. Within the running VM:

## Shut the system down to single user mode

# for fs in <mounted-filesystems>; do
>     dd if=/dev/zero of="${fs}/.zerofill" bs=26214400
>     rm "${fs}/.zerofill"
> done

## Now all the unallocated space should have been zeroed out
## shutdown the VM

Now on the host run whatever tool you need to reclaim space from the VM. This usually involves having the hypervisor or a utility read the virtual disk block by block discarding and blocks that are completely filled with zeros. In VMware Fusion, this is the utility:

# vmware-vdiskmanager -k <virtual-disk-file>

It’s usually that simple. When the filesystem is ZFS, things are different. For one thing, several disk mounting points will exist as a part of a zfs pool. You only need to fill one “filesystem” on a pool to zero out all of the unallocated space. More importantly though with compression and dedup on, you won’t be able to write zeros to the virtual drive. Here’s the procedure on a machine with zfs:

## Again, shut the system down to single user mode.

# for p in <affected-zfs-pools>; do
>    zfs set compression=off,dedup=off "${p}"
>    dd if=/dev/zero of=<pool-mountpoint>/.zerofill bs=26214400
>    rm <pool-mountpoint/.zerofill
>    zfs set compression=on,dedup=on
> done
# shutdown -h now

At this point the machine you should be able to reclaim the zeroed space using the hypervisors tools as before.

Changing VMware Fusion network settings

For those that run VMware Fusion, the “/Library/Preferences/VMware Fusion”  directory on the Mac is a wealth of information.

$ cd /Library/Preferences/VMware\ Fusion/
$ ls -l
total 40
-r--r--r-- 1 root wheel 31 Nov 17 11:01 lastLocationUsed
-rw-r--r-- 1 root wheel 548 May 5 2018 license-fusion-100-e3-201704
-rw-r--r-- 1 root wheel 689 May 5 2018 license-fusion-100-e4-201704
-rw-r--r-- 1 root wheel 547 Dec 6 2013 license-fusion-50-e3-201202
-rw-r--r-- 1 root wheel 547 Apr 10 2014 license-fusion-60-e3-201303
-rw-r--r-- 1 root wheel 547 Oct 31 2014 license-fusion-70-e3-201404
-rw-r--r-- 1 root wheel 688 Oct 25 2014 license-fusion-70-e4-201404
-rw-r--r-- 1 root wheel 547 Jun 23 2016 license-fusion-80-e3-201505
-rw-r--r-- 1 root wheel 740 Nov 3 05:54 networking
-rw-r--r-- 1 root wheel 740 Aug 7 20:23 networking.bak.0
drwxr-xr-x 10 root wheel 340 Nov 17 11:01 thnuclnt
drwxr-xr-x 4 root wheel 136 Dec 6 2013 vmnet1
drwxr-xr-x 7 root wheel 238 Dec 6 2013 vmnet8
$

The license-fusion… files have your license keys as well as other information in them. But today, the jewel for me is the networking file:

$ cat networking
VERSION=1,0
answer VNET_1_DHCP yes
answer VNET_1_DHCP_CFG_HASH E08B... ...D0D8
answer VNET_1_HOSTONLY_NETMASK 255.255.255.0
answer VNET_1_HOSTONLY_SUBNET 172.a.b.0
answer VNET_1_VIRTUAL_ADAPTER yes
answer VNET_8_DHCP yes
answer VNET_8_DHCP_CFG_HASH 2031... ...F498
answer VNET_8_HOSTONLY_NETMASK 255.255.255.0
answer VNET_8_HOSTONLY_SUBNET 10.c.d.0
answer VNET_8_NAT yes
answer VNET_8_VIRTUAL_ADAPTER yes
...

This file defines the networks that your host-only and nat network adapters use. VMware appears smart enough to avoid network collisions e.g. using 192.168.1.0/255 for the NAT adapter at vmnet8 when that’s also the network configured on your home router.

According to this article (VMware login required), simply editing this file and restarting VMware Fusion’s networking component should change the dhcp setting that your machine uses. Any skilled system or network administrator should be able to get their hands around that.

Finally, the lines that specify DHCP hashes appear to be the mechanism that VMware uses to detect changes in the networking file. If you dig deeper, there’s a directory for vmnet1.

$ ls -l vmnet1
total 8
-rw-r--r--  1 root  wheel  1575 Nov 17 11:01 dhcpd.conf
-rw-r--r--  1 root  wheel  1575 Nov 17 11:01 dhcpd.conf.bak
$ cat vmnet1/dhcpd.conf
# Configuration file for ISC 2.0 vmnet-dhcpd operating on vmnet1.
#
# This file was automatically generated by the VMware configuration program.
# See Instructions below if you want to modify it.
#
# We set domain-name-servers to make some DHCP clients happy
# (dhclient as configured in SuSE, TurboLinux, etc.).
# We also supply a domain name to make pump (Red Hat 6.x) happy.
#

###### VMNET DHCP Configuration. Start of "DO NOT MODIFY SECTION" #####
# Modification Instructions: This section of the configuration file contains
# information generated by the configuration program. Do not modify this
# section.
# You are free to modify everything else. Also, this section must start
# on a new line
# This file will get backed up with a different name in the same directory
# if this section is edited and you try to configure DHCP again.

# Written at: 11/17/2018 11:01:21
allow unknown-clients;
default-lease-time 1800; # default is 30 minutes
max-lease-time 7200; # default is 2 hours

subnet 172.a.b.0 netmask 255.255.255.0 {
range 172.a.b.128 172.a.b.254;
option broadcast-address 172.a.b.255;
option domain-name-servers 172.a.b.1;
option domain-name localdomain;
default-lease-time 1800; # default is 30 minutes
max-lease-time 7200; # default is 2 hours
}
host vmnet1 {
hardware ethernet 00:50:56:x:y:z;
fixed-address 172.a.b.1;
option domain-name-servers 0.0.0.0;
option domain-name "";
}
####### VMNET DHCP Configuration. End of "DO NOT MODIFY SECTION" #######
$

 

This is just a standard dhcpd.conf file as you would see if you ran isc-dhcpd. The interesting thing is that the hash is what you get if you do this:
$ sed -ne '/VMNET DHCP.*Start/,/VMNET DHCP.*End/ p' vmnet1/dhcpd.conf | shasum
e08b... ...d0d8 -

The more you know…