And now something completely different. Recently, while working on a project, I had to come up with a way to automate Ubuntu Server 20.04 VM installations on vSphere. Utilizing Ubuntu’s new autoinstall method together with some Ansible code I managed to get something up and running. Decent enough to share it with you.
Overview
The Playbook I’m showcasing in this article will carry out the following operations:
- Download the Ubuntu Server 20.04 ISO
- Modify ISO contents to enable unattended installation
- Upload the modified ISO to a vSphere datastore
- Create a VM and install Ubuntu Server 20.04 using the modified ISO
- Configure static IP on the Ubuntu Server
Step 5 is required because for some reason Ubuntu’s autoinstall reverts the network configuration to DHCP after rebooting the server. I hope this will be fixed in a future release.
Requirements
To run this Playbook you will need the following:
- An Ubuntu 20.04 machine with the following:
- sudo apt install python3 python3-pip git xorriso
- pip3 install ansible pyvim pyvmomi
- ansible-galaxy collection install community.general community.vmware ansible.posix
- An Internet connection
- Access to your vSphere environment
- vSphere 6.7 or higher
Playbook
Below the contents of the Ansible Playbook for reference. This together with the supporting files is actually better viewed and cloned on Github.
---
- hosts: localhost
name: DeployUbuntu.yml
gather_facts: false
vars:
TempDir: /tmp # Temp directory on the Ansible Controller
WorkingDir: /tmp/ubuntu20 # Working directory on the Ansible Controller
UbuntuISO: ubuntu-20.04.2-live-server-amd64.iso # Ubuntu ISO filename
UbuntuISO_URL: https://releases.ubuntu.com/20.04/ # Ubuntu ISO URL
UbuntuNewISO: ubuntu2004.iso # Ubuntu custom ISO name
vCenterServer: vcenter.demo.local # vCenter FQDN
vCenterUser: administrator@vsphere.local # vCenter username
vCenterPassword: VMware1! # vCenter password
DataCenter: SDDC # vCenter datacenter
vSphereCluster: Lab-Cluster # vCenter cluster
VMFolder: Management # vCenter VM folder
Datastore: Shared_VMs # vSphere datastore
DatastoreDir: /SDDCLab-ISO-Folder # vSphere datastore ISO directory
UbuntuVMName: demo-server # Ubuntu VM name of the virtual machine
UbuntuVMDiskSize: 50 # Ubuntu VM disksize in gigabytes
UbuntuVMMemorySize: 2048 # Ubuntu VM memory size in megabytes
UbuntuVMCPUs: 1 # Ubuntu VM number of CPUs
UbuntuVMCPUCores: 1 # Ubuntu VM number of cores
UbuntuVMPortGroup: VLAN-301 # Ubuntu VM vSphere portgroup
UbuntuOSLocale: en_US # Ubuntu OS locale
UbuntuOSKeyboardLayout: en # Ubuntu OS keyboard layout
UbuntuOSKeyboardVariant: us # Ubuntu OS keyboard variant
UbuntuOSIPv4Address: 10.203.0.50/24 # Ubuntu OS IPv4 address
UbuntuOSIPv4Gateway: 10.203.0.1 # Ubuntu OS IPv4 gateway
UbuntuOSIPv4DNS: 10.203.0.5 # Ubuntu OS DNS server
UbuntuOSSearchDomain: sddc.lab # Ubuntu OS DNS search domain
UbuntuOSHostname: demo-server # Ubuntu OS hostname
UbuntuOSUser: ubuntu # Ubuntu OS username
UbuntuOSPassword: VMware1! # Ubuntu OS password
tasks:
- name: Create working directory on Ansible Controller
ansible.builtin.file:
path: "{{ WorkingDir }}"
state: directory
- name: Check if Ubuntu ISO exists locally on Ansible Controller
ansible.builtin.stat:
path: "{{ TempDir }}/{{ UbuntuISO }}"
register: InstallerFileCheck
- name: Download Ubuntu ISO (if ISO file doesn't exist locally)
ansible.builtin.get_url:
url: "{{ UbuntuISO_URL }}{{ UbuntuISO }}"
dest: "{{ TempDir }}/{{ UbuntuISO }}"
when:
- InstallerFileCheck.stat.exists != true
- name: Mount Ubuntu ISO
ansible.posix.mount:
path: "{{ WorkingDir }}/iso"
src: "{{ TempDir }}/{{ UbuntuISO }}"
fstype: iso9660
opts: ro,noauto
state: mounted
- name: Copy txt.cfg from Ubuntu ISO
ansible.builtin.copy:
src: "{{ WorkingDir }}/iso/isolinux/txt.cfg"
dest: "{{ WorkingDir }}/isocopy/isolinux/"
mode: "666"
- name: Edit txt.cfg to modify append line
ansible.builtin.replace:
dest: "{{ WorkingDir }}/isocopy/isolinux/txt.cfg"
regexp: 'append initrd=/casper/initrd quiet ---'
replace: 'append initrd=/casper/initrd quiet --- autoinstall ds=nocloud;s=/cdrom/autoinstall/'
- name: Create directory to store user-data and meta-data
ansible.builtin.file:
path: "{{ WorkingDir }}/isocopy/autoinstall"
state: directory
- name: Copy user-data file to directory
ansible.builtin.template:
src: ./Ubuntu_user-data.j2
dest: "{{ WorkingDir }}/isocopy/autoinstall/user-data"
mode: "666"
- name: Create empty meta-data file in directory
ansible.builtin.file:
path: "{{ WorkingDir }}/isocopy/autoinstall/meta-data"
state: touch
mode: "666"
- name: Create custom Ubuntu ISO
ansible.builtin.command: "xorrisofs -relaxed-filenames -J -R -o {{ TempDir }}/{{ UbuntuNewISO }} -b isolinux/isolinux.bin -c isolinux/boot.cat -no-emul-boot -boot-load-size 4 -boot-info-table {{ WorkingDir }}/iso/ {{ WorkingDir }}/isocopy/"
args:
chdir: "{{ WorkingDir }}/isocopy/"
- name: Unmount Ubuntu ISO
ansible.posix.mount:
path: "{{ WorkingDir }}/iso"
src: "{{ TempDir }}/{{ UbuntuISO }}"
fstype: iso9660
opts: ro,noauto
state: absent
- name: Upload the custom Ubuntu ISO to the vSphere datastore
community.vmware.vsphere_copy:
hostname: "{{ vCenterServer }}"
username: "{{ vCenterUser }}"
password: "{{ vCenterPassword }}"
validate_certs: no
datacenter: "{{ DataCenter }}"
src: "{{ TempDir }}/{{ UbuntuNewISO }}"
datastore: "{{ Datastore }}"
path: "{{ DatastoreDir }}/{{ UbuntuNewISO }}"
- name: Deploy Ubuntu VM
community.vmware.vmware_guest:
hostname: "{{ vCenterServer }}"
username: "{{ vCenterUser }}"
password: "{{ vCenterPassword }}"
validate_certs: no
name: "{{ UbuntuVMName }}"
state: poweredon
guest_id: ubuntu64Guest
cluster: "{{ vSphereCluster }}"
datacenter: "{{ DataCenter }}"
folder: "{{ VMFolder }}"
disk:
- size_gb: "{{ UbuntuVMDiskSize }}"
type: thin
datastore: "{{ Datastore }}"
hardware:
memory_mb: "{{ UbuntuVMMemorySize }}"
num_cpus: "{{ UbuntuVMCPUs }}"
num_cpu_cores_per_socket: "{{ UbuntuVMCPUCores }}"
scsi: paravirtual
networks:
- name: "{{ UbuntuVMPortGroup }}"
device_type: vmxnet3
cdrom:
- controller_number: 0
unit_number: 0
type: iso
iso_path: "[{{ Datastore }}] {{ DatastoreDir }}/{{ UbuntuNewISO }}"
state: present
annotation: |
*** Auto-Deployed by Ansible ***
Username: {{ UbuntuOSUser }}
Password: {{ UbuntuOSPassword }}
- name: Start checking if the Ubuntu VM is ready
community.vmware.vmware_guest_info:
hostname: "{{ vCenterServer }}"
username: "{{ vCenterUser }}"
password: "{{ vCenterPassword }}"
datacenter: "{{ DataCenter }}"
validate_certs: no
name: "{{ UbuntuVMName }}"
schema: vsphere
register: vm_facts
until: vm_facts.instance.guest.hostName is search(UbuntuOSHostname)
retries: 30
delay: 60
- name: Set password for the Ubuntu user
community.vmware.vmware_vm_shell:
hostname: "{{ vCenterServer }}"
username: "{{ vCenterUser }}"
password: "{{ vCenterPassword }}"
validate_certs: no
vm_id: "{{ UbuntuVMName }}"
vm_username: "{{ UbuntuOSUser }}"
vm_password: VMware1!
vm_shell: /usr/bin/echo
vm_shell_args: "'{{ UbuntuOSUser }}:{{ UbuntuOSPassword }}' | sudo chpasswd"
- name: Copy network configuration file to working directory
ansible.builtin.template:
src: ./Ubuntu_Netplan.j2
dest: "{{ WorkingDir }}/00-installer-config.yaml"
mode: "666"
- name: Copy network configuration file to Ubuntu VM
community.vmware.vmware_guest_file_operation:
hostname: "{{ vCenterServer }}"
username: "{{ vCenterUser }}"
password: "{{ vCenterPassword }}"
validate_certs: no
vm_id: "{{ UbuntuVMName }}"
vm_username: "{{ UbuntuOSUser }}"
vm_password: VMware1!
copy:
src: "{{ WorkingDir }}/00-installer-config.yaml"
dest: "/home/{{ UbuntuOSUser }}/00-installer-config.yaml"
- name: Move network configuration file to right location on Ubuntu VM
community.vmware.vmware_vm_shell:
hostname: "{{ vCenterServer }}"
username: "{{ vCenterUser }}"
password: "{{ vCenterPassword }}"
validate_certs: no
vm_id: "{{ UbuntuVMName }}"
vm_username: "{{ UbuntuOSUser }}"
vm_password: VMware1!
vm_shell: /usr/bin/sudo
vm_shell_args: "mv /home/{{ UbuntuOSUser }}/00-installer-config.yaml /etc/netplan/00-installer-config.yaml"
- name: Appply the network configuration on Ubuntu VM
community.vmware.vmware_vm_shell:
hostname: "{{ vCenterServer }}"
username: "{{ vCenterUser }}"
password: "{{ vCenterPassword }}"
validate_certs: no
vm_id: "{{ UbuntuVMName }}"
vm_username: "{{ UbuntuOSUser }}"
vm_password: VMware1!
vm_shell: /usr/bin/sudo
vm_shell_args: netplan apply
- name: Delete working directory on Ansible Controller
ansible.builtin.file:
path: "{{ WorkingDir }}"
state: absent
Most of the tasks here are pretty self-explanatory. I’ve also tried to use descriptive names for each task to help you understand what is happening.
You will change some of the values of the variables defined under vars: so that they match your environment. Besides that it’s pretty much good to go.
Usage
So, to deploy an Ubuntu Server 20.04 VM using this Playbook you’ll first clone the repository:
git clone https://github.com/rutgerblom/ubuntu-autoinstall.git
Then modify DeployUbuntu.yml so that the values of the variables match your environment and requirements:

Now run the Playbook with:
sudo ansible-playbook DeployUbuntu.yml
The deployment kicks off and will take about 15 minutes depending on your environment:

And the result is a shiny new Ubuntu Server 20.04 VM:

Not too hard. If Ubuntu’s autoinstall would leave the networking configuration in place after reboot, I would be able to shave off 5 tasks and more than 10 minutes from this deployment. Perhaps somebody out there already knows of a way to handle this? Let me know in the comments.
Summary
In this article I showcased a very basic single-purpose Ansible Playbook for unattended deployment of an Ubuntu Server 20.04 VM on VMware vSphere. This can of course be expanded upon and become part of a larger (automation) process. Hopefully you’ll find this useful and can serve as some inspiration for your projects.
Thanks for reading.
References and resources:
thanks for the post i tried the same it was work fine i tried the same with the desktop version but it was not working as expected even i tried with pressed files but it was also not working if you can help me on the same it will be great i tried on Ubuntu forums but i’m not able to find any solution for the same
LikeLike
Hi,
a noob question: how are the LOCAL_UbuntuOS* variables passed inside the VM with LOCA_ stripped away, since they are used within the .j2 files for netplan and user data? BTW, nice guide!
LikeLike
Hi Gianni,
The script was updated after this article was published. The variables are not prefixed with “LOCAL_” anymore. Will update the post.
Thanks for the heads up.
LikeLiked by 1 person
It work for a few year but now with exsi 7.0 is does’t work anymore. @Rutger blom. Does It stil works for you?
During handling of the above exception, another exception occurred:
LikeLike