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:

3 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

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

      Like

Leave a Reply to rutgerblom Cancel 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 )

Google photo

You are commenting using your Google 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.