Not today...

comments

Tuto

Kvm Hello World

Tagged kvm , cli

Let’s start a small serie of post on KVM. It is a tool I sometime need to use when Docker is too simple for the problem at hand, or if I really need virtualization (to emulate another arch or a totally different system).

I will start by giving a series of snippets to quick start your KVM usage. I am doing this because I often struggle to retrieve simple information when I want to come back on this kind of technology. Most of the articles are deep diving on a specific option and I always wonder what are all those options passed to the CLI.

For this serie of articles I will use the Qemu CLI since it is the simplest and most used tool out there.

The hard disk drive

Qemu mostly rely on the qcow format for the data persistence. It offers a variety of capabilities I will not dig into in this article. Let’s keep things simple, and create a simple disk for our example:

qemu-img create -f qcow2 hdd.qcow2 10G

This will generate a new file which will grow up to 10G. It will be of qcow2 format as indicated by the option -f qcow2.

Another useful option is to create a new file backed by another one. This new file will only record the difference from the previous one. This is handy when you have finished the installation of the base image and start doing some configuration. It will allow you to go back to the initial state easily without the need of reinstalling everything:

# let's create our base image
qemu-img create -f qcow2 base.qcow2 10G
# we do what has to be done to have a clean install
qemu-system-x86_64 ...
# now we create a new image based on the previous one
qemu-img create -F qcow2 -f qcow2 -b base.qcow2 hdd.qcow2

We will now use hdd.qcow2 for our VM, keeping base.qcow2 safe from subsequent modifications. It will ease our development because we can now come back to a clean install by running the same command (beware it will erase hdd.qcow2):

# reset the hdd.qcow2 image
qemu-img create -F qcow2 -f qcow2 -b base.qcow2 hdd.qcow2

The options

Qemu comes with a lot of options, making it highly configurable. However, it may be overwhelming at first. I will share here the default values I use when starting a VM:

qemu-system-x86_64 -m 4G -smp 4 -cpu host -accel kvm \
	-monitor "unix:monitor.sock,server=on,wait=off" \
	-serial "chardev:serial0" \
	-chardev "socket,id=serial0,path=console.sock,server=on,wait=off" \
	-netdev "user,id=user.1" \
	-device "virtio-net,netdev=user.1" \
	-hda hdd.qcow2 -display none

The options are:

  • -m 4G: amount of RAM, here 4G
  • -smp 4: number of CPUs, here 4
  • -cpu host: CPU model, use the same as the host
  • -accel kvm: virtualization accelerator, here we use KVM
  • -monitor unix:monitor.sock,server=on,wait=off: we want a qemu console monitor, this helps managing the VM and allow the user to send commands (to shutwdown the system for example). This option contains a few instructions, because you will need a two way communication link. For this example we will use a unix socket, but it can also be a regular socket which can be reachable over the network (I will show an example of this a bit later).
    • unix:monitor.sock: This is the kind of socket we want (unix), with the path for the file to create.
    • server=on indicates to qemu that it should be a listening socket. You want to connect to it, not the other way around (not sure this sentence makes any sense).
    • wait=off tells qemu to not block waiting for the client to connect. If this option is set to true, you will have to connect to the socket before qemu can go further (it will not boot the VM until you connect).
  • -serial "chardev:serial0" redirect a serial port to a specified device. Here we use a named device called serial0 and define it as a unix socket with the next option. We could have directly defined it as a unix:console.sock,... and it would have been equivalent. I am just showing a different notation here.
  • -chardev "socket,id=serial0,path=console.sock,server=on,wait=off" describe a character device (this is a synonym for a two way communication link). It will be a unix socket with id serial0, the path for the socket will be console.sock. This device has the same options as the monitoring one.
  • -netdev "user,id=user.1" define a network device with id user.1 in user mode. User mode is the simplest connectivity setup and requires no additional privilege to run. On the downside performance is poor and the guest is not accessible from the host, you can check the official doc for more info. You can still define some port forwarding (we will do this a bit later in the post).
  • -device "virtio-net,netdev=user.1" attach the network device user.1 using the driver virtio-net.
  • -hda hdd.qcow2 use the hdd.qcow2 as a block device (-hda, -hdb, … should map to /dev/vda, /dev/vdb, …).
  • -display none we do not want to attach something here since we already have a unix socket console.sock.

Disclaimer: server,nowait is equivalent to server=on,wait=off and can be found out there on the internet. This has to be confirmed but it should be the legacy way of declaring those options.

Connecting to the control sockets

In this example I defined two unix sockets:

  • console.sock: this is the serial console of the emulated machine, it will behave as a regular shell once you properly connect to it.
  • monitor.sock: this is the Qemu controller interface, you can use it to control the VM (shutdown, inpect, connect stuff).

I wrote a small post explaining the options of the next command, check it out if you need details. Here is an example I am using personally to properly connect:

socat -,rawer,escape=0x1d unix-connect:console.sock

Some variation

If you do not want to use a unix socket you can directly publish the serial console to a local tcp port instead. Here is the proper option:

qemu-system-x86_64 ... \
	-chardev "socket,id=serial0,port=4444,host=localhost,telnet=on,server=on,wait=off" \
	-serial "chardev:serial0"

The connection string will become:

socat -,rawer,escape=0x1d tcp:localhost:4444

The first boot

Unless you are getting an image with a system already installed you will have to first boot from an ISO file. Since we are using an ISO the bootable device will be an emulated CD-ROM drive. You need to pass 2 additional options to Qemu. The boot order with -boot option, here we set it to d for drive. The second option is -cdrom to pass an ISO which will be mounted in the guest VM at /dev/cdrom.

qemu-system-x86_64 ... \
	-boot "d" -cdrom "<your_distro>.iso"

You will need to remove those options once the installation is done, otherwise, you will keep booting using the CD-ROM drive.

Alternatively: you can keep the option in the command line, to always run the same command even after the first installation by prefixing d with the once: keyword. It will become:

qemu-system-x86_64 ... \
	-boot "once:d" -cdrom "<your_distro>.iso"

You will need to be sure that installation is properly done because this will only boot once using the CD-ROM drive.

Publishing services and make them reachable from the host

Last part of this post will be about reaching the guest VM from the host. Given the current networking configuration, it will not be possible to connect through ssh or expose a web service.

The solution will be to perform a port forward from the guest to the host. This can be achieved when defining the network interface by passing an additional hostfwd option. Here is an example using the -netdev configuration I presented with the default options I set in one of the previous section:

qemu-system-x86_64 ... \
	-netdev "user,id=user.1,hostfwd=tcp::2222-:22" \
	-device "virtio-net,netdev=user.1"

This will publish the port 22 from the guest to the port 2222 of the host. You can combine as much hostfwd options as you want to publish all the services you need.

That’s all folks

This conclude this post with the options I would have loved to know when I started using Qemu. I hope it could help someone out there and save a lot of time when dealing with something as complicated as virtualization.