Using Packer to Automate Building Ubuntu Server 20.04 VM Images for Proxmox

,

Starting from 20.04, Ubuntu decided to update the live server installer to support automated installation specifications, enabling a fully automated installation process using only Subiquity. Subiquity is the new server installer (also known as “server everywhere”), designed to replace the classic system based on debian-installer.

This article explains how to generate an Ubuntu Server 20.04 virtual machine image template using Packer and Subiquity on Proxmox.

Introduction

Subiquity is only available in live versions (e.g., ubuntu-20.04.1-live-server-amd64.iso). The previous debian-installer-based systems have been discontinued and are no longer in use.

However, you can still use debian-installer by downloading the old server images found in the dedicated folder for ubuntu-legacy-server in the official Ubuntu image repository.

Subiquity

This new live system is based on cloud-init and uses YAML files to fully automate the installation process. It differs from previous systems in several ways.

The syntax is easier to understand (YAML vs. debconf-set-selections format).

There may be hybrid situations where some parts can be interactive while others can be automatically answered from the configuration.

Proxmox

Packer requires a user account to perform actions on the Proxmox API. The following commands will create a new user account packer@pve with restricted permissions.

$ pveum useradd packer@pve

$ pveum passwd packer@pve

Retype new password: ****************

Enter new password: ****************

$ pveum roleadd Packer -privs “VM.Config.Disk VM.Config.CPU VM.Config.Memory Datastore.AllocateSpace Sys.Modify VM.Config.Options VM.Allocate VM.Audit VM.Console VM.Config.CDROM VM.Config.Network VM.PowerMgmt VM.Config.HWType VM.Monitor”

$ pveum aclmod / -user packer@pve -role Packer

The Proxmox pveum can be accessed via SSH or from the web shell available in the node parameters on the UI. Download the Ubuntu Server 20.04 ISO from the image repository. As of writing this article, the latest available version is ubuntu-20.04.1-live-server-amd64.iso. Place the ISO in the storage category local under the ISO image storage.

Packer

Builder Configuration

Packer will execute a workflow to create a new template that can quickly bootstrap new VMs with the pre-configured settings applied.

Technically, Packer will start a virtual machine in Proxmox, boot the installer from the boot command referencing the configuration file, and convert the VM into a template after the installation is complete.

{

  “builders”: [{

    “type”: “proxmox”,

    “proxmox_url”: “https://proxmox.madalynn.xyz/api2/json”,

    “username”: “{{ user `proxmox_username` }}”,

    “password”: “{{ user `proxmox_password` }}”,

    “node”: “proxmox”,

    “network_adapters”: [{

      “bridge”: “vmbr0”

    }],

    “disks”: [{

      “type”: “scsi”,

      “disk_size”: “20G”,

      “storage_pool”: “local-lvm”,

      “storage_pool_type”: “lvm”

    }],

    “iso_file”: “local:iso/ubuntu-20.04.1-live-server-amd64.iso”,

    “unmount_iso”: true,

    “boot_wait”: “5s”,

    “memory”: 1024,

    “template_name”: “ubuntu-20.04”,

    “http_directory”: “http”,

    “boot_command”: [

      “<esc><wait><esc><wait><f6><wait><esc><wait>”,

      “<bs><bs><bs><bs><bs>”,

      “autoinstall ds=nocloud-net;s=http://{{ .HTTPIP }}:{{ .HTTPPort }}/ “,

      “— <enter>”

    ],

    “ssh_username”: “madalynn”,

    “ssh_password”: “madalynn”,

    “ssh_timeout”: “20m”

  }]

}

Most parameters are easy to understand. This `ssh_timeout` will give the installer time to download the latest security updates during the installation.

Use the following command to start Packer.

$ packer build -var-file=secrets.json ubuntu.json

The `var-file` parameter provides flexibility to extract secrets (like credentials) and dynamic parameters for building multiple Ubuntu images using the workflow. Minimum requirements should include Proxmox credentials from the previously created user.

