virt-deploy is a script that to deploy a set of virtual machines in libvirt and create the network topology with a single config file.
It can create a docker container for you and deploy the vms and the networks inside the container, which is useful to isolate a test setup for instance. The config of docker is inspired by libvirtd-in-docker.
-
libvirt installed and running correctly on local host
-
docker installed and running correctly on local host (optional)
-
already created bootable qcow2 vm images. The simplest way to create such image is via the usual virt-install script.
-
edit "qdeploy.conf" to describe the vms and the networks to deploy
The easiest way is to download a standalone binary here
to install is to use the provided Makefile to generate a standalone binary. The requirements are python 2.7, pip,virtualenv and make
To generate the binary:
$ make
The result binary "./dist/virt-deploy" can now be copied in any convenient place, e.g. ~/bin or /usr/local/bin
Check:
$ ./dist/virt-deploy
Warning: qdeploy.conf is missing in current directory
usage: virt-deploy [-h]
{dump,init,env-start,env-stop,vm-start,vm-install,
vm-stop,net-start,net-stop,net-list,vm-list,virsh,
virtmgr,sh,start,stop}
...
virt-deploy: error: too few arguments
. use_venv.sh
This will enter the virtualenv and define a bash alias 'virt-deploy' that executes the code directly in source tree
It is recommanded to get familiar with the script running directly in the host before trying to deploy in a docker env.
the virt-deploy script must be started in the directory containing both the qcow2 images and the qdeploy.conf file
In this example, we use virt-deploy to start a vm and 2 networks (aka bridges) directly in the local host.
The first bridge:
- has an ip address 192.168.100.1
- starts a dhcp server (dnsmasq) to server address in range [100-254]
- sets iptable rules to masquerade outgoing traffic
The vm has 2Gb ram, 2cpus and a vnc display
-
The example below assumes you already have created a debian image 'mydeb.qcow2' (eg using virt-install)
-
libvirt has been installed and running correctly in the local host (check systemctl status libvirtd)
paste the content below in 'qdeploy.conf' (see section below for an explanation of the syntax).
The noautoconsole is important. It tells the vm to start without showing the vm display. The vm display can later be obtained using the virt-manager.
The reason why 'noautoconsole' is so important is because of a bug/feature: if you don't specify 'noautoconsole', virt-deploy will wait for the vm terminates, which is a problem if you start multiple vms at once.
network "lan1"{
bridge.name="lan1"
forward.mode=nat
ip.address=192.168.100.1
ip.netmask=255.255.255.0
ip.dhcp.range {start=192.168.100.100 end=192.168.100.254}
}
network "lan2"{
bridge.name="lan2"
}
vm "mydeb" {
ram 2048;
vcpus 2;
graphics vnc;
noautoconsole;
network {network=lan1 model=e1000}
network {network=lan2 model=e1000}
}
$ virt-deploy init
This creates a hidden '.qdeploy' directory (which is mostly needed if using docker)
Check:
ls -l .qdeploy/
$ virt-deploy net-start -a
Check (1):
$ virsh net-list
Name State Autostart Persistent
----------------------------------------------------------
lan1 active no yes
lan2 active no yes
Note that the xml files needed by libvirt to create the network are under .qdeploy/ directory.
Check (2):
$ brctl show
$ virt-deploy vm-start -a
Check (1):
$ virsh list
Id Name State
----------------------------------------------------
2 mydeb running
Check (2):
Start virt-manager and click on 'mydeb', you should see the display.
- Check the ip address is 192.168.100.100
- Check the default route via 192.168.100.1
- Check the dns is 192.168.100.1
- Check the virtual machine 'details' panel (cpu, memory, ...)
creates a .qdeploy/ hidden directory
$ virt-deploy init
start all networks
$ virt-deploy net-start -a
start selected networks
$ virt-deploy net-start nw1 nw2
Stopped network are unregistered from libvirt.
stop all networks
$ virt-deploy net-stop -a
stop selected networks
$ virt-deploy net-stop nw1 nw2
start all vms
$ virt-deploy vm-start -a
start selected vms
$ virt-deploy vm-start vm1 vm2
stop all vms
$ virt-deploy vm-stop -a
stop selected vms
$ virt-deploy vm-stop vm1 vm2
This section describes the qdeploy.conf configuration file. This file describes the networks and the virtual machines you want to deploy.
It offers a consistent representation of the information that would otherwise be scattered in many xml files (one per network, see virsh net-define) and many shell commands (one per vm, see virt-install).
The idea is to register all the resource to libvirt at once and to unregister them also at once.
The 'qdeploy.conf' file is composed of 'network', 'vm', 'vm_defaults' and 'docker' sections. network and vm are mandatory.
The 'network' part allows to create one or many networks.
It corresponds exactly to the libvirt network format translated to a simpler config format (see syntax section below).
Example:
network "mgtnw1" {
bridge.name="mgtnw1"
forward.mode=nat
ip.address=192.168.202.1
ip.netmask=255.255.248.0
ip.dhcp.range {start=192.168.202.100 end=192.168.202.253}
ip.dhcp.host {mac=52:54:00:00:01:07 ip=192.168.202.161}
ip.dhcp.host {mac=52:54:00:00:01:0B ip=192.168.202.192}
}
In this example, the network has nat translation and a dhcp server. Some machines have fixed reserved addresses.
The 'vm' part permits to create one or several virtual machines (aka 'domains' in libvirt). Under the hood, we use 'virt-install' to create and start a virtual machine.
In our qdeploy.conf config file, the attributes of the 'vm' element corresponds to the virt-install command line parameters.
More specifically, virt-install parameters are translated into:
- an element with the name of the parameters
- a text attribute with the value of the parameter
- a mandatory semi column
Example
ram 4096;
If the parameter attribute is a comma-separated key-value list, it is possible to pass a list of attributes, which might be more readable.
Example
network {
network=mgtnw1;
mac=52:54:00:00:01:07;
model=e1000;
}
Full example
If you want to start a vm like so:
$ virt-install --name vm1 --ram 4000 --vcpus 2 --graphics vnc \
--network "network=mgtnw1,mac=52:54:00:00:01:07,model=e1000" \
--disk "./vm1.qcow2" --noautoconsole
Use the description below in qdeploy.conf
vm vm1 {
ram 4000;
vcpus 2;
graphics vnc;
noautoconsole;
network {
network=mgtnw1;
mac=52:54:00:00:01:07;
model=e1000;
}
}
Note that if the "disk" element is not specified, the script assumes that a 'qcow2' file exist with the name of the vm in the current working directory.
So in this case, the extra parameter is implicitly added:
disk "./vm1.qcow2"
The vm_defaults section can be used to set the properties common to all vms. The syntax is the same as 'vm'
Example:
vm_defaults {
ram 4000;
graphics "spice,listen=0.0.0.0";
noautoconsole;
transient;
}
This example means that all the vms:
- have 4Gb
- use spice display
- do not show the display at startup
- are unregistered they are stopped
The docker element allows to start a container that isolates the vms and the networks you create.
Example:
docker "mycontainer" {
mount "/data/qemu/vmtest";
mount "/data/qemu/base";
x11 true;
start_cmd "iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE";
}
'mount' indicates which directories should be made available to the container (at least the current directory).
'x11' specifies if you want to be able to open x11 applications inside the container, which is especially useful for virt-manager.
'start_cmd' is any bash command you want to start inside the container when the container starts. Here I specify that all the traffic leaving the container should have the address of the container.
It is possible to execute bash commands directly on the host when the docker container is started.
start_cmd "sudo ip route add 192.168.100.0/24 via 172.17.0.2";
stop_cmd "sudo ip route del 192.168.100.0/24 via 172.17.0.2";
In this example, I set a route to access the network I have created from my host via the docker container.
The file uses a very simple format which translates directly to xml. see here.
Note that contrary to xml, the double quotes can be omitted for 'simple' attributes such as number, ip addresses and identifiers (more generally, letters and digits without spaces). In doubt, always use double quote.
| description | etconfig format | xml format |
|---|---|---|
| element | network{ } | <network></network> |
| element(1) | network; | <network/> |
| attributes(2) | network{ipv6="yes"} | <network ipv6="yes"></network> |
| name attribute(3) | network "nw1" {} | <network name="nw1"></network> |
| text | ram {"4096"} | <ram>4096</ram> |
| text(4) | ram 4096; | <ram>4096</ram> |
| nested elements | dhcp{ host {ip=""} } | <dhcp> <host ip=""></host></dhcp> |
| nested elements(5) | dhcp.host.ip="" | <dhcp> <host ip=""></host></dhcp> |
| comments | # this is a comment | <!-- this is a comment --> |
| multi-line comments | /* this is a comment */ | <!-- this is a comment --> |
(1) if the element does not have attributes or nested-elements, brackets can be avoided (but a ';' is needed)
(2) note that an optional trailing ';' can be added to make separation between attributes clearer (e.g name="foo"; color="red"; )
(3) special simplified syntax for attribute called "name". It can be used only with the "brackets" form of element. This is syntactic sugar, you don't have to use it if it confuses you.
(4) if the text attribute is the only sub-element, brackets can be avoided (but a ';' is needed)
(5) if the nested elements are unique, it is possible to use a dotted notation
The example below is the same example as in quick start before, except that we say we want our networks and our vms to be started in a docker container, which permits to run several simulations on your host without interference.
Edit the qdeploy.conf to add a 'docker' section.
Change the 'mount' section to mount the current directory to docker (In my example, I have to mount 2 directories because my image is linked to a base image in another directory).
docker "mycontainer" {
mount "/data/qemu/tuto";
mount "/data/qemu/base";
x11 true;
}
network "lan1"{
bridge.name="lan1"
forward.mode=nat
ip.address=192.168.100.1
ip.netmask=255.255.255.0
ip.dhcp.range {start=192.168.100.100 end=192.168.100.254}
}
network "lan2"{
bridge.name="lan2"
}
vm "mydeb" {
ram 2048;
vcpus 2;
graphics vnc;
noautoconsole;
network {network=lan1 model=e1000}
network {network=lan2 model=e1000}
}
The steps to deploy your environment in docker are:
$ virt-deploy env-start
Checks:
$ docker ps
$ virt-deploy sh systemctl status libvirtd
$ virt-deploy sh virsh list
virt-deploy net-start -a
Check
$ virt-deploy sh virsh net-list
Name State Autostart Persistent
----------------------------------------------------------
lan1 active no yes
lan2 active no yes
$ virt-deploy vm-start -a
Check (1):
$ virt-deploy sh virsh list
Id Name State
----------------------------------------------------
2 mydeb running
Get the docker ip address (eg docker inspect), and enter the following command
virt-manager -c qemu+tcp://172.17.0.2/system
You should see your virtual machine 'mydeb'. If you click on it, the display should appear. Check that the machine has internet access (because we set forward mode to nat in the example above).
If you always connect to the same container, it is easier to set an environment variable:
export VIRSH_DEFAULT_CONNECT_URI='qemu+tcp://172.17.0.2/system'
You can now use virsh as usual from the host
$ virsh list
Id Name State
----------------------------------------------------
1 mydeb running
The 'virt-deploy sh' command is simply an alias to enter a running container (docker exec). You can use it to interact with libvirtd directly:
$ virt-deploy sh virsh list
Id Name State
----------------------------------------------------
1 mydeb running
virt-deploy sh ssh 192.168.100.104
If you have the x11 socket available from the container, you can also start the virt-manager directly in the countainer and get the display of your machine:
There is an alias to do this
virt-deploy virtmgr
This is useful if you need to scp to a vm for example.
The first thing to do is to add a route on the host to reach the internal network via docker. This can be done automatically by adding the following lines at the beginning of the qdeploy.conf file.
start_cmd "sudo ip route add 192.168.100.0/24 via 172.17.0.2";
stop_cmd "sudo ip route del 192.168.100.0/24 via 172.17.0.2";
Unfortunately, this is not sufficient. Now if you try to ssh to your vm, you'll get an error. The reason is that when you configure a libvirt network with a forward mode 'nat', it also creates iptable rules to prevent the outside from accessing the network.
So you have 2 options:
- remove the iptable rule set by libvirt
- use regular routing and configure masquerading yourself.
I prefer the second approach, so the config now becomes:
start_cmd "sudo ip route add 192.168.100.0/24 via 172.17.0.2";
stop_cmd "sudo ip route del 192.168.100.0/24 via 172.17.0.2";
docker "mycontainer" {
mount "/data/qemu/vmtest";
mount "/data/qemu/base";
start_cmd "iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE";
}
network "mgtnw1" {
bridge.name="mgtnw1"
forward.mode=route
ip.address=192.168.100.1
ip.netmask=255.255.248.0
ip.dhcp.range {start=192.168.100.100 end=192.168.100.253}
}
With this config, you should be able to access your vm started in docker directly from your host.