Linux virtual machine with KVM from the command line

If we want to raise virtual machines in a Linux environment that does not have a graphical environment, we can raise virtual machines using the command line with a XML template.

This article explains how the deployment performed with Ansible-libvirt at https://www.disasterproject.com/index.php/en/2019/08/kvm-linux-command-line/ works internally

Install Qemu-KVM and Libvirt

Linux virtual machine with KVM from the command line 1

First: we must to install libvirt and Qemu-KVM. In Ubuntu / Debian is installed with:

$ sudo apt-get install -y libvirt-daemon-system python-libvirt python-lxml

And in CentOS / Redhat with:

$ sudo yum install -y libvirt-daemon-kvm python-lxml

To launch the service we must do: $ sudo systemctl enable libvirtd && sudo systemctl start libvirtd

Configure a network template

Libvirt provides us with a powerful tool for managing virtual machines called ‘virsh’, which we must use to be able to manage KVM virtual machines from the command line.

For a virtual machine we mainly need three elements, the first is a network configuration that provides IP to virtual machines via DHCP. To do this libvirt needs XML template like the next template (which we will designate “net.xml”):

<network>
  <name>NETWORK_NAME</name>
  <forward mode='nat'>
    <nat>
      <port start='1' end='65535'/>
    </nat>
  </forward>
  <bridge name='BRIDGE_NAME' stp='on' delay='0'/>
  <ip address='IP_HOST' netmask='NETWORK_MASK'>
    <dhcp>
      <range start='BEGIN_DHCP_RANGE' end='END_DHCP_RANGE'/>
    </dhcp>
  </ip>
</network>

Whose main elements are:

  • NETWORK_NAME: Descriptive name that we are going to use to designate the network, for example, “test_net” or “production_net”.
  • BRIDGE_NAME: Each network creates an interface on the host server that will serve as gateway of the input/output packets of that previous network to the outside. Here we assign a descriptive name that let as identify the interface.
  • IP_HOST: The IP that such interface will have on the host server and that will be the gateway of the virtual machines.
  • NETWORK_MASK: Depends on the network, usually for testing must be use a class C (255.255.255.0)
  • BEGIN_DHCP_RANGE: To assign IPs to virtual machines using libvirt, there are an internal DHCP server (dnsmasq based), here we define the first IP of the range that we can serve to virtual machines.
  • END_DHCP_RANGE: And here we define the last IP that virtual machines can obtain.

Preparing the operating system image