{

  “proxmox_username”: “packer@pve”,

  “proxmox_password”: “fQk9f5Wd22aBgv”

}

Packer will start an HTTP server from the contents of the `http_directory` parameter. This will allow Subiquity to remotely fetch the cloud-init files.

The live installer Subiquity uses more memory than debian-installer. The default of 512M for Packer is insufficient, leading to strange kernel panics. Aim for at least 1G.

—[ end Kernel panic – not syncing: No working init found. Try passing init= option to kernel. See Linux Documentation/admin-guide/init.rst for guidance. ]—

The boot command tells cloud-init to start and use the nocloud-net data source to load the user-data and meta-data files from a remote HTTP endpoint. The additional `autoinstall` parameter will force Subiquity to perform destructive operations without user confirmation.

{

  …

  “boot_command”: [

    “<esc><wait><esc><wait><f6><wait><esc><wait>”,

    “<bs><bs><bs><bs><bs>”,

    “autoinstall ds=nocloud-net;s=http://{{ .HTTPIP }}:{{ .HTTPPort }}/ “,

    “— <enter>”

  ],

  …

}

Provisioner Configuration

Cloud-init will handle everything else. However, once Packer can connect to the VM via SSH, it assumes the configuration is complete. But at this point, the setup process is not yet fully finished. Packer should be instructed to wait for cloud-init to complete fully.

Technically, the simplest solution is to wait for the `/var/lib/cloud/instance/boot-finished` file to exist. Creating this file is the last thing that cloud-init does. A simple bash script while loop can solve the issue.

{

  “provisioners”: [{

    “type”: “shell”,

    “inline”: [

      “while [ ! -f /var/lib/cloud/instance/boot-finished ]; do echo ‘Waiting for cloud-init…’; sleep 1; done”

    ]

  }]

}

Once the SSH connection is available, the Provisioners will execute directly on the VM. Packer supports many provisioners. For example, an Ansible playbook or Chef client can be launched in this step.

Cloud Initialization

Since Subiquity uses cloud-init, the configuration should exist in two files: user-data and meta-data. `user-data` is the main configuration file that Subiquity and cloud-init will use for configuration. `meta-data` is an additional file that can host some extra metadata in the EC2 metadata service format.

The `meta-data` file can be empty (and will be used for Proxmox) but must exist; otherwise, cloud-init will not start correctly.

http

├── meta-data

└── user-data

Cloud-init supports configuration files in various formats. YAML is the easiest to understand and will be used in the following code snippets. Subiquity adds a new module `autoinstall`, which carries all the configurations required for installation.

#cloud-config

autoinstall:

  …

Unlike classic cloud-init files, everything must be under the `autoinstall` key. The rest will be ignored. The official documentation lists all available configurable parameters. Compared to debian-installer, the scope is narrowed, but Subiquity compensates with the ability to use all other cloud-init modules.

Behind the scenes, Subiquity will be able to handle some operations (like partitioning) itself and will generate a cloud-init configuration file that will execute after reboot.

All “native” cloud-init modules must be under the `user-data` key. For example, to use the `write_files` cloud-init module, the following configuration can be used.

#cloud-config

autoinstall:

  …

  user-data:

    write_files:

      – path: /etc/crontab

        content: |

                    15 * * * * root ship_logs

        append: true

    …

Autoinstall Configuration

Autoinstall is responsible for answering all questions posed during the installation process (keyboard layout, additional packages, etc.). The scope is limited, and other workflows should be managed using cloud-init modules (see above).

#cloud-config

autoinstall:

  version: 1

  locale: en_US

  keyboard:

    layout: fr

  ssh:

    install-server: true

    allow-pw: true

  packages:

    – qemu-guest-agent

The `qemu-guest-agent` package is required by Packer to detect the VM’s IP address for executing SSH connections. This will also allow Proxmox to display VM resources directly in the user interface.

The VM will be configured with an English installation using a French keyboard. The mapped keys correspond to the settings in `/etc/default/keyboard`. For more details, refer to its man page.

