VMware Cloud Community
MarcBouchard
Enthusiast
Enthusiast
Jump to solution

Setting up a pre-production environment...

My current employer has a pre-production environment that is being refreshed nightly from production. I started looking at the way it's being done (powerCLI script) and I'm trying to make things better.

Here's how it works.

A snapshot is taken of the production servers, thereby "freezing" the base vmdk disk. A new VM is created for the pre-production and it uses the "frozen" base disk of the production server as it's own disk, in non-persistent mode. The VM is of course connected to an isolated port group. This basically allows the dev/infra team to have an identical copy of the production servers at any time (just run the refresh script).

While this works very well, I have a concern about using production disks for this purpose. I usually prefer to leave production alone and have either full clones or fully provisioned pre-prod environment.

Let's assume I'm on board with going on like this and I want to automate the creation of these pre-prod shells. Right now they are built manually for the new servers, then the refresh script takes over.

Refresh script:

A) Shutdown pre-prod server

B) Remove snapshot on production server (committing latest changes to base vmdk)

C) Create new snapshot on production server (freezing the base vmdk)

D) Power up pre-prod server (with committed changes)


This script is scheduled to run every evening to start the next day with a fresh environment. Another benefit of using this non persistent approach is that a simple reboot brings the pre-prod back to a clean version (if the tests didn't go well).

Questions:

Is there a way to clone a VM without the disks? This would make the creation of new pre-prod servers very easy. Clone, attach disks, change port group. Done.

Is there a better way to achieve this kind of result without doing this kind of "fake linked-clones"?

0 Kudos
1 Solution

Accepted Solutions
LucD
Leadership
Leadership
Jump to solution

The following seems to work for me. Some remarks:

  • The VM is cloned without any of it's VMDK
  • The new VM is created on the same ESXi node and datastore as the source VM (since you didn't seem to have any specific requirements in that aspect)
  • This script does not work for a VM with RDM disks

$vmName = 'sourceVM'

$vmClone = 'cloneVM'

$folderName = 'MyBlueFolder'

$vm = Get-VM -Name $vmName

$folder = Get-Folder -Name $folderName

$spec = New-Object -TypeName VMware.Vim.VirtualMachineCloneSpec

$spec.Location = New-Object -TypeName VMware.Vim.VirtualMachineRelocateSpec

$spec.Location.Datastore = $vm.ExtensionData.Datastore[0]

$spec.Location.Host = $vm.ExtensionData.Runtime.Host

$spec.Config = New-Object -TypeName VMware.Vim.VirtualMachineConfigSpec

Get-HardDisk -VM $vm | %{

  $dev = New-Object -TypeName VMware.Vim.VirtualDeviceConfigSpec

  $dev.Device = $_.ExtensionData

  $dev.Operation = [VMware.Vim.VirtualDeviceConfigSpecOperation]::remove

  $dev.FileOperation = [VMware.Vim.VirtualDeviceConfigSpecFileOperation]::destroy

  $Spec.Config.DeviceChange += $dev

}

$vm.ExtensionData.CloneVM_Task($folder.ExtensionData.MoRef,$vmClone,$spec)


Blog: lucd.info  Twitter: @LucD22  Co-author PowerCLI Reference

View solution in original post

0 Kudos
19 Replies
Bartmosss
Enthusiast
Enthusiast
Jump to solution

Just some quick idea...

As I understand cloning a VM is only copying the .VMX file, into another folder with another name then copy the VMDK then change the file path for all .vmdk file in the new VMX.

So with a bit of scripting, you could script your "cloning" operation but skip the copy of the VMDK file.

Change the UUID of the VM then register the VM.

MCP, A+, Network+, Linux+, VSP, VTSP, VCP
0 Kudos
MarcBouchard
Enthusiast
Enthusiast
Jump to solution

I feel like it's going to be the way to do it, but I was (and still am) trying to find a better alternative, like maybe read the config of SourceVM and create a new VM from scratch based on what you read?

0 Kudos
Bartmosss
Enthusiast
Enthusiast
Jump to solution

I am not a Powercli guru, but you could surely extract the config from the existing VM and re-create one based on that.

Look at this blog post

Create multiple VMs using PowerCLI » VCDX56

I would base my script on his. I would skip the loop part and replace all hardcoded value by extracted value from the source VM.

I think it would be "cleaner" to do this with a Powercli script than with a shell script.  I don't see a "cleanest" way to do that!

Keep me in the loop for the final solution, as I could recycle the solution.

MCP, A+, Network+, Linux+, VSP, VTSP, VCP
0 Kudos
MarcBouchard
Enthusiast
Enthusiast
Jump to solution

The main issue is I need to have the "clone" image identical, otherwise when you boot it will detect a new NIC (losing IP) for example. Also, need to cover every possible aspect of the config of the VM. Multiples disks, nics, etc... Easiest would be a "clone without disks"...

Still searching... Smiley Happy

0 Kudos
Bartmosss
Enthusiast
Enthusiast
Jump to solution

Well, "cloning without disk" does not seem to be a very wanted solution Smiley Happy As I did search briefly too. Scripting is the only way to go, either Powercli or shell. Choose your best weapon Smiley Happy Either way, I will be a bit messy.  Powercli could be a bit more elegant.

If you are using Veeam Backup, there is the possibility to use the Virtual Lab option.

MCP, A+, Network+, Linux+, VSP, VTSP, VCP
0 Kudos
LucD
Leadership
Leadership
Jump to solution

Sorry to bump into this thread, but I think you can do this with the CloneVM_Task method.

In the VirtualMachineCloneSpec parameter, you can use config.devicechange to remove the disks.

Now that is the theory, I haven't tested this yet :smileygrin:

If you want I can give you a sample script


Blog: lucd.info  Twitter: @LucD22  Co-author PowerCLI Reference

0 Kudos
MarcBouchard
Enthusiast
Enthusiast
Jump to solution

Feel free to bump in anytime Smiley Happy

I'll look into it right away. Thanks for the lead!

EDIT: Hmmm... I thought it was a PowerCLI command lol... Yeah if you have an example on how to use the API, it would be helpful. Not that I want to be spoon fed but I'm getting comfortable with using standard PS commands but nowhere near as advanced as some of you are Smiley Happy

0 Kudos
LucD
Leadership
Leadership
Jump to solution

No cmdlet I'm afraid.

I'll cook something up Smiley Happy


Blog: lucd.info  Twitter: @LucD22  Co-author PowerCLI Reference

0 Kudos
MarcBouchard
Enthusiast
Enthusiast
Jump to solution

Thanks a lot...

Working off of this piece of code right now to see if I can figure it out.

http://www.jonathanmedd.net/2013/07/clone-a-vm-from-a-snapshot-using-powercli.html

0 Kudos
MarcBouchard
Enthusiast
Enthusiast
Jump to solution

Ok, so I've got this running, the clone is created. From my readings, this "createNewChildDiskBacking" should have created the VM while linked to the source disks like I want but it's not doing so.

function New-VMClone {

[CmdletBinding(DefaultParameterSetName=”Current Snapshot”)][OutputType('VMware.Vim.ManagedObjectReference')]

Param

  (

  [parameter(Mandatory=$true)]

  [ValidateNotNullOrEmpty()]

  [PSObject]$SourceVM,

  [parameter(Mandatory=$true)]

  [ValidateNotNullOrEmpty()]

  [String]$CloneName,

  [parameter(Mandatory=$false)]

  [ValidateNotNullOrEmpty()]

  [String]$VMFolder

  )

########################################################################

try {

  if ($SourceVM.GetType().Name -eq "string"){

  try {

  $SourceVM = Get-VM $SourceVM -ErrorAction Stop

  }

  catch [Exception]{

  Write-Warning "VM $SourceVM does not exist"

  }

  }

  elseif ($SourceVM -isnot [VMware.VimAutomation.ViCore.Impl.V1.Inventory.VirtualMachineImpl]){

  Write-Warning "You did not pass a string or a VM object"

  Return

  }

  ########################################################################

  # --- Get source VM information

  ########################################################################

  # https://www.vmware.com/support/developer/vc-sdk/visdk400pubs/ReferenceGuide/vim.vm.RelocateSpec.Disk...

  $CloneType = "createNewChildDiskBacking"

  if ($PSBoundParameters.ContainsKey('VMFolder')){

  try {

  $Folder = Get-Folder $VMFolder -Type VM -ErrorAction Stop

  $CloneFolder = $Folder.ExtensionData.MoRef

  }

  catch [Exception] {

  Write-Warning "VM Folder $VMFolder does not exist, using existing folder instead"

  $CloneFolder = $SourceVM.ExtensionData.Parent

  }

  }

  else {

  $CloneFolder = $SourceVM.ExtensionData.Parent

  }

  ########################################################################

  # --- Create CloneSpec and initiate Clone Task

  ########################################################################

  $CloneSpec = New-Object Vmware.Vim.VirtualMachineCloneSpec

  $CloneSpec.Snapshot = $SourceVM.ExtensionData.Snapshot.CurrentSnapshot

  $CloneSpec.Location = New-Object Vmware.Vim.VirtualMachineRelocateSpec

  $CloneSpec.Location.DiskMoveType = [Vmware.Vim.VirtualMachineRelocateDiskMoveOptions]::$CloneType

  #$CloneSpec.Config.RemoveDisk

  ########################################################################

  # --- Create clone

  ########################################################################

  $SourceVM.ExtensionData.CloneVM_Task($CloneFolder, $CloneName, $CloneSpec)

}

catch [Exception]{

  throw "Unable to deploy new VM from source"

  }

}

0 Kudos
LucD
Leadership
Leadership
Jump to solution

The following seems to work for me. Some remarks:

  • The VM is cloned without any of it's VMDK
  • The new VM is created on the same ESXi node and datastore as the source VM (since you didn't seem to have any specific requirements in that aspect)
  • This script does not work for a VM with RDM disks

$vmName = 'sourceVM'

$vmClone = 'cloneVM'

$folderName = 'MyBlueFolder'

$vm = Get-VM -Name $vmName

$folder = Get-Folder -Name $folderName

$spec = New-Object -TypeName VMware.Vim.VirtualMachineCloneSpec

$spec.Location = New-Object -TypeName VMware.Vim.VirtualMachineRelocateSpec

$spec.Location.Datastore = $vm.ExtensionData.Datastore[0]

$spec.Location.Host = $vm.ExtensionData.Runtime.Host

$spec.Config = New-Object -TypeName VMware.Vim.VirtualMachineConfigSpec

Get-HardDisk -VM $vm | %{

  $dev = New-Object -TypeName VMware.Vim.VirtualDeviceConfigSpec

  $dev.Device = $_.ExtensionData

  $dev.Operation = [VMware.Vim.VirtualDeviceConfigSpecOperation]::remove

  $dev.FileOperation = [VMware.Vim.VirtualDeviceConfigSpecFileOperation]::destroy

  $Spec.Config.DeviceChange += $dev

}

$vm.ExtensionData.CloneVM_Task($folder.ExtensionData.MoRef,$vmClone,$spec)


Blog: lucd.info  Twitter: @LucD22  Co-author PowerCLI Reference

0 Kudos
MarcBouchard
Enthusiast
Enthusiast
Jump to solution

LucD wrote:

The following seems to work for me. Some remarks:

  • The VM is cloned without any of it's VMDK
  • The new VM is created on the same ESXi node and datastore as the source VM (since you didn't seem to have any specific requirements in that aspect)
  • This script does not work for a VM with RDM disks

This seems to meet my requirements. Thank you very much!

Trying to understand the code however and it's a bit fuzzy.

$spec = New-Object -TypeName VMware.Vim.VirtualMachineCloneSpec

$spec.Location = New-Object -TypeName VMware.Vim.VirtualMachineRelocateSpec

$spec.Location.Datastore = $vm.ExtensionData.Datastore[0]

$spec.Location.Host = $vm.ExtensionData.Runtime.Host

$spec.Config = New-Object -TypeName VMware.Vim.VirtualMachineConfigSpec

This is what I had in my other script, basically preparing the foundation for the cloning process (specify datastore, which host to run on, etc...

Get-HardDisk -VM $vm | %{

  $dev = New-Object -TypeName VMware.Vim.VirtualDeviceConfigSpec

  $dev.Device = $_.ExtensionData

  $dev.Operation = [VMware.Vim.VirtualDeviceConfigSpecOperation]::remove

  $dev.FileOperation = [VMware.Vim.VirtualDeviceConfigSpecFileOperation]::destroy

  $Spec.Config.DeviceChange += $dev

}

$vm.ExtensionData.CloneVM_Task($folder.ExtensionData.MoRef,$vmClone,$spec)

This part gets the source VM's disk configuration and pipelines it into the block code that removes it from the cloning process, but what is the FileOperations ::destroy? Will the cloning actually copy/clone the disks then destroy them? And how does this work for multiple disks? This type of pipelining will loop through all disks (Get-HardDisk returns them all) and remove all of them?

Thanks!

0 Kudos
LucD
Leadership
Leadership
Jump to solution

As you see there are 2 types of "operation" for each device (harddisk), the regular "operation" says which devices not to include in the clone.

The "fileoperation" field states what should be done with the backing file of the device.

But as far as I can tell the VMDKs do not even get copied to the clone, and I suspect you could leave out the "fileoperation" alltogether.

The speed of the cloning process also seems to confirm that the VMDK are not even copied to the clone.


Blog: lucd.info  Twitter: @LucD22  Co-author PowerCLI Reference

0 Kudos
MarcBouchard
Enthusiast
Enthusiast
Jump to solution

I have been experimenting with large thick disks and as you say, the speed confirms it is not copying them. Thanks for the clarifications.

I am working on re-adding the disks and it works great when I run it manually, but when scripted, the "new-harddisk" commands run before the cloning is finished.

In other parts of the code, I use wait-task to avoid this issue but this seems to only work with cmdlets. Any input on how I can make sure (short of putting a timer) that the clone is done before proceeding?

########################################################################

# --- Create clone

########################################################################

$SourceVM.ExtensionData.CloneVM_Task($CloneFolder,$CloneName,$CloneSpec)

########################################################################

# --- Add SourceVM hard disks in non-persistent mode

########################################################################

$AllDisks=Get-HardDisk -VM $SourceVM

ForEach ($HD in $AllDisks) {

  New-HardDisk -VM $CloneName -Persistence IndependentNonPersistent -DiskPath $HD.Filename

  }

}

0 Kudos
MarcBouchard
Enthusiast
Enthusiast
Jump to solution

Just found a piece of code you wrote for someone else with the same query... i'll give it a shot Smiley Happy

Re: wait for completion of CloneVM_Task

$clonetaskMoRef = $_this.CloneVM_Task($folder, "myvm-clone", $spec)
$clonetask = Get-View $clonetaskMoRef
while("running","queued" -contains $clonetask.Info.State){
 
Write-Host "Task state $($clonetask.Info.State) ...."
 
sleep 1
 
$clonetask.UpdateViewData("Info.State")
}

Write-Host "Final task state $($clonetask.Info.State)"

0 Kudos
LucD
Leadership
Leadership
Jump to solution

An alternative is to use the CloneVM method instead of the CloneVM_Task as I had in my script.

The API methods that end in _Task are similar to the PowerCLI cmdlets you run with RunAsync switch.

They come back immediately and will run in the background.


Blog: lucd.info  Twitter: @LucD22  Co-author PowerCLI Reference

0 Kudos
MarcBouchard
Enthusiast
Enthusiast
Jump to solution

Thanks again! I will post the final scripts on my blog (with credits Smiley Wink ) when they are completed. Might come in handy for others!

0 Kudos
MarcBouchard
Enthusiast
Enthusiast
Jump to solution

As promised, here's the final script:

http://www.subnet192.com/?p=700

0 Kudos
LucD
Leadership
Leadership
Jump to solution

Great script, thanks for sharing.


Blog: lucd.info  Twitter: @LucD22  Co-author PowerCLI Reference

0 Kudos