Tag Archives: Packer

Using Packer To Build Development VMs

One fundamental development practice is to have a bullet-proof, repeatable process for building, upgrading and maintaining development environments. My current development practice relies on developing inside a VM using Visual Studio Code’s Remote Development plugin. I treat the development VMs as ‘disposable’, i.e. at any moment in time I ought to be able to commit my work in progress to Github, destroy the VM, build a new one and pick up right where I left off. I should also be able to move seamlessly from one virtualized environment to another – for example, I should be able to develop in the Proxmox VM at home and a VirtualBox VM on my laptop when I travel without any friction between the two.

I use Hashicorp’s Packer tool to automate the VM build and configuration process. I usually maintain two target platforms: 1) a Proxmox server with an NFS backend I maintain at home and 2) VirtualBox which is installed on my laptops. Packer is declarative and supports multiple ‘builders’ for different backends. Both Proxmox and VirtualBox are supported and there is minimal difference in the builder specifications between the two.

Practical Packer

Packer and its builders and provisioners do most of the heavy lifting for you in terms of getting a ‘vanilla’ VM built. Perhaps the most complicated bit is figuring out the correct ‘boot command prefix’ to get past the bootloader. Honestly, getting a prefix that works involves a bit of trial and error and is somewhat cryptic. For the projects I have in Github, the prefix ‘works for me’ but if you are running on either a very fast or very slow machine, then your mileage may vary.

With a ‘vanilla’ VM in hand, the next step is to tailor it to your development needs. Packer is not inherently modular but I have managed to introduce some modularity by providing a collection of scripts that will be run inside the VM after it boots to customize the environment. The Packer specification will invoke these scripts which will either execute or return immediately based on environment variables set from Packer variables which can be set in a HCL file or set on the Packer command line.

The main challenge when executing the scripts is determining if you want the scripted commands to run as root, which is how the provisioner executes shell scripts, or as the ‘development user’ created early in the provisioning process. Essentially, the ‘development user’ is the username you will want to use when logging into the VM for development. The scripts will automatically create this user and assign a single-use password that will have to be changed on first login. The ‘change on login’ feature was not straightforward – so if you want a similar capability, just lift it from my code.

Github Projects

In the https://github.com/stephanfr/Packer repository you will find the Packer specifications for building Ubuntu development VMs in either a Proxmox or VirtualBox environment as well as a project which will allow you to build a VM which can then be used to build bootable, customized RPi images in QEMU. This is a very nice capability and is all due to the work of Mateusz Kaczanowski’s in his PackerBuilderArm Project.

One VM build option sets up an AArch64 bare-metal build environment inside the VM with a directory structure for my RPi Bare Metal OS project. In general though, if you are looking for an easy ARM toolchain setup – you can lift that code as well and modify it for your purposes.

Maintenance

Since I use these specs myself, I will track Ubuntu releases and tooling updates – but the timing is likely to be a bit erratic. I do not generally update tools immediately, I tend to value a stable environment over ‘latest and greatest’ but once a year or so I will update. Mostly updates *should* be limited to tweaking config values but sometimes, stuff just breaks. For example, I have not had success automating deployment of the very most recent Ubuntu VMs.

RPI Bare Metal Cross Compiling Toolchain

Bare metal builds require a specific toolchain and compile options to insure that code normally generated for processes running in a standard OS environment is not generated and various standard libraries are not included.

Cross Compiling

I chose to use a cross-compiling approach for development. It should absolutely be possible to build the OS on a RPI itself with the correct toolchain but there are a lot of advantages that accrue from building on a larger x86_64 system with NFS, etc.

A variety of toolchains are available on the ARM developer website. At present they are all GCC based. Clang builds ought to work as well, though the make files will have to be modified. The toolchain I have been using is: AArch64 bare-metal target (aarch64-none-elf).

Libraries and Header Files

