Managing a VMware Template Lifecycle with Ansible
When we manage a large number of virtual machines (VMs), we want to
reduce the differences between them and create a standard template. By
using a standard template, it becomes easier to manage and propagate the
same operation on the different nodes. When using VMware vSphere, it is
a common practice to share a standardized VM template and use it to
create new VMs. This template is often called a golden image. Its
creation involves a series of steps that can be automated with Ansible.
In this blog, we will see how one can create and use a new golden image.
Prepare the golden image
We use image builder
to prepare a new image. The tool provides a user interface that allows
users to define custom images. In this example, we include the SSH
server and tmux. The result image is a file in the VMDK4 format that is
not totally supported by VMware vSphere 7, so this is the reason why we
use a .vmdk-4
suffix.
We upload the image using the uri module. Uploading large files using
this method is rather slow. If you can, you may want to drop the file
on the datastore directly (e.g: NFS/CIFS). The following example
considers that the datastore is called rw_datastore and the datacenter
name is my_dc. The following excerpt from the playbook shows how we
upload the image.
- name: Upload from contents of remote file
ansible.builtin.uri:
force_basic_auth: true
url: 'https://vcenter.test/folder/my-vmdk/my-golden-image.vmdk-4?dcPath=my_dc&dsName=rw_datastore'
url_username: '{{ lookup("ansible.builtin.env", "VMWARE_USER") }}'
url_password: '{{ lookup("ansible.builtin.env", "VMWARE_PASSWORD") }}'
method: PUT
status_code: 201
src: my-golden-image.vmdk-4
validate_certs: false
follow_redirects: yes
Now that we've uploaded our file, we will convert it as an up to date
VMDK file. For this purpose, we use
vmkfstools.
The tool is available on the ESXi7 hosts by default. In the task below,
we use delegate_to: esxi1.test to run the command on one of our hosts.
- name: Convert the image in an up to date VMDK format
ansible.builtin.command: "vmkfstools -i /vmfs/volumes/rw_datastore/my-vmdk/my-golden-image.vmdk-4.vmdk-4 /vmfs/volumes/rw_datastore/my-vmdk/my-golden-image.vmdk -d thin"
delegate_to: esxi1.test
vars:
ansible_user: root
ansible_python_interpreter: /bin/python3
Preparing the golden VM
At this stage, the disk is ready and we can connect it to a VM.
- name: Create a VM
vmware.vmware_rest.vcenter_vm:
placement:
cluster: ""
datastore: ""
folder: ""
resource_pool: ""
name: my-golden-vm
guest_OS: RHEL_7_64
hardware_version: VMX_11
memory:
hot_add_enabled: true
size_MiB: 1024
disks:
- type: SATA
backing:
type: VMDK_FILE
vmdk_file: "[rw_datastore] my-vmdk/my-golden-image.vmdk"
cdroms:
- type: SATA
sata:
bus: 0
unit: 2
nics:
- backing:
type: STANDARD_PORTGROUP
network: ""
register: my_vm
There are four different ways to clone a VM with Ansible's
vmware.vmware_rest collection.
This article explains the difference between them and how to use them
with Ansible.
Cloning the VM
When we clone a VM, we create a copy of the original. However, the
original can still evolve and a second clone from the same VM is likely
to be different. There is no guarantee that two clones will be based on
exactly the same image.
The vmware.vmware_rest.vcenter_vm
module allows us to prepare either
an instant clone or a regular clone.
Instant clone
According to the VMware official documentation,
an instant clone is a lightweight copy of a live VM. It shares memory
blocks with the original VM. This is the reason why the original VM must
be running before we can clone it.
- name: Turn the power on the VM on, since it's are pre-condition for Instant Clone
vmware.vmware_rest.vcenter_vm_power:
state: start
vm: '{{ my_vm.id }}'
- name: Wait until my VM is ready
vmware.vmware_rest.vcenter_vm_tools_info:
vm: '{{ my_vm.id }}'
register: vm_tools_info
until:
- vm_tools_info is not failed
- vm_tools_info.value.run_state == "RUNNING"
retries: 10
delay: 5
Now we've got the VM up and running, we can instant clone it:
- name: Create an instant clone of a VM
vmware.vmware_rest.vcenter_vm:
placement:
datastore: "{{ lookup('vmware.vmware_rest.datastore_moid', '/my_dc/datastore/local') }}"
folder: "{{ lookup('vmware.vmware_rest.folder_moid', '/my_dc/vm') }}"
resource_pool: "{{ lookup('vmware.vmware_rest.resource_pool_moid', '/my_dc/host/my_cluster/Resources') }}"
state: instant_clone
source: "{{ my_vm.id }}"
name: instant_clone_1
Regular clone
We can also clone an existing VM.
The operation takes more time, up to several hours, if the VM comes with
large disks. The operation creates a full clone of the original VM. This
time, the original VM doesn't need to be running.
- name: Create a full clone of a VM
vmware.vmware_rest.vcenter_vm:
placement:
datastore: "{{ lookup('vmware.vmware_rest.datastore_moid', '/my_dc/datastore/local') }}"
folder: "{{ lookup('vmware.vmware_rest.folder_moid', '/my_dc/vm') }}"
resource_pool: "{{ lookup('vmware.vmware_rest.resource_pool_moid', '/my_dc/host/my_cluster/Resources') }}"
state: clone
source: "{{ my_vm.id }}"
name: full_clone_1
Build a template
Unlike clone, a template gives us the guarantee that the VM inherits
from a static VM image. The vmware.vmware_rest collection gives us the
ability to prepare OVF packages, and since the 2.2.0 release, we can
also prepare a template VM.
Export a VM as an OVF package on a content library
We can export a VM as an OVF package and upload the package into a
content library. This is handy if you want to prepare a standard
template that you will reuse on your vSphere infrastructure. Also, an
OVF package can be downloaded from one vSphere and be reuploaded in a
different one.
In this example, we use Ansible to prepare the OVF package. In this
example, my_vm is not running and the content library is called
my_library_on_nfs.
- name: List all Local Content Library
vmware.vmware_rest.content_locallibrary_info:
register: all_content_libraries
- name: Use the name to identify the right Content Library
set_fact:
nfs_lib: "{{ all_content_libraries.value | selectattr('name', 'equalto', 'my_library_on_nfs')|first }}"
- name: Export the VM as an OVF on the library
vmware.vmware_rest.vcenter_ovf_libraryitem:
session_timeout: 2900
source:
type: VirtualMachine
id: "{{ my_vm.id }}"
target:
library_id: "{{ nfs_lib.id }}"
create_spec:
name: golden_image
description: an OVF example
flags: []
state: present
To spawn a new VM based on this OVF package, you need first to identify
its item entry on the content library.
- name: Get the list of items of the NFS library
vmware.vmware_rest.content_library_item_info:
library_id: '{{ nfs_lib.id }}'
register: lib_items
- name: Define a new fact with the golden image ID
ansible.builtin.set_fact:
golden_image_id: '{{ (lib_items.value|selectattr("name", "equalto", "golden_image")|first).id }}'
Once we've got the item ID, we can call vcenter_ovf_libraryitem
to
spawn a new VM. Since the ID is immutable, we can save for a future use.
- name: Create a new VM based on the OVF
vmware.vmware_rest.vcenter_ovf_libraryitem:
ovf_library_item_id: '{{ golden_image_id }}'
state: deploy
target: resource_pool_id: "{{ lookup('vmware.vmware_rest.resource_pool_moid', '/my_dc/host/my_cluster/Resources') }}"
deployment_spec:
name: my_vm_from_ovf
accept_all_EULA: true
storage_provisioning: thin
Export a VM as a VM template on a content library
The template creation is done with one call of the
vmware.vmware_rest.vcenter_vmtemplate_libraryitems
module. This module
was introduced in the vmware_rest collection 2.2.0.
Here, nfs_lib
is your content library and your VM details are
registered in the my_vm
variable.
- name: Create a VM template on the library
vmware.vmware_rest.vcenter_vmtemplate_libraryitems:
name: golden-template
library: "{{ nfs_lib.id }}"
source_vm: "{{ my_vm.id }}"
placement:
cluster: "{{ lookup('vmware.vmware_rest.cluster_moid', '/my_dc/host/my_cluster') }}"
folder: "{{ lookup('vmware.vmware_rest.folder_moid', '/my_dc/vm') }}"
resource_pool: "{{ lookup('vmware.vmware_rest.resource_pool_moid', '/my_dc/host/my_cluster/Resources') }}"
To spawn a new VM based on this template, we once again need to identify
the item on the content library.
- name: Get the list of items of the NFS library
vmware.vmware_rest.content_library_item_info:
library_id: '{{ nfs_lib.id }}'
register: lib_items
- name: Use the name to identify the item
set_fact:
my_template_item: "{{ lib_items.value | selectattr('name', 'equalto', 'golden-template')|first }}"
We use the same module for the deployment:
- name: Deploy a new VM based on the template
vmware.vmware_rest.vcenter_vmtemplate_libraryitems:
name: vm-from-template
library: "{{ nfs_lib.id }}"
template_library_item: "{{ my_template_item.id }}"
placement:
cluster: "{{ lookup('vmware.vmware_rest.cluster_moid', '/my_dc/host/my_cluster') }}"
folder: "{{ lookup('vmware.vmware_rest.folder_moid', '/my_dc/vm') }}"
resource_pool: "{{ lookup('vmware.vmware_rest.resource_pool_moid', '/my_dc/host/my_cluster/Resources') }}"
state: deploy
In conclusion, in just a couple of Ansible tasks, we can promote an
existing VM as a Template. This allows us to rely on Ansible to automate
the maintenance of our VM Templates and ensure the reproducibility of
the process.