I have spent the last few days trying to find a free backup solution to the newly free ESXi for windows only enviroments (in particular Windows XP). The solution for me was the following:
1. Installing Windows Services for UNIX (WSFU)
2. Copying the ESXi Server password and group files to Windows
3. Configuring WSFU for accepting ESX Server connections
4. Sharing the Windows folder for NFS compatibility
5. Configuring the ESXi Server to mount the Window NFS Share as Datastore.
6. Setup Backup Script
Attached is the complete steps.
I take NO credit for any of this. This is just a complation of others work formated to suit my needs and felt others could benift from it as I have.
by Jason Mattox from Vizioncore (direct copy of his work, I just added more information to make it work in Windows XP)
oem.tgz along with the other tgz files are extracted during the initial Loading VMware Hypervisor screen so it'l long before crond is started.
The script has now been updated to allow the names of the Virtual Machines to contain spaces:
http://communities.vmware.com/docs/DOC-8760
Hopefully you'll start to use dashes and underscores in the future and refrain from adding spaces into filenames/directories.
I think in the new script this line:
for VM_NAME in `cat ${VM_INPUT} | sed '/^$/d' | sed -e 's/^[ \t]*//;s/[ \t]*$//'`;
Should be:
for VM_NAME in `cat ${VM_INPUT} | sed '/^$/d' | sed -e 's/^[:blank:]*//;s/[:blank:]*$//'`;
Else you are going to just remove any "t","\" or spaces at beginning or end.
While at that, maybe this lines:
VMX_DIR=`echo ${VMX_CONF%.vmx}` VMX_DIR=`echo ${VMX_DIR%/*}`
Could be combined to:
VMX_DIR=`echo ${VMX_CONF%/*.vmx}`
At least in my tests it produced the same output that way ...
Then all
grep -E "${VM_NAME}" /tmp/vms_list | ...
should be changed to:
grep -E \"${VM_NAME}\" /tmp/vms_list | ...
So that the " is part of the search pattern. Else if input file contains 'test', it would also match for VMs that are called 'test1' or 'test vm' or anything else containing that word. Now that we have the " in the vms_list we should take benefit from it
I think it should be enough to do a
mkdir /etc/rc.early.d
and then create a script file in it:
echo 'echo "01 05 * * 1-5 /opt/Backup/ghettoVCBni.sh /opt/Backup/VMs2Backup" >>/var/spool/cron/crontabs/root' > /etc/rc.early.d/AddBackup2Crontab.sh chown root:root /etc/rc.early.d/AddBackup2Crontab.sh chmod 774 /etc/rc.early.d/AddBackup2Crontab.sh
I still need to test this, but I would think this should work.
-JoeSt
Hi William
Is there any special reason why there needs to be an input file that contains the display names of the VM's you want to back up? I tried your first version of the script and I'm sure it didn't do this (could have dreamt it however the new one seems to require this, is it becuase of the spaces in display names fix?
Good work on the script though...
Hello,
I'm wondering how difficult it would be to add a line to the script that copies week old backups to a different location prior to deleting them? Say I want to keep 7 backups and take one of those off-site per week and automate it all. What I could see doing is have the script copy the file that is 7 days old to an external hard drive and then delete it.
Thanks,
Ryan
lamw,
So adding a crontab to oem.tgz works like a charm. Didn't have to start crond like Dave mentioned, and the backups ran. However, when the jobs ran last night, I got a message for one of my VMs:
"Cannot open the disk '<location of disk on my host>.vmdk' or one of the snapshot disks it depends on. Reason: Device or resource busy."
Then,
"Failed to reopen disk '(null)'"
Then,
"Error encountered while restarting virtual machine after taking snapshot. The virtual machine will be powered off."
The VM just stayed off and we have to power it back on manually. Do you have any idea why that happened?
Should we be able to mount these vmdk files with the VMWare DiskMount utility? I'm trying to figure out if I can eliminate file level restores by just mounting the disk files and pulling data rather than turning the machine on through another host or something similar. I get an error using the VMWare utility.
kpc,
Both version 1 and 2 of the script has always worked in that fashion. You would feed in a list of the Virtual Machines you would like backed up and they're queried and backed up. How else would you do it
The reason for the display name, which I think most user's have both the display name and the actual name of their directory/etc all match up, is that VMware allows you to have a different display name than the actual directory and possibly some of the other attributes. The queries that are return by using either "vmware-cmd -l" or "vmware-vim-cmd vmsvc/getallvms" show only display name
Hopefully this answered your question.
It would not be too hard, but you'll have to modify this for your use case. You can set the rotation variable to go back at least 7 iterations and instead of "rm -rf" that backup, you could specify a new variable at the top and have it copy that off to say NFS datastore #2 and that can be used to be shipped offsite, then purge from that current backup datastore so you don't keep a duplicated copy unless that is you goal.
good luck
great to hear.
Did you use v1 or v2 of the script? Usually when it says it can't open the disk, the snapshot has not been taking and there is till a VMFS lock on the master VMDK you're trying to access. You'll usually get promoted with device or resource busy. Not sure about the second set of messages and what was done afterwords. Have you tried to run a backup on this VM before? Perhaps try to run a manual backup or run the script only on this VM and see if you run into any odd results.
lamw, great script. I wrote a short article to refer people to the solution you created:
http://www.yellow-bricks.com/2008/11/26/backup-your-esxi-vms/
Duncan
Blogging: http://www.yellow-bricks.com
If you find this information useful, please award points for "correct" or "helpful".
Hi JoSte,
Thanks for your feedback,
I've uploaded a small test script to answer some of your questions regarding section 1 (disagree, you'll see from sample what I mean)
and
section 2 (agree that the test script works, but for some reason when enabling the devel mode to print out the variables, there is discrepancy and I need to do more testing before making that change). I try to think of all the possible use cases where an end user could be providing bad input, say "spaces" in their names and try to take into account. The sed command that I had setup works as expected.
I agree with your section 3, I must have missed that and yes it would not do an exact match. I've modified the code but have not updated the doc, I'll do that later today.
Let me know if you have any questions regarding the "test.sh"
Thank's Duncan, very honored.
Love your blog by the way.
I think in the new script this line:
> for VM_NAME in `cat ${VM_INPUT} | sed '/^$/d' | sed -e 's/^[ \t]*//;s/[ \t]*$//'`; >Should be:
> for VM_NAME in `cat ${VM_INPUT} | sed '/^$/d' | sed -e 's/^[:blank:]*//;s/[:blank:]*$//'`; >Else you are going to just remove any "t","\" or spaces at beginning or end.
"\t" is the tab character, so it'd remove either spaces or tabs. Also, your sed command wouldn't work; you'd need to recode it as:
for VM_NAME in `cat "${VM_INPUT}" | sed '/^$/d' | sed -e 's/^[[:blank:]]*//;s/[[:blank:]]*$//'`
Or:
cat "${VM_INPUT}" | sed '/^$/d' | sed -e 's/^[[:blank:]]*//;s/[[:blank:]]*$//' | while read VM_NAME;
Note the quotes around $; without them, any spaces in the variable would break the script (the shell would see them as argument separators).
While at that, maybe this lines:
> VMX_DIR=`echo ${VMX_CONF%.vmx}` > VMX_DIR=`echo ${VMX_DIR%/*}` >Could be combined to:
> VMX_DIR=`echo ${VMX_CONF%/*.vmx}` >At least in my tests it produced the same output that way ...
'dirname' was intended for that specific purpose:
VMX_DIR=`dirname "${VMX_CONF}"`
The quotes ensure that spaces in the path are passed to the command correctly.
Then all
> grep -E "${VM_NAME}" /tmp/vms_list | ... >should be changed to:
> grep -E \"${VM_NAME}\" /tmp/vms_list | ... >So that the " is part of the search pattern. Else if input file contains 'test', it would also match for VMs that are called 'test1' or 'test vm' or anything else containing that word. Now that we have the " in the vms_list we should take benefit from it
Preferably you'd use word boundaries in the regular expression so that you're not forced to have quotes in the VM names:
grep -E "\<${VM_NAME}\>" /tmp/vms_list | ...
That way, if $VM_NAME == 'test', it won't match 'tests', 'test1', 'newtest', etc.
@lamw,
I have tested with your demo script. This are my findings:
# cat > /tmp/bad_input_file << __BAD_INPUT__ Monitoring & Maintenance RESNET-UCSB blah2 blah1 blah2 t tvm tt __BAD_INPUT__ ~ # IFS=$'\n' ~ # for VM_NAME in `cat /tmp/bad_input_file | sed '/^$/d' | sed -e 's/^[ \t]*//;s/[ \t]*$//'`; do echo "#$VM_NAME#" done #Monitoring & Maintenance# #RESNET-UCSB# #blah2# #blah1# #blah2# #vm# ~ #
So the sed on my ESXi does not interpret \t as tab. It just removes the t...
However this corrected version from aremmes works as I would expect it.
# for VM_NAME in `cat /tmp/bad_input_file | sed '/^$/d' | sed -e 's/^[[:blank:]]*//;s/[[:blank:]]*$//'` do echo "#$VM_NAME#" done #Monitoring & Maintenance# #RESNET-UCSB# #blah2# #blah1# #blah2# #t# #tvm# #tt# ~ #
Preferably you'd use word boundaries in the regular expression so that you're not forced to have quotes in the VM names:
grep -E "\<${VM_NAME}\>" /tmp/vms_list | ...
That way, if $VM_NAME == 'test', it won't match 'tests', 'test1', 'newtest', etc.
I am by no means an expert in this, but what if there is a VM_NAME == 'test vm' and 'test'. Wouldn't that 'test' also match the 'test vm', then?
~ # grep -E "\<test\>" /tmp/vms_list "16";"test vm";"[datastore-1]";"New Virtual Machine/New Virtual Machine.vmx" ~ # grep -E "\<test vm\>" /tmp/vms_list "16";"test vm";"[datastore-1]";"New Virtual Machine/New Virtual Machine.vmx" ~ #
How about this:
grep -E "\"${VM_NAME}\"" /tmp/vms_list | ...
It should work also with spaces in $VM_NAME.
~ # grep -E "\"test vm\"" /tmp/vms_list "16";"test vm";"[datastore-1]";"New Virtual Machine/New Virtual Machine.vmx" ~ # grep -E "\"test\"" /tmp/vms_list ~ #
~ # VMX_CONF=/vmfs/volumes/himalaya-local-SAS.VMStorage/RESNET-UCSB/RESNET-UCSB.vmx ~ # VMX_DIR=`echo ${VMX_CONF%.vmx}` ~ # VMX_DIR=`echo ${VMX_DIR%/*}` ~ # echo "Output: $VMX_DIR" Output: /vmfs/volumes/himalaya-local-SAS.VMStorage/RESNET-UCSB ~ # VMX_DIR="" ~ # VMX_DIR=`echo ${VMX_CONF%/*.vmx}` ~ # echo "Output: $VMX_DIR" Output: /vmfs/volumes/himalaya-local-SAS.VMStorage/RESNET-UCSB ~ #
So on my ESXi it is doing the same in both versions. However the suggested
VMX_DIR=`dirname "${VMX_CONF}"`
is anyway the better idea.
I am by no means an expert in this, but what if there is a VM_NAME == 'test vm' and 'test'. Wouldn't that 'test' also match the 'test vm', then?
> ~ # grep -E "\<test\>" /tmp/vms_list > "16";"test vm";"[datastore-1]";"New Virtual Machine/New Virtual Machine.vmx" > ~ # grep -E "\<test vm\>" /tmp/vms_list > "16";"test vm";"[datastore-1]";"New Virtual Machine/New Virtual Machine.vmx" > ~ # >How about this:
> grep -E "\"${VM_NAME}\"" /tmp/vms_list | ... >It should work also with spaces in $VM_NAME.
> ~ # grep -E "\"test vm\"" /tmp/vms_list > "16";"test vm";"[datastore-1]";"New Virtual Machine/New Virtual Machine.vmx" > ~ # grep -E "\"test\"" /tmp/vms_list > ~ # >
You're quite correct. Seeing the file format gives me an idea, though...
#!/bin/bash # Backup the current IFS OLD_IFS="${IFS}" IFS=";" # Name of VM we're looking for MY_VMNAME="vm" # Walk through stdin, stop at first match while read VM_ID VM_NAME VM_DS VM_VMX; do test "${MY_VMNAME}" = "${VM_NAME}" && break done << __VMS_LIST__ 1;vm01;[datastore-1];New Virtual Machine #1/New Virtual Machine #1.vmx 2;vm02;[datastore-1];New Virtual Machine #2/New Virtual Machine #2.vmx 3;vm03;[datastore-1];New Virtual Machine #3/New Virtual Machine #3.vmx 4;vm04;[datastore-1];New Virtual Machine #4/New Virtual Machine #4.vmx 5;random vm name;[datastore-1];random vm name/random vm name.vmx 6;vm;[datastore-1];vm/vm.vmx 7;vm05;[datastore-1];New Virtual Machine #5/New Virtual Machine #5.vmx 8;vm06;[datastore-1];New Virtual Machine #6/New Virtual Machine #6.vmx __VMS_LIST__ # Print the values of the last record read echo ${VM_ID} ${VM_NAME} ${VM_DS} ${VM_VMX} # Restore IFS IFS="${OLD_IFS}"
This assumes that the lines in the input is unique, but they don't have to be sorted. Running this gives:
~ # ./testvmlist.sh 6 vm [datastore-1] vm/vm.vmx ~ #
Message was edited by: aremmes -- some code was getting munched
Hi JoSte and aremmes,
Thanks for your guys feedback and suggestions! Definitely great ideas and I guess we've learned that there's more than one way to accomplish a task. I've been pretty busy this morning but I'll be taking some of that feedback and update the script. I'll need to run further tests to ensure it does not cause any side affects to the current functionality. I'll post it up once it's ready to go
I have tried to change the script with aremmes solution:
#dump out all virtual machines allowing for spaces now ${VMWARE_CMD} vmsvc/getallvms | sed 's/[[:blank:]]\{3,\}/ /g' | awk -F' ' '{print $1";"$2";"$3}' | sed 's/\] /\];/g' | sed '1,1d' > /tmp/vms_list IFS=$'\n' for MY_VMNAME in `cat ${VM_INPUT} | sed '/^$/d' | sed -e 's/^[[:blank:]]*//;s/[[:blank:]]*$//'`; do OLD_IFS="${IFS}" IFS=";" while read VM_ID VM_NAME VM_DS VMX_CONF; do test "${MY_VMNAME}" = "${VM_NAME}" && break done </tmp/vms_list # Restore IFS IFS="${OLD_IFS}" VMFS_VOLUME=`echo "${VM_DS}" | sed 's/\[//;s/\]//g'` # VM_ID=`grep "${VM_NAME}" /tmp/vms_list | awk -F ";" '{print $1}' | sed 's/"//g'` #ensure default value if one is not selected or variable is null if [ -z ${VM_BACKUP_DIR_NAMING_CONVENTION} ]; then VM_BACKUP_DIR_NAMING_CONVENTION="$(date +%F)" fi #esx if ; then #VMFS_VOLUME=`grep \"${VM_NAME}\" /tmp/vms_list | awk -F ";" '{print $3}' | sed 's/\[//;s/\]//;s/"//g'` #VMX_CONF=`grep \"${VM_NAME}\" /tmp/vms_list | awk -F ";" '{print $4}' | sed 's/"//g'` VMX_PATH="/vmfs/volumes/${VMFS_VOLUME}/${VMX_CONF}" VMX_DIR=`dirname "${VMX_PATH}"` #VMX_DIR=`echo ${VMX_DIR%/*}` fi #esxi if ; then #VMFS_VOLUME=`grep -E \"${VM_NAME}\" /tmp/vms_list | awk -F ";" '{print $3}' | sed 's/\[//;s/\]//;s/"//g'` #VMX_CONF=`grep -E \"${VM_NAME}\" /tmp/vms_list | awk -F ";" '{print $4}' | sed 's/\[//;s/\]//;s/"//g'` VMX_DIR=`dirname "${VMX_CONF}"` #VMX_DIR=`echo ${VMX_DIR%/*}` VMX_DIR=/vmfs/volumes/${VMFS_VOLUME}/${VMX_DIR} VMX_PATH=/vmfs/volumes/${VMFS_VOLUME}/${VMX_CONF} fi #checks to see if we can pull out the VM_ID if [ -z ${VM_ID} ]; then echo "Error: failed to extract VM_ID for ${MY_VMNAME}!"
It looks promising so far.
Error: failed to extract VM_ID for test! ########################################## Type: light Virtual Machine: test vm VM_ID: 16 VMX_PATH: /vmfs/volumes/datastore-1/New Virtual Machine/New Virtual Machine.vmx VMX_DIR: /vmfs/volumes/datastore-1/New Virtual Machine VMX_CONF: New Virtual Machine/New Virtual Machine.vmx VMFS_VOLUME: datastore-1 ########################################## Error: failed to extract VM_ID for test1! Error: failed to extract VM_ID for tesat2! Error: failed to extract VM_ID for test3 und4 5! Start time: Wed Nov 26 22:08:19 UTC 2008 End time: Wed Nov 26 22:08:19 UTC 2008 Duration : 0 Seconds Completed backing up specified Virtual Machines!
This is the input file:
test
test vm
test1
tesat2
test3 und4 5
It has some garbage in it by purpose...
Once the now unneeded code would be removed, the script should look nicer. Overall I think aremmes had a very nice idea here. It may be worth going that direction. It is less complicated.
Taking a look at the code and with the suggestions, we can actually remove some unnecessary code, since we rely on the "VMware vimsh" to pull out information. We don't have to break up the code on parsing the Virtual Machine information whether the script is running on an ESX w/SC or ESXi because the information can be pulled the same way.
Here is the code that'll get the modification:
${VMWARE_CMD} vmsvc/getallvms | sed 's/[[:blank:]]\{3,\}/ /g' | awk -F' ' '{print "\""$1"\";\""$2"\";\""$3"\""}' | sed 's/\] /\]\";\"/g' | sed '1,1d' > /tmp/vms_list IFS=$'\n' for VM_NAME in `cat "${VM_INPUT}" | sed '/^$/d' | sed -e 's/^[[:blank:]]*//;s/[[:blank:]]*$//'`; do VM_ID=`grep -E "\"${VM_NAME}\"" /tmp/vms_list | awk -F ";" '{print $1}' | sed 's/"//g'` #ensure default value if one is not selected or variable is null if [ -z ${VM_BACKUP_DIR_NAMING_CONVENTION} ]; then VM_BACKUP_DIR_NAMING_CONVENTION="$(date +%F)" fi VMFS_VOLUME=`grep -E "\"${VM_NAME}\"" /tmp/vms_list | awk -F ";" '{print $3}' | sed 's/\[//;s/\]//;s/"//g'` VMX_CONF=`grep -E "\"${VM_NAME}\"" /tmp/vms_list | awk -F ";" '{print $4}' | sed 's/\[//;s/\]//;s/"//g'` VMX_PATH="/vmfs/volumes/${VMFS_VOLUME}/${VMX_CONF}" VMX_DIR=`dirname "${VMX_PATH}"`
I will not have time to finalize testing but I'll do so after coming back from the holidays.
I'll be out of town and I hope everyone has a safe and happy holiday!
Sorry, double post...