I have avoided using any libraries and the absolute bare minimum of header files to build the OS. This includes creating very minimal replacements for the C standard library, the standard IO library and the C++ standard library. There are a number of platform specific libraries that are required.

Catch2 for Unit Testing

Unit Tests for the project are built using Catch2. At present, unit tests using Catch2 are compiled for the host machine – not cross-compiled for the target platform. Running Catch2 also requires access to some headers and libraries for standard compilation on the host. This is messy – no doubt about it – and this approach will miss issues with data structure alignment, which matters a lot on AArch64 platforms without memory management enabled. Eventually, it will be possible to host the tests on the target platform but for now my focus is to get the code tested in the most straightforward fashion.

Source code coverage metrics are also available for the unit tests using a coverage target in the makefile.

Directory Structure

The make files (yep good old fashioned make) expect the following structure:

~/dev
|--- RPIBareMetalOS
|--- project cloned from github
|--- gcc-cross
|--- aarch64-none-elf
|--- cross compiling files
~/dev_tools
|--- arm-gcc-toolchain
|--- Catch2

There is a script in the root directory of the project named setup_dev_env.sh which will setup the development environment. If you have a vanilla Ubuntu (or probably any reasonable Debian based instance) you can simply execute that script and it will install the cross compiling toolchain, Catch2, QEMU for AArch64 and then create the correct directories and copy files to the right places.

Quick Build

There will be another post with greater detail on the build process, however the steps to build are straightforward. To get a running OS, simply do the following after cloning the project from Github:

cd RPIBareMetalOS/
cd minimalclib/
make all
cd ../minimalstdio/
make all
cd ../rpibaremetalos/
cd resources
unzip sd_compressed.zip
cd ..
make armstub_all
make all

The to run the OS in QEMU:

cd image/
qemu-system-aarch64 -M raspi3b -kernel kernel8.img -serial stdio -sd sd.img

Repeatable Scripted Setup

My development pattern is to create an Ubuntu VM customized for various projects and develop within it using the Remote Development Extension in Visual Studio Code. I run a Proxmox server at home but also use VirtualBox.

I have a separate project with Packer scripts to create Ubuntu development VMs in either of the two hypervisors listed above. Modifying the script for other hypervisors should be straightforward. In addition to the build process, there are a number of install scripts that are available to customize the VM after creation, one of which sets up an AArch64 bare metal toolchain and the directory structure I use for development. After building the VM with the right options, one should be able to simply clone the bare metal OS repository, build and run on QEMU.

The github repository for the Packer scripts is: https://github.com/stephanfr/Packer

An example command line to build a development VM template in Proxmox is:

packer build -var "dev_username=????" -var "dev_password=password" -var "proxmox_host=????" -var "proxmox_node_name=????" -var "proxmox_api_user=packer@pve" -var "proxmox_api_password=ubuntu" -var "ssh_username=packer" -var "ssh_password=password" -var "vmid=????" -var "http_interface=????" -var "install_aarch64_cross=true" -var-file="./22.04/ubuntu-22-04-version.pkrvars.hcl" -var-file="./proxmox/proxmox-config.pkrvars.hcl" -var-file="vm_personalization.pkrvars.hcl" ./proxmox/ubuntu-proxmox.pkr.hcl

After the template is created, clone it and then you will be good to go.

An example command line to build a development VM in VirtualBox appears below:

packer build -var "dev_username=????" -var "dev_password=password" -var "ssh_username=packer" -var "ssh_password=ubuntu" -var "install_aarch64_cross=true" -var-file="./22.04/ubuntu-22-04-version.pkrvars.hcl" -var-file="./virtualbox/virtualbox-config.pkrvars.hcl" -var-file="vm_personalization.pkrvars.hcl" ./virtualbox/ubuntu-virtualbox.pkr.hcl

Replace the ‘????’ sequences with appropriate values. At first login as the development user, you will be forced to change the user’s password.

More documentation can be found in the README file in the Packer script repository.