Using Docker for a portable QEMU environment
This article explores running QEMU in a Docker container to easily build, provision, maintain and run the necessary packages for QEMU development.
230708 Update
I have added support for virtiofsd shared files from the host,
mapped as a docker volume and then mounted in the QEMU guest. It works well but there were several small issues to overcome:
- understanding how to map `/dev/shm` through docker and then into QEMU,
- configuring the virtio deamon in docker,
- configuring QEMU to access the virtio deamon in docker.
It seems to work reliably now. See this Docker QEMU github repo and tag v1.1
Abstract
I work on QEMU a good bit, mostly full system emulation but, at times, also user space (for example, cross-compiling). Why QEMU? As I have said in previous articles, the big advantages of using QEMU over physical hardware (including prototype boards) are:
- a guest OS can be spun-up and provisioned quickly, allowing for rapid prototyping,
- a lot of runtime and state information can be gleaned from the QEMU Monitor,
- there are a number of debugging and analysis options including GDB.
See my past articles for some uses of QEMU:
However, QEMU has evolved quickly and now requires specific versions of the meson build system, python-3 and python-3 packages along with a compiler toolchain and linker shared objects. The problem I increasingly encounter is keeping all the dependency packages consistent and up-to-date without breaking utilities on my host system — a Dell Intel laptop running Ubuntu Linux.
The solution I have come to embrace is to create a docker container with all the packages I need to manage QEMU. Included in these are those packages necessary to build and install QEMU from source. I could clone the QEMU git repo in the container but do this on the host and map all the directories I need as docker volumes. I also map the host KVM device for hypervisor access. This provides me all the capabilititie of QEMU/KVM for speed. Alternatively, I can disable KVM hypervisor and use the TCG for kernel debugging or heterogeneous CPU architectures.
The list of advantages of the docker QEMU framework over a host running QEMU include:
- the docker container has one mission: QEMU support. All packages are groomed to that mission,
- QEMU and a guest OS can be created, updated, run and destroyed fairly quickly,
- the docker container can be easily modified and re-created to incorporate new QEMU and guest OS features.
Essentially I am working in a QEMU-specific sandbox. If something goes wrong (e.g. OS panic or image corruption) I can quickly recover. In the worst case, I destroy the docker image and rebuild everything from scratch. This process takes roughly fifteen minutes, mostly watching docker image creation or the QEMU source build.
Functional Overview
This document and github repo demonstrate booting, provisioning and managing:
- a docker container running Debian Linux with all the necessary support packages for QEMU,
- a QEMU executable build from source and install inside the container,
- a Debian Linux Cloud cloud image downloaded and running in the QEMU full emulator. This could be any
rootfs
including MS Windows but for this demonstration I chose a recent Debian cloud image for simplicity.
There are several operational features to note:
- On first boot, the Debian Linux Cloud image is provisioned with Cloud Init yaml files combined into an image file. This is a well documented and simpler alternative to the Debian preseed framework.
- QEMU can be run using the KVM hypervisor (also known as a VM accelerator) or the TCG internal tiny code generator.
QEMU can launch a guest OS using all command line arguments or with a QEMU device configuration file
. There is very little documentation on the QEMU device configure files other than some examples in the QEMU source tree and the source code itself (start withqemu-options.def
)
File Descriptions
All files are in this Docker QEMU github repo.
README.md
: this filebashlib.sh
: library of bash functions for managing the docker container and qemu image. This should be used as an example of the necessary steps for each component: docker, QEMU and the guest OS image.env_vars
: bash source file to set environment variables used inbashlib.sh
qemu.Dockerfile
: dockerfile to create the docker imagebashrc.docker
: custom.bashrc
copied to docker image (seeqemu.Dockerfile
)metadata.yaml
: Cloud Init system file defining the guest id and hostnameuser-data.yaml
: Cloud Init user file defining user groups, users, debian packages and ancillary initialization commandsid_qemu_dummy.key
: example RSA PKE file for ssh access to QEMU guest VM
Additionally some files and directories are dynamically created:
- The QEMU executable,
$Q_P
, is generated bybashlib.sh:q_p_bld
. Theqemu.git
repo is assumed to be cloned and a the desired tag is branched on the host (outside the scope of this doc). Theqemu.git
repo is mounted as docker volume bybashlib.sh:docker_r
. - The desired debian generic cloud image will be pulled from the official debian site by
bashlib.sh:get_debian_cloud
and converted to a local copy. - See
bashlib.sh
for the use and location of progress log files includingLOG.CONFIG.QEMU
and$Q_TOP/build/meson-logs/meson-log.txt
Functional Details
In bashlib.sh:bld_all
there is a rough template for the necessary steps to create a docker/QEMU/Debian guest OS platform. It will need to be modified depending on your host platform, capabilities, and directory locations.
The template assumes this Docker QEMU github repo has been man:git-clone
on the host and a recent qemu source tree repo has been man:git-clone
locally on the host, branched and cleaned of prior builds. Since the QEMU build steps are performed in the container, any build artifacts should be removed first.
Briefly, here are the steps using a man:bash
shell for a first time run
Host
Create a docker image and then launch a running container using the image. See env_vars
, qemu.Dockerfile
and bashrc.docker
to customize the docker container.
Docker Container
First test the docker container for the necessary packages (see bashlib.sh:docker_check
).
A docker container can access the host X11 window manager for creating windows in the container but I have found this to be more trouble than worth. However, I enjoy having multiple shells connected to the container (see bashlib.sh:docker_conn_shell
.)
The first major step in the container is to configure and build QEMU in the mapped docker volume. See bashlib.sh:q_p_bld
for configuring and making the QEMU executable. Check that QEMU is built correctly using bashlib.sh:q_p_bld_check
. This function has a command line option CMD_LONG
to run the QEMU test scripts which takes about 15 minutes to run.
If the QEMU executable is fully functional then use bashlib.sh:q_p_install
to install the components in the container. I used the/usr/local
prefix to install under (which requires sudo
).
QEMU Guest OS
One of the simplest guest OS images is a Debian Linux Cloud image. See bashlib.sh:qemu_get_debian_cloud
for steps to create a run image, and build the seed.img
provisioning file from the Cloud Init yaml files.
Now we are ready to launch the Debian guest OS in the QEMU emulator. I demonstate two similar ways to do this:
bashlib.sh:qemu_run_args
: all the configuration options are passed on the commandline. This is the most common way to start QEMU.bashlib.sh:qemu_run_cfg
: most of the configuration is defined in a QEMU device configuration file and read using the-readconfig
option. This is the "new" way but is not well documented.
These functions work identically but I like the device configuration file because it is easier to maintain and can be commented.
Both launch functions currently enable KVM for the performance benefit. To disable KVM, and use the default TCG just switch the kvm
keyword to tcg
.
QEMU emulator
Once QEMU is launched it will “boot” the Debian Linux Cloud guest OS.
On first boot the Debian cloud image will read the Cloud Init provisioning information built in seed.img
. Once provisioned the seed.img
drive is ignored. The seed.img
drive can safely be removed in subsequent runs but for simplicity I keep it.
When the Debian login prompt comes up, login with the user creds in user-data.yaml
. Use bashlib.sh:qemu_guest_check
to verify some basic functionality.
From there one possible step is entering the QEMU Monitor and query information about the block devices(qemu) info block
. You should see two block devices: the guest OS formatted as qcow2
device and the seed.img
formatted as a raw
device.
The guest OS has an SSH server and a pubkey authentication configured in the user-data.yaml
ssh_authorized_keys
section for dave
. Guest OS ssh access cannot be performed from the host yet, only the docker container.
Summary
This document describes a safe and clean methodology to set up and run the QEMU emulator with an example guest OS. It can serve as a template for building more complex QEMU frameworks using heterogenous hardware or prototype development on the guest OS.
The features that I will investigate in the future are:
- SSH connection from the host directly into the QEMU guest OS. Currently, SSH access must be from the docker container to the QEMU guest.
Originally published at https://github.com.