The second element is the virtual machine image, this image can be created or downloaded, the second is recommended to reduce the deployment time. An image source for virtual machines with KVM / libvirt is Vagrant (https://app.vagrantup.com/boxes/search?provider=libvirt), to obtain an image of the virtual machine that interests us, we must download from https://app.vagrantup.com/APP_NAME/boxes/APP_TAG/versions/APP_VERSION/providers/libvirt.box where APP_NAME is the name of the application we want to download (e.g. debian), APP_TAG is the distribution of this application (e.g. stretch64) and finally APP_VERSION is the version of the application (e.g. 9.9.0). Even so, there are specific image repositories such as CentOs that are at http://cloud.centos.org/centos/7/vagrant/x86_64/images.

Once the libvirt.box file is obtained, it must be decompressed with tar -zcf libvirt.box, which generates three files, one of which is the image of the virtual machine (box.img) that we will rename to NAME_DESCRIPTIVE.qcow2 and copy it to the standard folder for libvirt virtual machine images (/var/lib/libvirt/images) and we will give the libvirt user permissions so that he can manage that image (chown libvirt /var/lib/libvirt/images/NAME_DESCRIPTIVE.qcow2)

It is important to know that if we want to access the virtual machine we will need a password that we can download with wget -O insecure_private_key https://raw.githubusercontent.com/hashicorp/vagrant/master/keys/vagrant, and we must apply permissions for access only with the current user ($ chmod 600 insecure_private_key).

Virtual Machine Template

The third element to create the virtual machine once the last two are created is a virtual machine template, which for a recent libvirt release will have the following form:

<domain type='kvm'>
  <name>VM_NAME</name>
  <memory unit='MB'>VM_MEMORY</memory>
  <vcpu>CPU_NUMBER</vcpu>
  <os>
    <type>hvm</type>
    <bootmenu enable='no'/>
    <boot dev='hd'/>
  </os>
  <features>
    <acpi/>
    <apic/>
  </features>
  <clock offset='utc'/>
  <on_poweroff>destroy</on_poweroff>
  <on_reboot>restart</on_reboot>
  <on_crash>destroy</on_crash>
  <devices>
    <disk type='file' device='disk'>
      <driver name='qemu' type='qcow2'/>
      <source file='IMAGE_STORAGE_PATH/IMAGE_NAME.qcow2'/>
      <target dev='vda' bus='virtio'/>
    </disk>
    <interface type='network'>
      <source network='NETWORK_NAME'/>
      <model type='virtio'/>
    </interface>
    <console type='pty'>
      <target type='serial' port='0'/>
    </console>
    <input type='mouse' bus='ps2'>
      <alias name='input0'/>
    </input>
    <input type='keyboard' bus='ps2'>
      <alias name='input1'/>
    </input>
    <graphics type='spice' port='5900' autoport='yes' listen='127.0.0.1'>
      <listen type='address' address='127.0.0.1'/>
      <image compression='off'/>
    </graphics>
    <video>
      <model type='cirrus' vram='16384' heads='1' primary='yes'/>
      <alias name='video0'/>
    </video>
    <memballoon model='virtio'/>
  </devices>
</domain>

Whose elements are:

  • VM_NAME: The descriptive name that will allow us to identify the virtual machine
  • VM_MEMORY: How much memory in MB we allocate to the virtual machine
  • CPU_NUMBER: How many virtual CPUs will the virtual machine have
  • IMAGE_STORAGE_PATH: The path where we have saved the virtual machine, the standard is /var/lib/libvirt/images
  • IMAGE_NAME: A descriptive name we have given to the image in the previous stage
  • NETWORK_NAME: A descriptive name have we given to the network that will manage the virtual machines.

Deploying Virtual Machines

Once the templates are created and the image is deployed, we execute:

# sudo virsh net-create plantilla_red.xml
# sudo virsh create plantilla_máquina_virtual.xml

Finally we must check that it works, first we identifyif the virtual machines are running with # sudo virsh list, second we need to get the ip in use with # sudo virsh net-dhcp-leases NETWORK_NAME and finally we access the assigned IP with # ssh -i insecure_private_key vagrant @IP

Raise a KVM Linux virtual machine from command line

If we want to raise virtual machines in a KVM Linux environment that doesn’t need graphical environment, we can raise virtual machines from the command line using an XML template.

Install Qemu and Libvirt

First we must is install libvirt and Qemu-KVM, in Ubuntu / Debian is installed with:

$ sudo apt-get install -y libvirt-daemon-system python-libvirt python-lxml

In CentOS/Redhat with:

$ sudo yum install -y libvirt-daemon-kvm python-lxml

To start the service we will make $ sudo systemctl enable libvirtd && sudo systemctl start libvirtd

Configuring a network template

Libvirt give us with a powerful tool for managing virtual machines called ‘virtsh’, which we must use to manage KVM virtual machines from the command line.

To deploy a virtual machine we need three elements: the first is a network configuration that among other things provides IP virtual machines via DHCP. To do this, libvirt needs an XML template like the following (which we will call net.xml):

<network>
  <name>NETWORK_NAME</name>
  <forward mode='nat'>
    <nat>
      <port start='1' end='65535'/>
    </nat>
  </forward>
  <bridge name='BRIDGE_NAME' stp='on' delay='0'/>
  <ip address='IP_HOST' netmask='NETMASK'>
    <dhcp>
      <range start='BEGIN_DHCP_RANGE' end='END_DHCP_RANGE'/>
    </dhcp>
  </ip>
</network>

Whose main elements are:

  • NETWORK_NAME: The descriptive name that we are going to give to the virtual network, for example test_net or demo_net.
  • BRIDGE_NAME: Each network creates an interface on the host server that will serve as gateway of the packets of that network to outside. We need to assign a descriptive name for that.
  • IP_HOST: The IP that the bridge interface will have on the host server and that will be the gateway of the virtual machines
  • NETMASK: Depends on the network, usually for testing give a class C (255.255.255.0)
  • BEGIN_DHCP_RANGE: To assign IPs to virtual machines, libvirt use an internal DHCP server (based on dnsmasq), here is the element where we define the first IP that we can serve to virtual machines.
  • END_DHCP_RANGE: And here we define the last IP that we can use in virtual machines.

Preparing the Operating System Image

The second element is the image of the virtual machine, this image can be created or downloaded, and the second option is recommended to reduce the deployment time.

An image source for virtual machines with KVM / libvirt is Vagrant (https://app.vagrantup.com/boxes/search?provider=libvirt), to obtain an image of the virtual machine that interests us, we can get it from https://app.vagrantup.com/APP_NAME/boxes/ETIQUETA_APP/versions/VERSION_APP/providers/libvirt.box where APP_NAME is the name of the operating system we want to use (eg debian), APP_TAG is the distribution of this application (eg stretch64) and finally APP_VERSION is the version (eg 9.9.0). There are specific image repositories such as CentOs that are out of vagrant repositories, CentOS can be downloaded at http://cloud.centos.org/centos/7/vagrant/x86_64/images.

Once the libvirt.box file is obtained, it is decompressed with tar -zcf libvirt.box, which generates three files, one of which is the image of the virtual machine (box.img) that we will rename to DESCRIPTIVE_NAME.qcow2 and copy it to the standard folder for libvirt virtual machine images (/var/lib/libvirt/images), and give the libvirt user permissions to manage that image (chown libvirt /var/lib/libvirt/images/DESCRIPTIVE_NAME.qcow2)

Important: if we want to access the virtual machine we will need a password that we can download with wget -O insecure_private_key https://raw.githubusercontent.com/hashicorp/vagrant/master/keys/vagrant, and apply permissions for the access only for the current user ($ chmod 600 insecure_private_key)

Virtual machine template

The third element to create the virtual machine once the network is created is a virtual machine template, for a recent operating system will have a struct like this:

<domain type='kvm'>
  <name>VM_NAME</name>
  <memory unit='MB'>VM_MEMORY</memory>
  <vcpu>NUMBER_OF_CPUS</vcpu>
  <os>
    <type>hvm</type>
    <bootmenu enable='no'/>
    <boot dev='hd'/>
  </os>
  <features>
    <acpi/>
    <apic/>
  </features>
  <clock offset='utc'/>
  <on_poweroff>destroy</on_poweroff>
  <on_reboot>restart</on_reboot>
  <on_crash>destroy</on_crash>
  <devices>
    <disk type='file' device='disk'>
      <driver name='qemu' type='qcow2'/>
      <source file='IMAGES_STORAGE_PATH/IMAGE_NAME.qcow2'/>
      <target dev='vda' bus='virtio'/>
    </disk>
    <interface type='network'>
      <source network='NETWORK_NAME'/>
      <model type='virtio'/>
    </interface>
    <console type='pty'>
      <target type='serial' port='0'/>
    </console>
    <input type='mouse' bus='ps2'>
      <alias name='input0'/>
    </input>
    <input type='keyboard' bus='ps2'>
      <alias name='input1'/>
    </input>
    <graphics type='spice' port='5900' autoport='yes' listen='127.0.0.1'>
      <listen type='address' address='127.0.0.1'/>
      <image compression='off'/>
    </graphics>
    <video>
      <model type='cirrus' vram='16384' heads='1' primary='yes'/>
      <alias name='video0'/>
    </video>
    <memballoon model='virtio'/>
  </devices>
</domain>

Whose elements are:

  • VM_NAME: Descriptive name that will allow us to identify the virtual machine
  • VM_MEMORY: How much memory in MB we allocate to the virtual machine
  • NUMBER_OF_CPUS: How many virtual CPUs will have the virtual machine
  • IMAGE_STORAGE_PATH: The path where we saved the virtual machine image, the standard is /var/lib/libvirt/images
  • IMAGE_NAME: Descriptive name we have given to the image
  • NETWORK_NAME: Descriptive name we have given to the network used by virtual machines

Virtual machine deployment

Once the templates are created and the image is deployed, we must execute:

# sudo virsh net-create network_template.xml
# sudo virsh create virtual_machine_templat.xml

Finally we must check that it works, first we identify if it is started with # sudo virsh list, second we get the ip in use with # sudo virsh net-dhcp-leases NETWORK_NAME and finally we access the assigned IP with # ssh -i insecure_private_key vagrant@IP

KVM, Ansible and how to deploy a test environment

In local development environments there is always a need for simulation of more powerful environments, as usually happens in the making of demos.

For this we’ll always follow the “KISS” philosophy (keep it simple stupid!) And we will use those services so that our Linux requires the least use of possible resources. We’ll need two tools to simplify the work that are Ansible for deployment and KVM as a hypervisor.

Images for the test environment

The first step is to raise a system that provides us with images in the simplest way possible. We’ll find that Vagrant as a wonderful source of images. We have two ways to use it:

  1. Download from https://www.vagrantup.com/downloads.html and install (with sudo dpkg -i vagrant_VERSION_x86_64.deb in Debian / Ubuntu environments or with sudo rpm -i vagrant_VERSION_x86_64.rpm in RHEL / Centos environments), to get an image as small as possible we will make use of a debian 9.9.0 with the following command:
    $ vagrant box add --provider libvirt debian/stretch64
    ==> box: Loading metadata for box 'debian/stretch64'
    box: URL: https://vagrantcloud.com/debian/stretch64
    ==> box: Adding box 'debian/stretch64' (v9.9.0) for provider: libvirt
    box: Downloading: https://vagrantcloud.com/debian/boxes/stretch64/versions/9.9.0/providers/libvirt.box
    box: Download redirected to host: vagrantcloud-files-production.s3.amazonaws.com
    ==> box: Successfully added box 'debian/stretch64' (v9.9.0) for 'libvirt'!
    The downloaded image will be in ~/.vagrant.d/boxes/debian-VAGRANTSLASH-stretch64/9.9.0/libvirt, in the form of three files, being the one that interests us: box.img which is an image with QCOW format.

  2. Directly download the images that we will use, for example a Centos image: http://cloud.centos.org/centos/7/vagrant/x86_64/images/CentOS-7.Libvirt.box and a Debian image: https://app.vagrantup.com/debian/boxes/stretch64/versions/9.9.0/providers/libvirt.box
    To make the deployment easier, Ansible has been configured to download automatically and save the image in /root/.images and use it directly without need to do anything else.

The next thing we need is to download the Ansible tasks that will allow us to launch our test environment, the “package” is formed by a file that will be really important called “inventory.yml” where we will really define how our demo will be, it is formed by a “creation” and a “destruction” file of the virtualized environment. The rest of the files are variables and functions (tasks) that will execute when is necessary to raise our environment. We proceed to download the environment with:

$ git clone https://github.com/aescanero/disasterproject
$ cd disasterproject/ansible

Inside the “ansible” directory we’ll found a “files” directory that has Vagrant’s insecure private key that will help us to access each of the machines that we will deploy. This key is obtained from https://raw.githubusercontent.com/hashicorp/vagrant/master/keys/vagrant, we proceed to change the permissions so that the key is accepted by SSH:

$ chmod 600 files/insecure_private_key

Download Ansible

In order to perform Ansible tasks, we must obtain and install Ansible following the instructions in the Ansible installation guide (https://docs.ansible.com/ansible/latest/installation_guide/intro_installation.html), for example for Debian you have to follow the next instructions:

$ sudo sh -c 'echo "deb http://ppa.launchpad.net/ansible/ansible/ubuntu trusty main" >>/etc/apt/sources.list'
$ sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 93C4A3FD7BB9C367
$ sudo apt-get update && sudo apt-get install -y ansible 

Installing libvirt and KVM

Next is to install the rest of the packages that we will need, in debian they are:

$ sudo apt-get install -y libvirt-daemon-system python-libvirt python-lxml

In CentOs are:

$ sudo yum install -y libvirt-daemon-kvm python-lxml

To start the service we’ll execute $ sudo systemctl enable libvirtd && sudo systemctl start libvirtd

Create an inventory for the environment

In the next step we will edit the “inventory.yml” file that should have a format like that:

all:
  children:
    vms:
      hosts:
        debian:
          memory: 1024
          vcpus: 1
          vm_ip: "192.168.8.2"
          linux_flavor: "debian"
      vars:
        domain: disasterproject.com
        network: "192.168.8"

We can see the definition of the machine/s (name: debian, memory in MB, number of vcpus, ip, and linux flavor – for now only debian or centos -) and a series of global values, as a domain , the network without the last octet (the network will be a class C, typical /24 mask, sufficient for demo enviroment), where {{network}}. 1 is the IP running as a gateway to all the virtual machines that we’ll deploy, the IPs of the virtual machines will be configured via DHCP and must belong to that network. Both {{network}}. 1 and the range {{network}}.240/28 are reserved and can’t be used for virtual machines.

Deploy the virtual machines

The next step is to launch the MVs with Ansible for this we execute:

$ ansible-playbook -i inventory.yml create.yml --ask-become-pass

We can see all the steps to deploy the virtual machine:

When the execution is finished correctly, the virtual machine/s are started and ready to use, in our example we can login to the MV with the IP 192.168.8.2 with:

$ ssh -i files/insecure_private_key vagrant@192.168.8.2

The vagrant user has sudo so we can manage the virtual machine without problems.

Comparing KVM and VirtualBox. Why KVM?

Finally, some conclusions over the performance of KVM and VIrtualBox hypervisors, although it is true that the VirtualBox console is efficient, the performance of this virtualization solution suffers when there is a lot of access to disk and / or CPU, although in the latest version (6. x) has clearly improved this, remains behind KVM and is not recommended for development or demos. More information on openbenchmarking: https://openbenchmarking.org/result/1812203-PTS-VIRTUALI66

Here are some results from iozone (/ usr / bin / iozone -s24m -r64 -i 0 -i 1 – + u -t 1) which indicate us improved performance in 3 of the 4 tests performed over a VM in KVM enviroment and VirtualBox enviroment:

KVMVirtualBox
Avg throughput per process Avg throughput per process
Throughput for 1 initial writers
922105.38 kB/sec
Throughput for 1 initial writers
712577.69 kB/sec
Best
22.7%
Throughput for 1 rewriters
1097535.38 kB/sec
Throughput for 1 rewriters
1244981.12 kB/sec
Worse
13.4%
Throughput for 1 readers
2971712.50 kB/sec
Throughput for 1 readers
1833079.75 kB/sec
Best
38.1%
Throughput for 1 re-readers
2219869.75 kB/sec
Throughput for 1 re-readers
559970.25 kB/sec
Best
74.7%
Scroll to top