Building Qemu-Kvm Images With Packer (Part I)
Building Rocky Linux 9 VM Images with Packer
Date: 11 April 2023
1 Introduction
We are going to create a RockyLinux image using Packer.
Creating your own VM image can significantly speed up the provisioning of new VMs whenever you need them.
Instead of repeatedly installing the operating system, we'll build VM images for Rocky Linux 9 and Ubuntu 22.04, with our own configuration, this will ensure consistency across our infrastructure.
Part 1 covers building a Rocky Linux 9 image with Packer, while Part 2 focuses on Ubuntu 22.04
2 Prerequisites
- We need a server with KVM installed on it
- ISO file for the image you want to build ( in our case, we are using RockyLinux9.1)
3 The Plan
- We are going to install Packer.
- Write a Packer template using HCL2.
- Write a kickstart configuration file for our RockyLinux system.
- Write a Basic shell script to automate basic configuration for our image.
- Build the final image
4 Installing Packer
Installing Packer is a straightforward process. Simply visit the official documentation, where you can find the appropriate version of Packer for your operating system.
I'm using Arch Linux, and Packer is already available in the Arch repos.
We will install qemu-ui-gtk
as well, this will enable us to easily monitor Packer's installation of the operating system through a graphical user interface.
sudo pacman -S packer qemu-ui-gtk --noconfirm packer --version
1.8.6
5 Write Packer Templates with HCL2
HashiCorp has developed its own configuration language, known as the HashiCorp Configuration Language (HCL), which is utilized in its suite of tools.
HCL is in version 2, and we are going to use it to create our own VM image template.
Each virtualization platform has it's own plugin which is called a builder in Packer's term.
For our Qemu platform, we'll need to use the QEMU builder plugin, which you can find on this page.
We will create a file named main.pkr.hcl
, it will serve as our primary template.
touch main.pkr.hcl
5.1 Installing Packer's builder for QEMU
Add this to main.pkr.hcl
packer { required_plugins { qemu = { version = " >= 1.0.9" source = "github.com/hashicorp/qemu" } } }
And then, run this command
packer init main.pkr.hcl
This command is used to initiate our Packer template, which is basically downloading the necessary builder binaries.
5.2 Packer's main blocks
Packer templates consist of several built-in blocks, each serving as a container for configuration settings.
The most important blocks that we are going to use here are the following:
source
blocks contain configuration for builder plugins, including hardware and resource allocation settings such as type of hardware, RAM and CPU requirements. Each source block has a unique name that can be referenced in thebuild
block.build
blocks can reference one or more source blocks, each of which may have its own provisioners and post-processors blocks.provisioner
blocks contain your provisionners, like shell scripts, Ansible playbook..etc. These blocks are nested inside of abuild
block.
Recently, a kernel panic has occurred while attempting to build a RockyLinux9.X image.
As noted in this issue, adding the -cpu host
flag to the qemuargs
section can resolve this issue.
Alright, add the following to your main.pkr.hcl
file.
# Define QEMU source for rocky source "qemu" "rocky" { vm_name = "rocky-base-image.qcow2" http_directory = "./http" output_directory = "./artifacts" iso_url = "<Put your ISO URL Here" iso_checksum = "sha256:Put ISO checksum Here" format = "qcow2" accelerator = "kvm" net_device = "virtio-net" disk_interface = "virtio" disk_size = "25G" memory = 1024 cpus = 2 headless = false boot_wait = "5s" shutdown_command = "echo admin | sudo -S -E shutdown -P now" ssh_username = "admin" ssh_password = "admin" ssh_timeout = "60m" ssh_handshake_attempts = 2000 # (Bootstrapping with a Kickstart Config File) boot_command = [ "<up><wait><tab><wait> net.ifnames=0 biosdevname=0 inst.text inst.ks=http://{{ .HTTPIP }}:{{ .HTTPPort }}/ks.cfg<enter><wait>"] qemuargs = [ [ "-m", "1024M" ], [ "-smp", "2" ], [ "-cpu", "host" ] ] } # Define build process build { sources = ["source.qemu.rocky"] # (Execute shell scripts) provisioner "shell" { scripts = ["../scripts/configs.sh"] expect_disconnect = true } }
In the build
block, we can reference our source block by it's name source.qemu.rocky
.
We also used a provisioner
block calling the configs.sh
shell script.
6 Kickstart file
Packer initiates its HTTP server at boot time to serve configuration files, we'll create a directory named http
and store our Kickstart configuration file within. Then, we can reference the Kickstart file with the boot command.
mkdir http
Create a kickstart file with the following content, (or any content you want).
# Global settings cdrom # Specify installation media type lang en_US.UTF-8 # Set language and character encoding keyboard us # Set keyboard layout # Network settings network --bootproto=dhcp --device=eth0 --nameserver=10.10.0.2,10.10.0.3 --noipv6 --activate --onboot=on # User settings rootpw --plaintext admin # Set root password user --name=admin --plaintext --password admin # Create a user account timezone Africa/Algeria # Set timezone bootloader --timeout=1 --location=mbr --append="net.ifnames=0 biosdevname=0" # Configure bootloader text # Use text mode install skipx # Do not configure X Window System zerombr # Clear master boot record clearpart --all --initlabel # Clear all existing partitions autopart --nohome --nolvm --noboot # Automatically partition disk # System service settings firewall --enabled # Enable firewall selinux --enforcing # Enable SELinux in enforcing mode firstboot --disabled # Disable Initial Setup on first boot reboot --eject # Reboot system after installation services --enabled="NetworkManager,sshd,chronyd" # Enable specified services # Package installation settings %packages --ignoremissing --excludedocs openssh-clients sudo vim bash-completion selinux-policy-devel wget nfs-utils net-tools tar bzip2 deltarpm rsync dnf-utils redhat-lsb-core elfutils-libelf-devel -fprintd-pam -intltool -iwl*-firmware -microcode_ctl %end # Post installation settings %post # # Sudo configuration echo 'Defaults:admin !requiretty' > /etc/sudoers.d/admin echo '%admin ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers.d/admin chmod 440 /etc/sudoers.d/admin # SSH configuration echo "PubkeyAcceptedKeyTypes=+ssh-rsa" >> /etc/ssh/sshd_config /usr/bin/systemctl enable sshd # Update all packages /usr/bin/yum -y update %end
7 Shell script for Provisionning
The path of the shell script is ../scripts/configs.sh
This is a basic shell script that creates a file called /etc/banner
with a greeting message, enable the Banner
option and restarts the sshd
service to apply the new configuration.
Feel free to add any additional configuration you see fit.
#!/bin/bash # We need to use `tee` command, because redirection ">" doesn't pass sudo privileges cat << EOF | sudo tee /etc/banner ┌────────────────────────┐ │!! Welcome to Homelab !!│ └────────────────────────┘ EOF # Enable `Banner` option sudo sed -i 's/^#Banner none/Banner \/etc\/banner/' /etc/ssh/sshd_config # Restart sshd service sudo systemctl restart sshd
8 Building the image
Let's first validate our template.
packer validate .
The configuration is valid.
And then build.
export PACKER_LOG=1 && packer build .
Packer building process -- output
2023/04/11 20:13:04 [INFO] Packer version: 1.8.6 [go1.20.1 linux amd64] 2023/04/11 20:13:04 Detected xdg config directory from env var: /home/zakaria/.config 2023/04/11 20:13:04 [TRACE] discovering plugins in /usr/bin 2023/04/11 20:13:04 Detected xdg config directory from env var: /home/zakaria/.config 2023/04/11 20:13:04 [TRACE] discovering plugins in /home/zakaria/.config/packer/plugins 2023/04/11 20:13:04 [DEBUG] Discovered plugin: qemu = /home/zakaria/.config/packer/plugins/github.com/hashicorp/qemu/packer-plugin-qemu_v1.0.9_x5.0_linux_amd64 2023/04/11 20:13:04 [INFO] found external [-packer-default-plugin-name-] builders from qemu plugin 2023/04/11 20:13:04 [TRACE] discovering plugins in . 2023/04/11 20:13:04 [INFO] PACKER_CONFIG env var not set; checking the default config file path 2023/04/11 20:13:04 [INFO] PACKER_CONFIG env var set; attempting to open config file: /home/zakaria/.packerconfig 2023/04/11 20:13:04 [WARN] Config file doesn't exist: /home/zakaria/.packerconfig 2023/04/11 20:13:04 Detected xdg config directory from env var: /home/zakaria/.config 2023/04/11 20:13:04 [INFO] Setting cache directory: /home/zakaria/.cache/packer 2023/04/11 20:13:04 Detected xdg config directory from env var: /home/zakaria/.config 2023/04/11 20:13:04 [TRACE] listing potential installations for "github.com/hashicorp/qemu" that match " >= 1.0.9". plugingetter.ListInstallationsOptions{FromFolders:[]string{"/usr/bin/packer", ".", "/home/zakaria/.config/packer/plugins"}, BinaryInstallationOptions:plugingetter.BinaryInstallationOptions{APIVersionMajor:"5", APIVersionMinor:"0", OS:"linux", ARCH:"amd64", Ext:"", Checksummers:[]plugingetter.Checksummer{plugingetter.Checksummer{Type:"sha256", Hash:(*sha256.digest)(0xc000a92200)}}}} 2023/04/11 20:13:04 [TRACE] Found the following "github.com/hashicorp/qemu" installations: [{/home/zakaria/.config/packer/plugins/github.com/hashicorp/qemu/packer-plugin-qemu_v1.0.9_x5.0_linux_amd64 v1.0.9}] 2023/04/11 20:13:04 [INFO] found external [-packer-default-plugin-name-] builders from qemu plugin 2023/04/11 20:13:04 [TRACE] Starting external plugin /home/zakaria/.config/packer/plugins/github.com/hashicorp/qemu/packer-plugin-qemu_v1.0.9_x5.0_linux_amd64 start builder -packer-default-plugin-name- 2023/04/11 20:13:04 Starting plugin: /home/zakaria/.config/packer/plugins/github.com/hashicorp/qemu/packer-plugin-qemu_v1.0.9_x5.0_linux_amd64 []string{"/home/zakaria/.config/packer/plugins/github.com/hashicorp/qemu/packer-plugin-qemu_v1.0.9_x5.0_linux_amd64", "start", "builder", "-packer-default-plugin-name-"} 2023/04/11 20:13:04 Waiting for RPC address for: /home/zakaria/.config/packer/plugins/github.com/hashicorp/qemu/packer-plugin-qemu_v1.0.9_x5.0_linux_amd64 2023/04/11 20:13:04 packer-plugin-qemu_v1.0.9_x5.0_linux_amd64 plugin: 2023/04/11 20:13:04 Plugin address: unix /tmp/packer-plugin309220793 2023/04/11 20:13:04 packer-plugin-qemu_v1.0.9_x5.0_linux_amd64 plugin: 2023/04/11 20:13:04 Waiting for connection... 2023/04/11 20:13:04 Received unix RPC address for /home/zakaria/.config/packer/plugins/github.com/hashicorp/qemu/packer-plugin-qemu_v1.0.9_x5.0_linux_amd64: addr is /tmp/packer-plugin309220793 2023/04/11 20:13:04 packer-plugin-qemu_v1.0.9_x5.0_linux_amd64 plugin: 2023/04/11 20:13:04 Serving a plugin connection... 2023/04/11 20:13:04 packer-plugin-qemu_v1.0.9_x5.0_linux_amd64 plugin: 2023/04/11 20:13:04 [TRACE] starting builder -packer-default-plugin-name- 2023/04/11 20:13:04 packer-plugin-qemu_v1.0.9_x5.0_linux_amd64 plugin: 2023/04/11 20:13:04 use specified accelerator: kvm 2023/04/11 20:13:04 [TRACE] Starting internal plugin packer-provisioner-shell 2023/04/11 20:13:04 Starting plugin: /usr/bin/packer []string{"/usr/bin/packer", "plugin", "packer-provisioner-shell"} 2023/04/11 20:13:04 Waiting for RPC address for: /usr/bin/packer 2023/04/11 20:13:05 packer-provisioner-shell plugin: [INFO] Packer version: 1.8.6 [go1.20.1 linux amd64] 2023/04/11 20:13:05 packer-provisioner-shell plugin: Detected xdg config directory from env var: /home/zakaria/.config 2023/04/11 20:13:05 packer-provisioner-shell plugin: [INFO] PACKER_CONFIG env var not set; checking the default config file path 2023/04/11 20:13:05 packer-provisioner-shell plugin: [INFO] PACKER_CONFIG env var set; attempting to open config file: /home/zakaria/.packerconfig 2023/04/11 20:13:05 packer-provisioner-shell plugin: [WARN] Config file doesn't exist: /home/zakaria/.packerconfig 2023/04/11 20:13:05 packer-provisioner-shell plugin: Detected xdg config directory from env var: /home/zakaria/.config 2023/04/11 20:13:05 packer-provisioner-shell plugin: [INFO] Setting cache directory: /home/zakaria/.cache/packer 2023/04/11 20:13:05 packer-provisioner-shell plugin: args: []string{"packer-provisioner-shell"} 2023/04/11 20:13:05 packer-provisioner-shell plugin: Detected xdg config directory from env var: /home/zakaria/.config 2023/04/11 20:13:05 packer-provisioner-shell plugin: Plugin address: unix /tmp/packer-plugin771583659 2023/04/11 20:13:05 packer-provisioner-shell plugin: Waiting for connection... 2023/04/11 20:13:05 Received unix RPC address for /usr/bin/packer: addr is /tmp/packer-plugin771583659 2023/04/11 20:13:05 packer-provisioner-shell plugin: Serving a plugin connection... 2023/04/11 20:13:05 Build debug mode: false 2023/04/11 20:13:05 Force build: false 2023/04/11 20:13:05 On error: 2023/04/11 20:13:05 Waiting on builds to complete... 2023/04/11 20:13:05 Starting build run: qemu.rocky 2023/04/11 20:13:05 Running builder: 2023/04/11 20:13:05 [INFO] (telemetry) Starting builder qemu.rocky qemu.rocky: output will be in this color. 2023/04/11 20:13:05 packer-plugin-qemu_v1.0.9_x5.0_linux_amd64 plugin: 2023/04/11 20:13:05 Qemu path: /usr/bin/qemu-system-x86_64, Qemu Image path: /usr/bin/qemu-img qemu.rocky: Retrieving ISO qemu.rocky: Trying https://download.rockylinux.org/pub/rocky/9/isos/x86_64/Rocky-9.1-x86_64-minimal.iso 2023/04/11 20:13:05 packer-plugin-qemu_v1.0.9_x5.0_linux_amd64 plugin: 2023/04/11 20:13:05 Acquiring lock for: https://download.rockylinux.org/pub/rocky/9/isos/x86_64/Rocky-9.1-x86_64-minimal.iso?checksum=sha256%3A750c373c3206ae79784e436cc94fffc122296cf1bf8129a427dcd6ba7fac5888 (/home/zakaria/.cache/packer/8f4d630bc056b35e6243168c126713b9dad68ffd.iso.lock) ==> qemu.rocky: Trying https://download.rockylinux.org/pub/rocky/9/isos/x86_64/Rocky-9.1-x86_64-minimal.iso?checksum=sha256%3A750c373c3206ae79784e436cc94fffc122296cf1bf8129a427dcd6ba7fac5888 qemu.rocky: Rocky-9.1-x86_64-minimal.iso 412.40 KiB / 1.48 GiB [>-----------------------------------------------------------------------------------------------------------------------] 0.03% 5h25m58s ... ...
After the ISO file is downloded, an interface will pop up from which you can follow the automated installation.
—
Congratulations, you have built your first image using Packer!