Remote connections from Packer require an SSH server. By default, it will attempt to connect using only a username and password. This requires the `allow-pw` parameter to be enabled.

If `allow-pw` is not set, the SSH server will only accept connections using certificates. Packer must be configured to use the `ssh_keypair_name` section to do this.

Identity

In addition to the previous parameters, Subiquity can also create user accounts during configuration using the `identity` section.

#cloud-config

autoinstall:

  identity:

    hostname: ubuntu

    username: madalynn

    password: $6$xyz$1D0kz5pThgRWqxWw6JaZy.6FdkUCSRndc/PMtDr7hMK5mSw7ysChRdlbhkX83PBbNBpqXqef3sBkqGw3Rahs..

This section is also responsible for setting the hostname. Since this VM is only used as a base for the template, it is inconsequential and should be set during final configuration.

The previous block will create a user `madalynn` with `madalynn` as the password.

You can use the following command to generate a Unix encrypted password.

$ openssl passwd -6 -salt xyz madalynn

For more flexible account creation, you can alternatively use the cloud-init module `users`.

#cloud-config

autoinstall:

  …

  user-data:

    users:

      – name: madalynn

        passwd: $6$xyz$1D0kz5pThgRWqxWw6JaZy.6FdkUCSRndc/PMtDr7hMK5mSw7ysChRdlbhkX83PBbNBpqXqef3sBkqGw3Rahs..

        groups: [adm, cdrom, dip, plugdev, lxd, sudo]

        lock-passwd: false

        sudo: ALL=(ALL) NOPASSWD:ALL

        shell: /bin/bash

        ssh_authorized_keys:

          – ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJEXrziuUOCpWPvwOsGuF4K+aq1ufToGMi4ra/1omOZb

This module provides additional parameters for configuration: groups, shell binaries, SSH authorized keys, etc. The `sudo` parameter allows the command to be used without entering a password. This will help later when, for example, Ansible completes installation from the template.

You cannot use both the cloud-init module `users` and the autoinstall `identity` block simultaneously. If `identity` exists, Subiquity will discard the `users` module configuration.

Networking

Subiquity allows the use of two preconfigured layouts, `lvm` and `direct`. By default, Subiquity will use the `lvm4G` logical volume. The installer will not extend partitions to use the full capabilities of the volume group. You can also configure the size of the swap file on the filesystem (0 to disable).

#cloud-config

autoinstall:

  …

  storage:

    layout:

      name: direct

    swap:

      size: 0

If `direct` is used, `/dev/sda2` will create a single partition using the entire disk.

Filesystem                           Size  Used Avail Use% Mounted on

udev                                 1.9G     0  1.9G   0% /dev

tmpfs                                394M  696K  393M   1% /run

/dev/sda2                             20G  3.6G   15G  20% /

tmpfs                                2.0G     0  2.0G   0% /dev/shm

tmpfs                                5.0M     0  5.0M   0% /run/lock

tmpfs                                2.0G     0  2.0G   0% /sys/fs/cgroup

You can also use a more complex configuration based on curtin under the `config` key. If the VM has multiple disks, this will be a requirement (in which case, the preconfigured layout will not work).

#cloud-config

autoinstall:

  …

  storage:

    config:

      – type: disk

        id: root-disk

        size: largest

      – type: partition

        id: boot-partition

        device: root-disk

        size: 10%

      – type: partition

        id: root-partition

        size: 20G

      – type: partition

        id: data-partition

        device: root-disk

        size: -1

The previous configuration will create three partitions on the largest drive.

10% for the boot partition.

20G for the root partition.

The remainder for the data partition.

These are the first steps of a custom layout. However, this is not enough; further steps (formatting, mounting, etc.) are required.

If you use a preconfigured layout, the custom configuration will be ignored.

GitHub

The complete configuration can be found in the GitHub repository `aerialls/madalyn-packer`.


Leave a Reply

Your email address will not be published. Required fields are marked *