Last time we covered booting a FreeBSD VM image using QEMU. This is great for setting up a VM by hand or just logging in to play around and experiment with the system. For production use though we want to automate configuration of the FreeBSD VM image so that we are not manually running install commands and editing configuration files, etc.
For this we use Packer from HashiCorp.
Once again these instructions assume an amd64 (x86_64) Ubuntu 22.04 host machine and using QEMU as the virtualization layer. The Linux KVM hardware accelerator can be used to improve performance.
Install Packer
The official instructions vary per operating system, but in reality all that’s needed is to download the self-contained packer executable.
➜ packer version
Packer v1.9.4
Enable rootless KVM
By default Linux disallows non-root users from making use of the KVM feature of the kernel. The implication is that all commands launching a VM would need to be run as root which is not ideal. Instead we can change the permissions to allow non-root users to use the KVM.
sudo chmod a+rw /dev/kvm
Download CI FreeBSD VM image
FreeBSD thankfully provides CI VM images
which include sshd
running by default. This is a huge quality of life
improvement over dealing with boot commands and kickstart configuration. All we
need to do is configure Packer to access the VM using SSH after booting.
Packer can be configured to download and extract images automatically but it’s more efficient to download the image and decompress it before invoking packer. Iterating on the packer config then does not re-download and re-decompress the image.
wget https://download.freebsd.org/releases/CI-IMAGES/14.0-RELEASE/amd64/Latest/FreeBSD-14.0-RELEASE-amd64-BASIC-CI.raw.xz
unxz FreeBSD-14.0-RELEASE-amd64-BASIC-CI.raw.xz
The unxz
command can take several minutes to complete even on a modern machine
with an NVMe drive.
Create packer config
Create a minimal packer config file freebsd.pkr.hcl
source "qemu" "freebsd14" {
iso_urls = [
"./FreeBSD-14.0-RELEASE-amd64-BASIC-CI.raw"
]
iso_checksum = "none"
accelerator = "kvm"
format = "qcow2"
qemuargs = [
["-bios", "/usr/share/ovmf/OVMF.fd"],
["-cpu", "host"],
]
headless = true
disk_image = true
efi_boot = true
boot_wait = "30s"
memory = 2048
cpus = 2
ssh_username = "root"
ssh_password = ""
shutdown_command = "shutdown -p now"
shutdown_timeout = "30s"
output_directory = "outputs"
vm_name = "freebsd14.qcow2"
}
build {
sources = ["source.qemu.freebsd14"]
}
packer {
required_plugins {
qemu = {
source = "github.com/hashicorp/qemu"
version = "~> 1"
}
}
}
Use packer init
to install the necessary plugin(s) for QEMU.
packer init freebsd.pkr.hcl
At this point we already have enough to start building an image.
packer build freebsd.pkr.hcl
We could run the image directly using QEMU like in the previous blog post, but that isn’t the goal. What we really want to do is automatically configure the image, turning it into the useful machine it should be.
Configure the image with provisioners
As an example let’s add a setup.sh
script that runs some basic commands for
bootstrapping the system. We’ll also place a file into /tmp
on the server
just to demonstrate how.
In a directory named files
create hello.txt
Locally,
➜ cat files/hello.txt
Hello from FreeBSD! :)
And also create setup.sh
#!/usr/bin/env sh
set -xeuo pipefail
# Update base image.
freebsd-update --not-running-from-cron fetch install
# Some automation defaults.
export ASSUME_ALWAYS_YES=YES
echo WITH_PKGNG=yes >> /etc/make.conf
pkg bootstrap
pkg update
pkg upgrade -y
echo 'autoboot_delay="0"' >> /boot/loader.conf
# Install packages.
pkg install bat
Now we can edit the build
block of the packer config with two provisioners.
The first uses the "file"
provisioner to copy our files
directory onto the
machine under /tmp/install/
. The second uses the "shell"
provisioner to execute
the commands in setup.sh
which in our case updates the system, sets some nice
automation defaults around packages, and installs the bat
package.
build {
sources = ["source.qemu.freebsd14"]
provisioner "file" {
destination = "/tmp/install"
source = "./files"
}
provisioner "shell" {
scripts = [
"./files/setup.sh",
]
}
}
Ready to Run
And with that, our Packer configuration is able to build and configure the
FreeBSD image automatically, and repeatedly the same way every time. If you
boot into the machine you should be able to run the bat
command.
packer build freebsd.pkr.hcl
qemu-system-x86_64 \
-m 4096 \
-smp 2 \
-cpu host \
-bios /usr/share/ovmf/OVMF.fd \
-serial mon:stdio -nographic \
-drive file=./outputs/freebsd14.qcow2 \
-enable-kvm
root@freebsd:~ # bat /tmp/install/hello.txt
───────┬───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
│ File: /tmp/install/hello.txt
───────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
1 │ Hello from FreeBSD! :)
───────┴───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
root@freebsd:~ #
gl;hf!