Recently a team mate asked me to create a small linux image with a certain tool set, so he can use it for network testing. You may say, why to create it, if you can download pre-created images already. Well, thats true, but it was a nice challenge and an opportunity to dust off my Linux skills.
I have to state that what I did is far from perfect, considering I’ve spent only several hours on it. but it can be a good start for anyone trying to do the same.
Now, creation of OVF/OVA templates in VMware vSphere is nothing new. There are bunch of articles out there on the web, some are old some are new, but I wasn’t able to find even one which would cover the process end to end.
So here is what I’ve done.
1. Choose a distro.
First of all I had to decide what Linux distro to use. Not much to think for me, I know RedHat, I am even certified a RHCSA, so well, I will use CentOS. But the process is universal, should work on any distro.
2. Download and install.
My goal was to make the template as small as possible, so I decided to use the Netinstall image of CentOS. To my surprise it was not listed on the official CentOS website, but a fast Google search resolved that for me.
3. Install a VM and install needed packages.
Nothing much to cover here. Usual VM build with minimal install. Run yum update to update the system.
Then make sure you have open-vm-tools installed. Just run yum install open-vm-tools. If it’s installed it will say it’s installed, if not it will install it for you.
To check if service is running use systemctl status vmtoolsd. VMware tools are critical for customization, you will know why later.
4. Enable and configure vApp Options.
Now, template customization is happening using parameters provided by vApp settings. So we need to Configure those first.
a.Enable vApp Options.
Edit setting of you VM. Click the vApp Options tab. select the Enable vApp options checkbox.
b.Fill in authoring details(if you want)
Enter Name of the image, Version, and any other details you would want to have here.
c.Change OVF settings.
I selected OVF environment transport to be VMware tools.
d. Create OVF properties.
This is where you define which settings you want to gather during OVF.OVA deployment, so that you can configure those on the OS. VM need to be powered off, so you can configure this.
I chose to have IP, Netmask, Default Gateway, hostname and 2 DNS servers.
To create each of these parameters click New under Properties:
Here is an example of IP setting.
- Category: Helps you group properties.
- Label: The label with which this property will be shown in Deployment wizard.
- Key ID: ID of this property in vApp properties XML file. Can be whatever.
- Type: type of the property, in this case it’s a String.
Here is a list of Properties I’ve created for this image.
5. Fetch data
Time to configure OS to fetch those settings on boot, and sett each property.
But to do this we need to have a way to see these properties inside the OS. And here is where VMware tools come into play.
Here is the command to fetch vApp settings from inside the VM.
vmtoolsd --cmd "info-get guestinfo.ovfenv"
Output of this file is in XML format, so all we are left to do is to parse it and do the config.
6. Configure OS
There are many ways to do this in various programming languages. I am not a programmer, so I decided to go with a shell script.
And BTW, I have to say, I am not an expert in shell scripting either, I am sure there are much more much better ways to do what I’ve done. But considering I’ve spend only couple of hours with this, I am quite satisfied.
First things first. How to parse the XML output and get the values we need? Here is what I came up with.
#!/bin/bash vmtoolsd --cmd "info-get guestinfo.ovfenv" > /tmp/ovf_env.xml TMPXML='/tmp/ovf_env.xml' # gathering values date +"%m.%d.%Y %T "; echo "Sorting..." IP=`cat $TMPXML| grep -e ip0 |sed -n -e '/value\=/ s/.*\=\" *//p'|sed 's/\"\/>//'` NETMASK=`cat $TMPXML| grep -e netmask0 |sed -n -e '/value\=/ s/.*\=\" *//p'|sed 's/\"\/>//'` GW=`cat $TMPXML| grep -e gateway |sed -n -e '/value\=/ s/.*\=\" *//p'|sed 's/\"\/>//'` HOSTNAME=`cat $TMPXML| grep -e hostname |sed -n -e '/value\=/ s/.*\=\" *//p'|sed 's/\"\/>//'` DNS0=`cat $TMPXML| grep -e dns0 |sed -n -e '/value\=/ s/.*\=\" *//p'|sed 's/\"\/>//'` DNS1=`cat $TMPXML| grep -e dns1 |sed -n -e '/value\=/ s/.*\=\" *//p'|sed 's/\"\/>//'`
Basically whats happening here is , write the output of the vmtoolsd command to a temp xml file, then I cat this file and parse it using grep and sed to find info I need. I don’t claim this to be the most elegant solution though 🙂 All parameters are assigned to variables for later use.
Now, we have all the settings we need, lets configure OS.
Network settings
CentOS uses Network manager by default, I could disable it to avoid the haste, but I decided to use it instead. Bellow is the code to configure Connection using Network manager CLI.
# NMCLI fetch existing netwrok interface device name IFACE=`nmcli dev|grep ethernet|awk '{print $1}'` # NMCLI fetch existing connection name. This will have to be recreated. CON=`nmcli con show|grep -v NAME| awk '{print $1}'` # NMCLI remove connection nmcli con delete $CON # Create new connection # Check if IP and NETMASk variables exist and are not empty if [ -z ${IP+x} ] && [ -z ${NETMASK+x} ]; then # IF empty configure connection to use DHCP nmcli con add con-name "$IFACE" ifname "$IFACE" type ethernet else # If variables exist, configure interface with IP and netmask and GW. Also set DNS settings in same step. nmcli con add con-name "$IFACE" ifname $IFACE type ethernet ip4 $IP/$NETMASK gw4 $GW && echo "IP set to $IP/$NETMASK. GW set to $GW" nmcli con mod "$IFACE" ipv4.dns "$DNS0,$DNS1" && echo "DNS set to $DNS0,$DNS1" fi
Configure Hostname
hostnamectl set-hostname $HOSTNAME --static
7. Additional considerations..
There are several things to consider.
1.We need to make sure this script will run only one on first boot. To resolve this I added code to create a state file. Every time script runs it will check if state file exists, and if yes it will exit.
STATE='/opt/ovfset/state' if [ -e $STATE ] then date +"%m.%d.%Y %T " echo "$STATE file exists. Doing nothing." exit 1 else # WHOLE SCRIPT CONTENT IS EXECUTED AS PART OF else. date +"%m.%d.%Y %T " echo "This script will not be executed on next boot if $STATE file exists" echo "If you want to execute this configuration on Next boot remove $STATE file" date +"%m.%d.%Y %T " ; echo "Creating State file" date > /opt/ovfset/state fi
2. We need to log. So in the overall script I will be adding echo before each action and will place the log near the state file.
3. After all is done its not a bad idea to reboot, so I will add a reboot action to the end of the script.
4. Place the script in some permanent OS accessible directory. I chose to place it to /opt/ovfset.
8. Run script on boot
Again, as with other settings in Linux, there are many ways you can use to run things on boot. I chose the old fashioned way using rc.d.
But in CentOS 7 rc.d is not executed by default. to enable it you need to grant Execute rights to the rc.local script.
chmod u+x /etc/rc.d/rc.local
Then, just add a command you want to run on boot to the end of the rc.local file
bash /opt/ovfset/ovf_set.sh >> /opt/ovfset/run.log 2>&1
Description: Use bash, to run /opt/ovfset/ovf_set.sh script, redirect all output to /opt/ovfset/run.log.
9. Final touches
Before powering off your VM, make sure state file is deleted. You can also consider removing any signs of your activity on the VM, like command history, and SSH keys.
Connect VM to a generic virtual switch, like for example VM network.
Once ready, showdown the VM, and export it as and OVF or OVA.
10. Deploy OVF/OVA
This is how wizard will look during deployment.
11. Whole script
I know my script is not ideal, but I will attach it here in case anyone will want to take and improve it.
#!/bin/bash STATE='/opt/ovfset/state' if [ -e $STATE ] then date +"%m.%d.%Y %T " echo "$STATE file exists. Doing nothing." exit 1 else echo "+++++++++++++++++++++++++++++++++++++++++++" echo "++++++++++++ OVF Config script ++++++++++++" echo "+ System will be rebooted after execution +" echo "+++++++++++++++++++++++++++++++++++++++++++" # create XML file with settings date +"%m.%d.%Y %T " ; echo "Fetcing values" vmtoolsd --cmd "info-get guestinfo.ovfenv" > /tmp/ovf_env.xml TMPXML='/tmp/ovf_env.xml' # gathering values date +"%m.%d.%Y %T "; echo "Sorting..." IP=`cat $TMPXML| grep -e ip0 |sed -n -e '/value\=/ s/.*\=\" *//p'|sed 's/\"\/>//'` NETMASK=`cat $TMPXML| grep -e netmask0 |sed -n -e '/value\=/ s/.*\=\" *//p'|sed 's/\"\/>//'` GW=`cat $TMPXML| grep -e gateway |sed -n -e '/value\=/ s/.*\=\" *//p'|sed 's/\"\/>//'` HOSTNAME=`cat $TMPXML| grep -e hostname |sed -n -e '/value\=/ s/.*\=\" *//p'|sed 's/\"\/>//'` DNS0=`cat $TMPXML| grep -e dns0 |sed -n -e '/value\=/ s/.*\=\" *//p'|sed 's/\"\/>//'` DNS1=`cat $TMPXML| grep -e dns1 |sed -n -e '/value\=/ s/.*\=\" *//p'|sed 's/\"\/>//'` # NMCLI fetch existing netwrok interface device name date +"%m.%d.%Y %T "; echo "Gathering info about network interfaces..." IFACE=`nmcli dev|grep ethernet|awk '{print $1}'` # NMCLI fetch existing connection name. This will have to be recreated. CON=`nmcli con show|grep -v NAME| awk '{print $1}'` # NMCLI remove connection nmcli con delete $CON # Create new connection date +"%m.%d.%Y %T " ; echo "Setting Network settings...." # Check if IP and NETMASk variables exist and are not empty if [ -z ${IP+x} ] && [ -z ${NETMASK+x} ]; then date +"%m.%d.%Y %T " ; echo "No IP information found. Trying DHCP" # IF empty configure connection to use DHCP nmcli con add con-name "$IFACE" ifname "$IFACE" type ethernet else date +"%m.%d.%Y %T " ; echo "Setting..." # If variables exist, configure interface with IP and netmask and GW. Also set DNS settings in same step. nmcli con add con-name "$IFACE" ifname $IFACE type ethernet ip4 $IP/$NETMASK gw4 $GW && echo "IP set to $IP/$NETMASK. GW set to $GW" nmcli con mod "$IFACE" ipv4.dns "$DNS0,$DNS1" && echo "DNS set to $DNS0,$DNS1" fi # Set Hostname date +"%m.%d.%Y %T " ; echo "Setting Hostname..." hostnamectl set-hostname $HOSTNAME --static # Notification for future date +"%m.%d.%Y %T " echo "This script will not be executed on next boot if $STATE file exists" echo "If you want to execute this configuration on Next boot remove $STATE file" date +"%m.%d.%Y %T " ; echo "Creating State file" date > /opt/ovfset/state # Wait a bit and reboot sleep 5 reboot fi
12. Closing word
Not perfect I know, but works. Let me know if you have questions. always open for discussion.
Latest posts by Aram Avetisyan (see all)
- Make Youtube Videos About Technology? Why not… The Cross-Cloud Guy - October 7, 2021
- Automating (NSX-T) REST API using Ansible URI module - December 29, 2020
- Quick Reference: Create Security Policy with Firewall Rules using NSX-T Policy API - May 4, 2020
Your explanation was well written and just the thing I was looking for. I also appreciate your example script to use as a starting point. Thanks for making this guide, it worked for me using vCenter 6.5.
Hi Evan, Glad to hear it was helpful 🙂
I ended up needing to add “mv /opt/ovfset/run.log /opt/ovfset/lastrun.log” to the end of the script before the sleep line to get useful logging out of the script, else the state file notification would overwrite the log.
Assuming you are using rc.local as well to execute the script on boot(which is not an ideal method of course) redirecting output to log using “>>” instead of “>” should make sure file is not overwritten on reboot. So command would look like “bash /opt/ovfset/ovf_set.sh >> /opt/ovfset/run.log 2>&1”. Using your method is also an option though. Thanks for pointing out though, i will update the article.
Thank you for this effort on writing this post. However, I can’t seem to make it work for me as my template reboots permanently after using whole script in rc.local. Am I missing something here ?
Hey Falko,
Not sure what you mean by “using whole script in rc.local.”
There are several things to consider:
1. you should not include the whole script in rc.local, you should include pointer to the script so it is executed (e.g. bash /opt/ovfset/ovf_set.sh, covered in paragraph 8 if the article.)
2. The script includes a reboot command, if it reboots every time it means the script is not able to check is state file exists. make sure the path /opt/ovfset/ exists in your environment, and that the script is able to write to it (covered in paragraph 7 of the article)
3. rc.local is just one of the options to run scripts on boot in Linux, the method to use very much depends on the linux distribution and the version of distribution. So if rc.local does not work for you try some other method, like created a service from the script.
If you are still running into issues, we can try to troubleshoot, but for that we will need more info. For example you can try to login into single user mode and see whats happening during boot and why is it rebooting.
Hey Aram,
thank you for you answer, I just missed to place the script correctly! In the meantime I further developed that script to create templates with 2 or 3 network cards. Really thankful for your post as it helped me a lot !!
Always welcome!
This seems to be a working script except the networking. It is not pushing in the static information from the script to the network manager.
After the script, it still pulls dhcp. Any help would be appreciated.
Hi Matt, the script was written on CentOS 6 if i remember correctly. It is possible that the nmcli behaviour changes in recent versions so i would look in that direction. If you followed the article end to end you should have a run.log generated, take a look in that file see if there is any strange messaging like, cannot find interface or something..