Machina is a lightweight and opinionated virtual machine manager. It uses QEMU to start and stop kernel virtual machines on linux systems. Each virtual machine is operated as a systemd unit.
This software is experimental. There are no gaurantees about API compatibility, file format compatibility, or fitness for purpose.
The systemd
units generated by machina
currently require the QEMU process
to be run as root. This presents a security concern should a malicious
virtual machine process ever escape from its containment. In light of this,
machina
is not yet recommended for production use.
The machina library and program are written in Go.
The machina
program is distributed as a single binary with no dependencies
beyond those of QEMU itself.
While machina
is involved in the generation of systemd
unit files and
assists with virtual machine preparation and teardown, it is not responsible
for launching the QEMU process. Instead, systemd
launches the QEMU process
directly.
This approach removes machina
as a point of failure during normal operation,
and allows machina
to be updated without interrupting the virtual machines
managed by it.
The machina
design encourages creation and use of virtual machine tags.
Tags define a common set of attributes for virtual machines. This reduces
the amount of configuration that must be managed for each virtual machine.
Instead of interrogating a running environment, machina
expects host system
configuration to be defined explicitly. As a result, machina
can be used
to generate systemd
units for remote and offline hosts, as long as the host
configuration files are available.
Other virtual machine managers tend to haul around a lot of configuration
data for each virtual machine in order to reproduce its environment precisely.
The machina
system goes in the opposite direction, attempting to reduce
the per-machine configuration to its most necessary and succinct components.
Users of machina
should think about high level concepts for their virtual
machines:
- What disk volumes does it need?
- Where are my disk images stored?
- How many cores and how much RAM does it need?
- What network connections should it have?
The primary task of machina
is to translate these concepts into QEMU command
lines that can be invoked by systemd
. The opinions of the machina
designers
are expressed in this process, making decisions about PCI Express bus layouts,
what QEMU device implementations to use, and other similar concerns.
Host system configuration is separate from virtual machine guest configuration.
Virtual machine definitions can easily be relocated to new hosts without modification.
The machina
design is focused on supporting QEMU and KVM.
Virtual machines can easily be configured to enable Hyper-V Enlightenments used by Windows guests.
Virtual machines can be supplied with OVMF firmware, allowing the virtual machines to run in the context of a modern UEFI BIOS.
Machina supports the use of mediated devices provided by some graphics hardware vendors. This allows virtual machines to share access to hardware resources that provide this feature.
The systemd
units will attempt an orderly shutdown of their virtual machines
when systemd
asks the unit to stop. This is accomplished by issuing QMP
shutdown commands which are delivered to the virtual environment as ACPI power
commands.
The only identifiers that must be provided for each virtual machine are its name and its machine-wide unique identifier, the UUID.
While machina
allows for explicit configuration of MAC addresses, volume
serial numbers and other such identifiers, it is capable of producing stable
identifiers as needed. It accomplishes this by deriving identifiers from
the machine's UUID and name via hashing.
The QEMU process that manages a virtual machine should run as a non-root user with least privileges.
While virtual machines can easily be disabled via machina disable
, there is
no facility for removal of their systemd
units.
The currently supported patterns are very rudimentary and don't allow for evaluation of mathematical expressions when determining things like port numbers.
A graphical user interface for creation and management of machina
configuration files would be neat.
The designers of machina
prefer to operate their virtual machines as raw
disk images stored on zfs
datasets. This allows for images to be efficiently
snapshotted and for snapshots to be efficiently transported. Support for
qcow2
and other disk formats has not been a focus, though it would not be
difficult to add.
There is no support for live migration at this time.
There is no facility for total annihilation of virtual machines, which would include permanent deletion of their disk volumes.
This is an inherantly dangerous ability and won't be added without much consideration.
Bash completion doesn't work when using sudo
, as in sudo machina <command>
.
Newer versions of machina
might make different choices or use different
default values when generating systemd
units. This can result in changes to
the virtual hardware environment of the machines that it manages.
While this poses some risk, most guest operating systems are tolerant of hardware changes.
Usage: machina <command>
Manages kernel virtual machines via QEMU.
Flags:
-h, --help Show context-sensitive help.
Commands:
install
Installs the machina command in the system path.
cat [<machines> ...]
Displays the machina configuration for virtual machines.
list
Lists all of the virtual machines present.
status <machines> ...
Displays the systemd unit status for virtual machines.
observe <machines> ...
Reports QMP events for virtual machines.
generate <machines> ...
Generates systemd unit configuration files from /etc/machina/machine.conf.d/*.conf.json.
enable <machines> ...
Enables the systemd units for virtual machines.
disable <machines> ...
Disables the systemd units for virtual machines.
start <machines> ...
Starts the systemd units for virtual machines.
stop <machines> ...
Stops the systemd units for virtual machines.
shutdown <machines> ...
Sends a shutdown command to one or more virtual machines.
prepare <machines> ...
Prepares the host environment for a virtual machine to start.
teardown <machines> ...
Removes host resources prepared for a virtual machine.
connect <machines-or-connections> ...
Connects a whole virtual machine or individual connections to the network.
disconnect <machines-or-connections> ...
Disconnects a whole virtual machine or individual connections from the network.
query pci <machines> ...
Describes the PCI Bus in running virtual machines.
query cpu <machines> ...
Describes the virtual CPUs present in running virtual machines.
gen-id
Generate a random machine identifier.
gen-mac
Generate a random MAC hardware address.
args <machines> ...
Displays the QEMU arguments for virtual machines.
run [<machine>]
Run a virtual machine directly via QEMU.
Run "machina <command> --help" for more information on a command.
The machina
design divides configuration into two portions:
- System (host) configuration
- Virtual machine (guest) configuration
When machina
builds a systemd
unit for a virtual machine, it reads two
files and uses their content to produce the systemd
unit:
- The system host configuration file (
/etc/machina/machina.conf.json
) - The virtual machine guest configuration file
(
/etc/machina/machine.conf.d/[guest-name].conf.json
)
The system configuration file defines the storage location, available networks, and interpretation of tags on a particular system. It describes the environmental features that are unique to that particular host.
The virtual machine configuration file defines the name, ID, tags and attributes of a virtual machine. It describes the features of a guest and what it expects from its host.
If a virtual machine definition is relocated to a new host, it gets combined
with a different system configuration file, and thus produces a systemd
unit that is suitable for that host.
Note: The configuration file format is subject to change, and this example configuration could fall out of sync as changes are made.
Machine configuration files are stored in individual conf.json
files within
/etc/machina/machine.conf.d
. Here's an example for a theoretical
test-vm
, which would be placed in a
/etc/machina/machine.conf.d/test-vm.conf.json
file:
{
"id": "8b18191a-234f-45ce-b43c-b46e28cd2f70",
"description": "Test VM (2023-04-27)",
"tags": ["vdi-employee", "vdi-cad", "windows", "windows-10-media", "firmware-202302"],
"vars": {
"employee-id": "9000"
}
}
System configuration is stored in the /etc/machina/machina.conf.json
file. Here's an example
that provides definitions for the tags used by the example test machine:
{
"storage": {
"guest-firmware-vars": {
"path": "/zfs-tank/",
"pattern": "${machine-name}/os/${machine-name}-${volume}.vars.bin",
"type": "firmware"
},
"guest-data": {
"path": "/zfs-tank/",
"pattern": "${machine-name}/os/${machine-name}-${volume}.raw",
"type": "raw"
},
"firmware-code": {
"path": "/usr/lib/machina/firmware/",
"pattern": "${volume}.code.bin",
"type": "firmware",
"readonly": true
},
"iso-ahci": {
"path": "/usr/lib/machina/iso/",
"pattern": "${volume}.iso",
"type": "iso-ahci"
},
"iso-scsi": {
"path": "/usr/lib/machina/iso/",
"pattern": "${volume}.iso",
"type": "iso-scsi"
}
},
"network": {
"local": {
"device": "kvmbr0"
}
},
"mdev": {
"nvidia-rtx8000": {
"address": "0000:c1:00.0",
"types": {
"cad-standard": "GRID RTX8000-6Q"
}
}
},
"tag": {
"vdi-employee": {
"attrs": {
"spice": {
"enabled": true,
"port-pattern": "5${employee-id}",
"displays": 1
},
"agent": {
"qemu": {
"enabled": true,
"port-pattern": "4${employee-id}"
}
}
}
},
"vdi-cad": {
"attrs": {
"cpu": {"sockets": 1, "cores": 4},
"memory": {"ram": 32768}
},
"volumes": [
{"name": "os", "storage": "guest-data", "bootable": true}
],
"connections": [
{
"name": "0",
"network": "local"
}
],
"devices": [
{
"name": "vgpu",
"class": "cad-standard"
}
]
},
"windows": {
"attrs": {
"enlightenments": {"enabled": true},
"qmp": {
"enabled": true
}
},
"volumes": [
{"storage": "iso-ahci", "name": "virtio-win-latest"}
]
},
"windows-10-media": {
"volumes": [
{"storage": "iso-scsi", "name": "win10"}
]
},
"firmware-202302": {
"attrs": {
"firmware": {
"code": {"storage": "firmware-code", "name": "ovmf-stable202302"},
"vars": {"storage": "guest-firmware-vars", "name": "firmware"}
}
}
}
}
}
Here is the resulting summary offered by machina cat test-vm
:
Name: test-vm
Description: Test VM (2023-04-27)
ID: 8b18191a-234f-45ce-b43c-b46e28cd2f70
Tags: vdi-employee,vdi-cad,windows,windows-10-media,firmware-202302
Vars:
employee-id: 9000
Firmware Code (read-only): ovmf-stable202302: firmware-code
Firmware Variables (read/write): firmware: guest-firmware-vars
Sockets: 1
Cores: 4
RAM: 32768M
Hyper-V Enlightenments: Enabled
QMP: Enabled
QMP Socket Path: /run/machina/test-vm/systemd.0.qmp.socket
QMP Socket Path: /run/machina/test-vm/command.0.qmp.socket
QMP Socket Path: /run/machina/test-vm/command.1.qmp.socket
QEMU Guest Agent: Enabled
QEMU Guest Agent Port: 49000
Spice Display: Enabled
Spice Port: 59000
Spice Display Count: 1
Volumes:
os: guest-data (wwn: 0x5525400908FE6258, serial: 9IQ4PUV68QOCNS2C75OKUS04I4, bootable)
virtio-win-latest: iso-ahci (wwn: 0x5525400982DB87CE, serial: JAVJJVPOOPTIV73ONECE40VFD4)
win10: iso-scsi (wwn: 0x5525400F7C2ED5D0, serial: 0V17D9DH4EVT9SFOCV9CFTR0BK)
Connections:
0: local (mac: 52:54:00:26:77:fa)
Devices:
vgpu: cad-standard (3e2cee4d-1002-4989-af98-3e03b8ad3197)
Here is the resulting systemd
unit file, as shown by
machina generate --preview test-vm
:
[Unit]
Description=machina KVM test-vm
Wants=network-online.target
After=network-online.target
StartLimitIntervalSec=1m0s
StartLimitBurst=2
[Service]
Type=simple
ExecStart=qemu-system-x86_64 \
-uuid 8b18191a-234f-45ce-b43c-b46e28cd2f70 \
-name test-vm \
-enable-kvm \
-nodefaults \
-nographic \
-machine type=q35,vmport=off,pflash0=ovmf-stable202302,pflash1=test-vm-firmware \
-cpu host,hv_relaxed,hv_spinlocks=0x1fff,hv_vapic,hv_time \
-smp sockets=1,cores=4 \
-m size=32768M \
-rtc base=utc,clock=host,driftfix=slew \
-mon chardev=qmp.0,mode=control \
-mon chardev=qmp.1,mode=control \
-mon chardev=qmp.2,mode=control \
-spice port=59000,addr=127.0.0.1,disable-ticketing=on \
-global driver=cfi.pflash01,property=secure,value=on \
-global driver=qxl-vga,property=ram_size,value=67108864 \
-global driver=qxl-vga,property=vram_size,value=67108864 \
-boot menu=on,reboot-timeout=5000 \
-object iothread,id=iothread.0 \
-blockdev driver=file,node-name=ovmf-stable202302,read-only=on,filename=/usr/lib/machina/firmware/ovmf-stable202302.code.bin \
-blockdev driver=file,node-name=test-vm-firmware,filename=/zfs-tank/test-vm/os/test-vm-firmware.vars.bin \
-blockdev driver=file,node-name=test-vm-os-file,filename=/zfs-tank/test-vm/os/test-vm-os.raw \
-blockdev driver=raw,node-name=test-vm-os,file=test-vm-os-file \
-blockdev driver=file,node-name=virtio-win-latest,read-only=on,filename=/usr/lib/machina/iso/virtio-win-latest.iso \
-blockdev driver=file,node-name=win10,read-only=on,filename=/usr/lib/machina/iso/win10.iso \
-chardev socket,id=qmp.0,server=on,wait=off,path=/run/machina/test-vm/systemd.0.qmp.socket \
-chardev socket,id=qmp.1,server=on,wait=off,path=/run/machina/test-vm/command.0.qmp.socket \
-chardev socket,id=qmp.2,server=on,wait=off,path=/run/machina/test-vm/command.1.qmp.socket \
-chardev socket,id=guestagent,host=127.0.0.1,port=49000,server=on,wait=off,nodelay=on \
-chardev spicevmc,id=vdagent,debug=0,name=vdagent \
-chardev spicevmc,id=usbredir.0,debug=0,name=usbredir \
-chardev spicevmc,id=usbredir.1,debug=0,name=usbredir \
-netdev tap,id=net.0,ifname=test-vm.0,script=/usr/bin/machina-ifup,downscript=/usr/bin/machina-ifdown \
-device ioh3420,id=pcie.1.0,chassis=0,bus=pcie.0,addr=1.0,multifunction=on \
-device virtio-serial-pci,id=serial,bus=pcie.1.0 \
-device virtserialport,id=serial.0.0,bus=serial.0,nr=1,chardev=guestagent,name=org.qemu.guest_agent.0 \
-device virtserialport,id=serial.0.1,bus=serial.0,nr=2,chardev=vdagent,name=com.redhat.spice.0 \
-device qxl-vga,id=qxl.0,bus=pcie.0,addr=1.1 \
-device ioh3420,id=pcie.1.2,chassis=2,bus=pcie.0,addr=1.2 \
-device qemu-xhci,id=usb,bus=pcie.1.2,p2=4,p3=4 \
-device usb-tablet,id=usb.0.1,bus=usb.0,port=1 \
-device usb-redir,id=usb.0.2,bus=usb.0,port=2,chardev=usbredir.0 \
-device usb-redir,id=usb.0.3,bus=usb.0,port=3,chardev=usbredir.1 \
-device ioh3420,id=pcie.1.3,chassis=3,bus=pcie.0,addr=1.3 \
-device virtio-scsi-pci,id=scsi,bus=pcie.1.3,iothread=iothread.0,num_queues=4 \
-device scsi-hd,id=scsi.0.0,bus=scsi.0,channel=0,scsi-id=0,lun=0,drive=test-vm-os,wwn=0x5525400908FE6258,serial=9IQ4PUV68QOCNS2C75OKUS04I4,bootindex=1 \
-device scsi-cd,id=scsi.0.1,bus=scsi.0,channel=0,scsi-id=0,lun=1,drive=win10 \
-device ide-cd,id=sata.0,bus=ide.1,drive=virtio-win-latest \
-device ioh3420,id=pcie.1.5,chassis=5,bus=pcie.0,addr=1.5 \
-device virtio-net-pci,bus=pcie.1.5,mac=52:54:00:26:77:fa,netdev=net.0 \
-device ioh3420,id=pcie.1.6,chassis=6,bus=pcie.0,addr=1.6 \
-device vfio-pci,id=vfio.0,bus=pcie.1.6,sysfsdev=/sys/bus/mdev/devices/3e2cee4d-1002-4989-af98-3e03b8ad3197
ExecStartPre=machina prepare test-vm
ExecStop=machina shutdown --system --timeout 1m25s test-vm
ExecStopPost=machina teardown test-vm
RestartSec=10s
TimeoutStopSec=1m30s
Restart=on-failure
RuntimeDirectory=machina/test-vm
[Install]
WantedBy=multi-user.target