backup vmware esxi with a rake (script)
Recently we built a small VMware ESXi cluster (3 nodes) for testing and developing purpose. After playing with it for a while (and spinning up a few dozen vms), we started looking around for a cheap and simple backup solution.
step 0 - install the tools
- install ruby
- install bundler
gem install bundler
- create the project folder
mkdir esxi_backup
- create a file named
Gemfile
into the project folder with the following content:
source 'https://rubygems.org'
gem 'rbvmomi'
group :development, :test do
gem 'rake'
end
- install all the dependencies
cd esxi_backup && bundle install
- create the project directories
mkdir config && mkdir lib && mkdir scripts
step 1 - build a small automation lib
vSphere Hypervisor it's fully (and easy) "scriptable" through its comprehensive vsphere api. Unfortunately the api isn't available for the single server free edition, so you need a fully licensed server even for testing it.
As always, if there is an api or library, there is some sort of ruby wrapper to make life easier... this time let me thank RbVmomi for his contribution to the ruby fellowship.
To keep things more clean (and "upgradable"), we will write a simple configuration file in config/vcenter.yml
:
connection:
host: vsphere_host
user: vsphere_api_user
pwd: api_user_password
datacenter: vsphere_datacenter
backup:
datastore: vsphere_datastore
folder: backup_folder_inside_datastore
n.b. the file above stores sensitive informations. In a secure production environment it's better not to leave them around (even worse if written in files...), but that's another post...
A small helper class in lib/vcenter.rb
:
# coding: utf-8
require 'rbvmomi'
class VCenter < Object
attr_accessor :config, :vim, :data_center
public
def init()
@config = YAML.load_file("config/vcenter.yml")
@vim = RbVmomi::VIM.connect host: @config["connection"]["host"], user: @config["connection"]["user"], password: @config["connection"]["pwd"], insecure: true
@data_center = vim.serviceInstance.find_datacenter(@config["connection"]["datacenter"])
end
def backup_vm(vm_path, vm_snapshot='', vm_datastore='')
vm_name = vm_path.split('/')[-1]
timestamp = Time.now.utc.strftime('%y%m%d%H%M%S')
vm = @data_center.find_vm("#{vm_path}")
backup_folder = @data_center.vmFolder.children.find{|folder| folder.name==@config["backup"]["folder"]}
iscsi_datastore = @data_center.find_datastore((vm_datastore.nil? || vm_datastore=='') ? @config["backup"]["datastore"] : vm_datastore)
vm_relocate_spec = { datastore: iscsi_datastore, diskMoveType: :moveAllDiskBackingsAndDisallowSharing, transform: :sparse}
vm_clone_spec = { location: vm_relocate_spec, powerOn: false, template: false }
vm_clone_spec[:snapshot] = find_snapshot(vm, vm_snapshot, nil) unless (vm_snapshot.nil? || vm_snapshot=='')
vm.CloneVM_Task(:folder => backup_folder, :name => "#{vm_name}#{(('_' + vm_snapshot) unless (vm_snapshot.nil? || vm_snapshot=='')).to_s}_#{timestamp}", :spec => vm_clone_spec).wait_for_completion
end
def find_snapshot(vm, snapshot_name, parent_snapshot)
found_snapshot = nil
if parent_snapshot.nil?
snapshots = vm.snapshot.rootSnapshotList
else
snapshots = parent_snapshot.childSnapshotList
end
snapshots.each do |snapshot|
if snapshot.name==snapshot_name
found_snapshot = snapshot.snapshot
else
found_snapshot = find_snapshot(vm, snapshot_name, snapshot) unless snapshot.childSnapshotList.size==0
end
end
return found_snapshot
end
end
and finally a rake file in lib/task/setup.rake
require './lib/vcenter'
include RakeHelper
namespace :vcenter do
desc 'backup vm'
task :backup_vm, [:vm_name,:vm_snapshot,:vm_datastore] do |t, args|
abort if args[:vm_name].nil?
label = "#{args[:vm_name]}#{((' [' + args[:vm_snapshot] + ']') unless (args[:vm_snapshot].nil? || args[:vm_snapshot]=='')).to_s}#{((' [' + args[:vm_datastore] + ']') unless (args[:vm_datastore].nil? || args[:vm_datastore]=='')).to_s}"
puts "vm: #{label} - starting backup..."
api = VCenter.new
api.init
api.backup_vm args[:vm_name], args[:vm_snapshot], args[:vm_datastore]
puts "vm: #{label} - backup completed!"
end
end
With all this in place you can open a shell and
cd esxi_backup
bundle exec rake vcenter:backup_vm[VM_NAME]
if your VM it's inside a folder
bundle exec rake vcenter:backup_vm[FOLDER/VM_NAME]
if you want to backup a specific VM snapshot
bundle exec rake vcenter:backup_vm[VM_NAME,SNAPSHOT_NAME]
if any of the above parameters has spaces or special shell characters, just wrap the whole rake command inside single/double quotes (depending on your OS)
bundle exec "rake vcenter:backup_vm[FOLDER/VM_NAME,SNAPSHOT_NAME]"
(as you can guess I'm a great rake fan. I think it's a very useful wrapper for shell scripting...)
post scriptum
All the files above are available in github
notes
Cover image by Alex