Skip to content

Adding a GUI to your QEMU KVM Guest OS

sakaki edited this page Sep 28, 2019 · 3 revisions

Several methods to run GUI-based applications on your guest OS, to improve productivity!

Prerequisites

This guide assumes that you have already set up a console-based QEMU KVM guest OS and got this to run successfully on your 64-bit Gentoo RPi4 (or RPi3), per these instructions. So, if you haven't yet, please do this first, before proceeding.

As before, we'll be targetting:

You can of course adapt these instructions to your own requirements (most other 64-bit aarch64 OSes can be switched in as the guest, and you could use a non-Gentoo host if you wished, provided it has KVM support in its kernel.

Connecting to the QEMU Guest OS over ssh

To begin, if you're currently running the guest VM, shut it down (you can do so by running sudo shutdown as the ubuntu user). Next, restart it by issuing the following (as your regular user demouser, working on the gentoo-on-rpi-64bit image booted on an RPi3 B or B+) from a console in the /home/demouser/qemu-test directory:

demouser@pi64 ~/qemu-test $ qemu-system-aarch64 -M virt -cpu host \
  -m 384M -smp 2  -nographic \
  -bios QEMU_EFI.fd \
  -cdrom seed-kvm-bionic-01.iso \
  -drive if=none,file=bionic-image-01.img,id=hd0 -device virtio-blk-device,drive=hd0 \
  -device virtio-net-device,netdev=vmnic -netdev user,id=vmnic,hostfwd=tcp::5555-:22  \
  -accel kvm 2>/dev/null

Hint: if this fails with an exception, simply issue pkill qemu from another window and try again.This happens sometimes when booting the UEFI firmware.

This is almost the same as last time, but with two changes:

  • We have allocated more memory (384MiB vs 256MiB) to the guest, a bare minimum to run a GUI; and
  • We have requested QEMU forward port 5555/tcp on the host to port 22/tcp on the guest; this will allow us to connect via ssh.

If running on a 2GiB or 4GiB RPi4, feel free to allocate more than 384MiB of memory in the above command; the emulated system will peform significantly better if you do.

As before, once this is run, the guest will start up (you may need to press Enter a few times at the GRUB boot stage). A minute or so later you should see an Ubuntu login prompt (in the same terminal as you issued the qemu-system-aarch64 command, since -nographic was specified).

Now open a new terminal window on your gentoo-on-rpi-64bit desktop, and (as demouser) issue:

demouser@pi64 ~/qemu-test $ ssh -p 5555 ubuntu@127.0.0.1

Enter the password for the ubuntu user (passw0rd) when prompted, and you should be in, connected independently from the QEMU console link (which is in the window where you issued qemu-system-aarch64, just a moment ago), via localhost port 5555 (which QEMU forwards to port 22 - the standard ssh port - on the guest).

Updating the Guest, and Installing an Example GUI Application

Now you're logged in as ubuntu, take the opportunity to update your system, reboot, then install some exemplar GUI-based software on the guest (an editor). Working within the ssh window (as the ubuntu user), issue:

ubuntu@kvm-bionic:~$ sudo apt-get update
ubuntu@kvm-bionic:~$ sudo apt-get -y upgrade
ubuntu@kvm-bionic:~$ sudo reboot

Once the guest system comes back up again within QEMU, re-establish the ssh/ubuntu terminal connection as before, then issue (from that ssh terminal, as the ubuntu user):

ubuntu@kvm-bionic:~$ sudo apt-get install -y mousepad 

mousepad is a simple editor app which can run on X11. It will pull in a number of additional dependency libraries, as the image starts with no GUI support at all, so please be patient.

Using Guest GUI Applications via X11 Forwarding to the Host

Once the install is complete, you can try out the a first approach to using a GUI from your guest: X11 forwarding onto your host's X server. To do so, open a fresh terminal on your (host) desktop and issue (as demouser):

demouser@pi64 ~/qemu-test $ ssh -f -T ubuntu@127.0.0.1 -p 5555 -Y mousepad /etc/os-release 2>/dev/null

The -f tells ssh to background after asking for a password, and the -T disables pseudo-terminal allocation. The -Y enables (for convenience) trusted X11 forwarding, allowing the guest applications to use your host's X11 server to for graphical I/O.

Enter ubuntu's password (passw0rd) when prompted, and then, if all is well, you should find that an editor window opens on your host desktop, but the underlying mousepad application (and /etc/os-release file it is editing, as you can see from its content) is on the guest system.

This is a highly efficient way to use the guest's GUI applications, since only one X server is running (your host's). However, it has a number of drawbacks. There are security implications to opening up your host's X11 server (see these notes, for example), and while it is possible to work around these (using e.g. Xephyr inside firejail on the host), it's still not an ideal solution for more involved work.

