Automate Ubuntu Server 20.04 Installation with Ansible

Posted by

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:

  1. Download the Ubuntu Server 20.04 ISO
  2. Modify ISO contents to enable unattended installation
  3. Upload the modified ISO to a vSphere datastore
  4. Create a VM and install Ubuntu Server 20.04 using the modified ISO
  5. 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:

4 comments

  1. 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

    Like

  2. 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!

    Like

  3. 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:

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.