VMware templates and OVF
VMware VCenter and OVF Environment properties together give us a great tool to achieve ultra-fast deployment and re-deployment of virtual machines. Since you can easily transport custom properties from the VCenter Settings page all the way into the guest OS, it's very simple to achieve truly low-touch deployment of ready-made templates.
I now have a template for a Ubuntu 9.04 Server system set up and customized to my basic needs, that can be deployed, including hostname, mailname and network configuration, within just a few minutes. Using VLANs, I can assign this machine to any network - and even move it to another one without even logging in to the Linux system (although in this case I fall back on a Windows-style reboot, just to make it easy... :) ).
The basic building blocks are these, given a fully configured virtual machine to be used as a template:
Properties in VCenter
On the Options tab of the Settings dialog in VCenter, you can define a number of properties (vApp Options | Advanced | Properties) which can be made visible inside the guest system, after you set up some basic configuration in other sections under vApp Options. I chose to use "VMware Tools" as my "OVF Environment Transport".
The properties I've defined are
| ip | IP address |
| netmask | Network mask |
| gateway | Default gateway |
| hostname | Host name |
| domain | Domain name (for the "hosts" file and for the mailname |
| dns1 | First DNS server |
| dns2 | Second DNS server |
| srchdomains | DNS search domains, space separated |
Reading the properties in the guest OS
If you, like me, used "VMware Tools" as the transport method for the environment data, you can read the OVF environment inside the guest OS using the command
vmware-guestd --cmd 'info-get guestinfo.ovfEnv'
If you use the "ISO Image" method, you should get the same information through a virtual CD-ROM device that can be mounted in the guest OS.
What you get in both cases is an XML representation of the environment. You can find the schema through a link on this page if you need it.
To parse this properly, you need an XML parser. I decided to use an XSL transform, using Sablotron, which is available as a standard Ubuntu package. My XSL style sheet (/usr/local/etc/ovf.xsl) just selects the Properties from the XML file and produces a list of shell script variable assignments:
<?xml version="1.0" encoding="UTF-8"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:oe="http://schemas.dmtf.org/ovf/environment/1"> <xsl:output method="text" indent="no"/> <xsl:template match="/"> <xsl:for-each select="oe:Environment/oe:PropertySection/oe:Property"> <xsl:text>export ovf_</xsl:text> <xsl:value-of select="@oe:key"/> <xsl:text>="</xsl:text> <xsl:value-of select="@oe:value"/> <xsl:text>"
</xsl:text> </xsl:for-each> </xsl:template> </xsl:stylesheet>
Running the transform is as simple as this:
vmware-guestd --cmd 'info-get guestinfo.ovfEnv' | sabcmd /usr/local/etc/ovf.xsl
The result looks something like this:
export ovf_dns1="208.67.220.220" export ovf_dns2="208.67.222.222" export ovf_domain="foo.nu" export ovf_gateway="1.2.3.1" export ovf_hostname="mybox" export ovf_ip="1.2.3.4" export ovf_netmask="255.255.255.192" export ovf_srchdomains="apple.com microsoft.com"
The configuration script
This stuff is run at boot time only, and it will set the hostname (and domain name in /etc/hosts), the mailname, the ip address, netmask and gateway, the dns servers, the search domains, and the postfix mailhost data.
I use the ipcalc package to calculate network and broadcast address from the given data. ipcalc is also available as a standard Ubuntu package.
The script is built with nasty old shell script syntax, since that's what I default to when nothing is forcing me into another style. Whenever there are brackets with just a few spaces inside, they are most likely one space and one TAB character.#!/bin/sh export PATH=$PATH:/usr/sbin:/usr/bin:/usr/local/bin IFFILE=/etc/network/interfaces HOSTSFILE=/etc/hosts RESOLVCONF=/etc/resolv.conf HOSTNAMEFILE=/etc/hostname MAILNAMEFILE=/etc/hostname PFMAINCF=/etc/postfix/main.cf XSLFILE=/usr/local/etc/ovf.xsl TMPFILE=`mktemp` ifcount=`grep "^iface " $IFFILE | grep -vc " lo "` if [ "$ifcount" != 1 ]; then echo "Can't handle the format of $IFFILE. Too many interfaces?" exit 1 fi eval `vmware-guestd --cmd 'info-get guestinfo.ovfEnv' | sabcmd $XSLFILE` if [ ! -z "$ovf_dns1" -o ! -z "$ovf_dns2" -o ! -z "$ovf_dns" ]; then txt_dns="`echo ' dns-nameservers '$ovf_dns $ovf_dns1 $ovf_dns2 | sed 's/ */ /g'`" d_dns="`echo $ovf_dns $ovf_dns1 $ovf_dns2 | sed 's/ */ /g'`" else txt_dns="`grep '^[ ]dns-nameservers ' $IFFILE | head -1`" d_dns=`echo $txt_dns | sed 's/^.*dns-nameservers[ ]*\(.*\)$[ ]*/\1/'` fi if [ ! -z "$ovf_ip" ]; then txt_ip=" address $ovf_ip" d_ip=$ovf_ip else txt_ip="`grep '^[ ]address ' $IFFILE | head -1`" d_ip=`echo $txt_ip | sed 's/^.*address[ ]*\(.*\)[ ]*$/\1/'` fi if [ ! -z "$ovf_netmask" ]; then txt_netmask=" netmask $ovf_netmask" d_netmask=$ovf_netmask else txt_netmask="`grep '^[ ]netmask ' $IFFILE | head -1`" d_netmask=`echo $txt_netmask | sed 's/^.*netmask[ ]*\(.*\)$/\1/'` fi d_network=`ipcalc -nb $d_ip/$d_netmask | grep "^Network:" | sed 's/^.*:[ ]*\([0-9.]*\)\/.*$/\1/'` txt_network=" network $d_network" d_broadcast=`ipcalc -nb $d_ip/$d_netmask | grep "^Broadcast:" | sed 's/^.*:[ ]*\([0-9.]*\).*$/\1/'` txt_broadcast=" broadcast $d_broadcast" if [ ! -z "$ovf_gateway" ]; then txt_gateway=" gateway $ovf_gateway" d_gateway=$ovf_gateway else txt_gateway="`grep '^[ ]gateway ' $IFFILE | head -1`" d_gateway=`echo $txt_gateway | sed 's/^.*gateway[ ]*\(.*\)$/\1/'` fi if [ ! -z "$ovf_srchdomains" ]; then txt_srchdomains=" dns-search $ovf_srchdomains" d_srchdomains=$ovf_srchdomains else txt_srchdomainis="`grep '^[ ]dns-search ' $IFFILE | head -1`" d_srchdomainis="`echo $txt_srchdomains | sed 's/^.*dns-search[ ]*\(.*\)$/\1/'`" fi d_hostname=`hostname 2>/dev/null` if [ ! -z "$ovf_hostname" -a "$ovf_hostname" != "$d_hostname" ]; then d_hostname=$ovf_hostname hostname "$d_hostname" echo "$d_hostname" > $HOSTNAMEFILE fi d_domain=`hostname -d 2>/dev/null` if [ ! -z "$ovf_domain" -a "$ovf_domain" != "$current_domain" ]; then d_domain=$ovf_domain fi cat > $TMPFILE <<EOF 127.0.0.1 localhost $d_ip $d_hostname.$d_domain $d_hostname # The following lines are desirable for IPv6 capable hosts ::1 localhost ip6-localhost ip6-loopback fe00::0 ip6-localnet ff00::0 ip6-mcastprefix ff02::1 ip6-allnodes ff02::2 ip6-allrouters ff02::3 ip6-allhosts EOF if diff $HOSTSFILE $TMPFILE > /dev/null 2>&1; then : else echo " * OVF: Changing hostname/domain name" mv $TMPFILE $HOSTSFILE if [ -x /etc/init.d/syslog-ng ]; then /etc/init.d/syslog-ng restart fi fi if [ -f $PFMAINCF ]; then sed -e 's/^myhostname = .*$/myhostname = '$d_hostname.$d_domain'/' -e 's/^mydestination = [^,]*\(, local.*\)$/mydestination = '$d_hostname.$d_domain'\1/' $PFMAINCF > $TMPFILE if diff $PFMAINCF $TMPFILE > /dev/null; then : else echo " * OVF: Chainging mailname" mv $TMPFILE $PFMAINCF echo "$d_hostname.$d_domain" > $MAILNAMEFILE fi fi cat > $TMPFILE <<EOF # This file describes the network interfaces available on your system # and how to activate them. For more information, see interfaces(5). # The loopback network interface auto lo iface lo inet loopback # The primary network interface auto eth0 iface eth0 inet static $txt_ip $txt_netmask $txt_network $txt_broadcast $txt_gateway # dns-* options are implemented by the resolvconf package, if installed $txt_dns $txt_srchdomains EOF if diff $IFFILE $TMPFILE > /dev/null 2>&1; then rm $TMPFILE else echo " * OVF: Changing network config" mv $TMPFILE $IFFILE /etc/init.d/networking restart fi echo "search $d_srchdomains" > $TMPFILE for ns in $d_dns; do echo nameserver $ns >> $TMPFILE done mv $TMPFILE $RESOLVCONF
I run this using the sysvinit subsystem, using an init script that looks like this:
#!/bin/sh # Basic support for IRIX style chkconfig # chkconfig: 235 19 08 # description: Manages the OVF data # Basic support for the Linux Standard Base Specification 1.0.0 (to be used by # insserv for example) ### BEGIN INIT INFO # Provides: vmware-tools # Required-Start: $network # Required-Stop: $network # Default-Start: 2 3 5 # Default-Stop: 0 6 # Description: Manages the OVF data ### END INIT INFO main() { # See how we were called. case "$1" in start) /usr/local/bin/ovfconfig.sh ;; *) echo "Usage: `basename "$0"` {start|stop|status|restart|force-reload}" exit 1 esac exit 0 } main "$@"
This script is configured to run after vmware-tools has started.
Conclusion
With all this in place, I can deploy, configure and start a new Linux system without any in-guest customization, in about five minutes. This can of course be improved in many ways but it does show the possibilities of this system.
All three files are in the attached archive, so use that instead of cut and paste.
| Attachment | Size |
|---|---|
| ovfconfig.tar.gz | 2.23 KB |


Comments
Thanks for a great post.
I was wondering how you found the ovfEnv variable in
vmware-guestd --cmd 'info-get guestinfo.ovfEnv'
And if you know a document that shows other variables. The only one that I have been able to find at the moment is IP
I am hoping to get the display name of the VM to set its hostname.
Hi! I don't remember where I originally found the info, but this seems to be a good guide: http://www.vmware.com/support/developer/scripting-API/doc/Scripting_API.pdf
/Hans