Running a Headless Xfce4 Desktop on the Guest OS

So, while X11 forwarding is handy to keep in mind for quick one-off access to individual apps, let's next put together a full "remote desktop" for our guest.

There are various ways to approach this issue, but since QEMU on aarch64 on the RPi4/3 does not currently support the QXL paravirtual graphics card (which would be the default route on x86_64), we'll instead run a virtual framebuffer (Xvfb)-backed X11 server on the guest, run a lightweight desktop on that (xfce4), and forward the resulting (otherwise invisible) desktop to the host via VNC.

OK, to begin, install the necessary software on your guest. Running as the ubuntu user, within the ssh terminal again, issue:

ubuntu@kvm-bionic:~$ sudo apt-get install -y xfce4 xvfb x11vnc xfce4-taskmanager xfce4-cpugraph-plugin xfce4-terminal links2

I don't recommend using --no-install-recommends with xfce4; you'll end up missing things like dbus-x11 which make it essentially unusable.

and let this run to completion (it will take a while, downloading ~80MiB of archives which take up ~400MiB when installed - the qcow2 disk image has sufficient space though). In the above:

  • xfce4 is a relatively lightweight desktop system for X11 (you could just run a windowing manager, like openbox, but we're trying to push the envelope here ^-^);
  • xvfb is a virtual framebuffer for X11 (a pretend graphics card that renders to a memory buffer);
  • x11vnc is a VNC server for X11 (we'll use this in preference to QEMU's bundled VNC server, which does not always work correctly with aarch64);
  • xfce4-taskmanager is a simple process monitor app for xfce4; installing it is optional for a minimal setup, but recommended;
  • xfce4-cpugraph-plugin is a panel plugin for xfce4 that displays an running CPU load; installing it is optional (but nice to have);
  • xfce4-terminal is a terminal emulator for xfce4; optional (but nicer than xterm!); and
  • links2 is a super-light-weight web browser that can run in text or X11 mode; installing it is optional (but having some sort of web-browsing capability is nice when configuring a system).

Once the above has completed, you can start xfce4 on your guest!

Begin by creating an X11 virtual framebuffer on display :1, and putting it into the background. We'll make this 800 x 600 pixels at 24-bit depth, you can vary this as desired (but don't go crazy, there isn't a lot of memory to play with here, unless you have a 2GiB or 4GiB RPi4 ^-^). Working as the ubuntu user in the ssh terminal, issue:

ubuntu@kvm-bionic:~$ export DISPLAY=:1
ubuntu@kvm-bionic:~$ Xvfb $DISPLAY -screen 0 800x600x24 &

Hint - you can use nohup with these commands if desired. Also, there's for many applications, 16 bit depth is sufficient (rather than 24, as we have used).

Now start the xfce4 desktop itself, for the ubuntu user! Still in the same terminal, issue:

ubuntu@kvm-bionic:~$ startxfce4 &>/dev/null&

Apart from a bit of CPU activity, nothing will apparently happen, but that's because the desktop is being rendered to our new virtual framebuffer only (this is the same trick sometimes used to run GUIs on headless cloud VM images etc.).

Next, start up the x11vnc server, serving the same screen and display. Still as the ubuntu user, in the same console window, issue:

ubuntu@kvm-bionic:~$ x11vnc -display $DISPLAY -bg -nopw -listen localhost -xkb &>/dev/null

The -bg instructs the server to background itself after setup; the -nopw disables the "no password" warning; the -listen localhost directive instructs the server to only accept connections on (guest) 127.0.0.1; and -xkb uses the XKEYBOARD extension, hopefully avoiding most keymapping problems.

With that done, you now have an xfce4 desktop running over an X11 server on the guest, rendering to a virtual Xvfb framebuffer, and available for remote viewing via VNC on (guest) port 127.0.0.1:5900/tcp (the port numbering is by convention, you can specify a different one if you like).

Preparing the Host to Run a VNC Client

We're almost there now, but two problems remain.

The first issue is that the gentoo-on-rpi-64bit image does not ship with a VNC client pre-installed. Fortunately, net-misc/tigervnc is available on the binhost (as a binary package). To install it, issue (as the demouser user on a terminal in the host desktop):

demouser@pi64 ~/qemu-test $ sudo emerge --verbose --noreplace net-misc/tigervnc

This shouldn't take long. The program it installs may be launched from the commandline as vncviewer (and will also appear in the desktop ApplicationsInternet menu, as TigerVNC Viewer).

The second issue is that the the guest localhost port 5900 isn't visible on the host system by default. There are various ways around this, but to avoid having to set up multiple networking cards on QEMU, here we'll just take advantage of another neat / scary ssh feature, port forwarding. Using this, we can request ssh transparently forward traffic from a given port on the host system to another on the remote side (including replies).

To do so here, working in the same host terminal, and still as demouser, issue:

demouser@pi64 ~/qemu-test $ ssh -f -N -T -L 5910:localhost:5900 ubuntu@127.0.0.1 -p 5555

The -f and -T options we've seen before; the -N option tells ssh there is no command payload. The -L component sets up the port forwarding, from 5910 on the host to 5900 on the guest.

Enter ubuntu's password (passw0rd) when prompted. Nothing will appear to happen, but the tunnel for VNC traffic is now in place between host and guest.

Connecting to the Guest Desktop from the Host, using VNC

All that remains is to open a viewer! Still working as demouser, in the same host terminal, issue:

demouser@pi64 ~/qemu-test $ vncviewer 127.0.0.1:5910 &>/dev/null&

And with luck (and OOM-killer permitting ^-^) a window should open on the host, showing the guest desktop. Here's a screenshot of one of my gentoo-on-rpi-64bit RPi3's, on which the above steps have been run:

https://raw.githubusercontent.com/sakaki-/resources/master/raspberrypi/pi3/kvm-bionic-vnc.png

Note that for this setup, I changed the icons (using the Xfce ApplicationsSettingsAppearance tool) after launch to the Ubuntu-Mono-Light set, downloaded (using the links browser!) an Ubuntu 18.04 desktop png, for appearance sake, and installed the cpugraph plugin. But everything else is vanilla.

If you look at the above screenshot, you'll notice that:

  • There are four different connections to the guest in use: the bottom right QEMU terminal (which I have here rotated into QEMU monitor mode using Ctrla followed by c; do this again and you'd get a synthetic serial console login prompt); the ssh terminal connection (top right, here already logged in as the ubuntu user); the X-forwarded mousepad editor (one from bottom on the right) and the VNC desktop itself (the large window on the left).
  • The host and guest are running different kernels - this is not simply a chroot. Compare the output from uname -a in the Gentoo terminal (one from top on the right) and in the terminal window in the VNC guest desktop (and also in the ssh terminal).
  • You can't see it here, but they're running different init systems too: OpenRC on the host, and systemd on the guest.
  • System load is low (see the cpu graph plugins, in the top horizontal panel, on host and guest). KVM virtualization imposes very low overhead, as most code runs natively on both guest and host.
  • The guest (as we specified when invoking qemu-system-aarch64) has only 2 cpu cores available, whereas the host has 4 (see the same graph plugins). You can do fun things with cpu affinity if you really want to minimize the latter stepping on the toes of the former, but I haven't for this simple example.
  • You can launch apps etc. on the guest as you wish - it is a full xfce4 system. So in the above, I've opened the thunar file browser and an xfce4-terminal.

Have fun ^-^

Some Closing Thoughts

There is the small question of why any sane person would want to do any of this in the first place, of course ^-^. It is perfectly possible to chroot most guest systems (even 32-bit guest on a 64-bit host), provided you are comfortable sharing a kernel, and there aren't any init system-expectation mismatches. KVM does provide pretty strong isolation I suppose, so if you had a server component you wanted to absolutely lock down (tor, for example) you could put it in a firejail chroot on a hardened guest OS, and then pipe traffic to it from the host... mostly though, on such a resource-limited system as the RPi3, it's for fun ^-^

On the higher-memory (2 and 4 GiB) RPi4 models, however, running a guest OS via KVM can make good sense.

One other point: forcing the X11 and VNC interaction over an ssh tunnel locally will involve encryption / decryption overhead. If you wanted to do the above in a production system, it'd be better to set up another virtual network card on QEMU shared by the host and guest, and vector traffic over that. Or use a paravirtualized graphics card (but unfortunately these don't seem to be available for vc4 / aarch64 at present, although I'd be happy to be proven wrong on that point).

Clone this wiki locally