backup vmware esxi with a rake (script)

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 ''

gem 'rbvmomi'

group :development, :test do
  gem 'rake'

  • 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:

  host: vsphere_host
  user: vsphere_api_user
  pwd: api_user_password
  datacenter: vsphere_datacenter
  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

  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"])

  def backup_vm(vm_path, vm_snapshot='', vm_datastore='')
    vm_name = vm_path.split('/')[-1]
    timestamp ='%y%m%d%H%M%S')
    vm = @data_center.find_vm("#{vm_path}")
    backup_folder = @data_center.vmFolder.children.find{|folder|["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
  def find_snapshot(vm, snapshot_name, parent_snapshot)
    found_snapshot = nil
    if parent_snapshot.nil?
      snapshots = vm.snapshot.rootSnapshotList
      snapshots = parent_snapshot.childSnapshotList
    snapshots.each do |snapshot|
        found_snapshot = snapshot.snapshot
        found_snapshot = find_snapshot(vm, snapshot_name, snapshot) unless snapshot.childSnapshotList.size==0
    return found_snapshot

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 =
    api.backup_vm args[:vm_name], args[:vm_snapshot], args[:vm_datastore]
    puts "vm: #{label} - backup completed!"

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


Cover image by Alex