On an internet-connected x86_64 host running CentOS Linux 7.9 with qemu-kvm and dependencies installed (see Set up qemu-kvm for CentOS 7):
Retrieve an iso installation image.
Create and configure a qemu-kvm guest VM.
Test run guest VM and modify its configuration.
Save guest VM image and runtime config files for re-use.
To create and run VMs as a non-root user your USER account must be a member of the kvm and qemu groups.
Create a private workspace:
> mkdir -p ${HOME}/qemu/{cfg,efi,img,pki,run}
-netdev user
This is the default; each VM is in its own private network with non-routable ip address(es) assigned to the guest's NIC(s) by the built-in qemu DHCP server:
Default network: 10.0.2.0/24
Gateway (host) address: 10.0.2.2
DNS server address: 10.0.2.3
Guest address range: 10.0.2.15 to 10.0.2.31
Set alternative and additional params at the -netdev
command line. You must add the 'net=', 'host=', 'dhcpstart=' and 'dns=' params if using a pre-set $MACADDR for the NIC.
Reference: Qemu Documentation/Networking
-netdev tap
For a VM to have full internet connectivity with a public ip address your USER account must have permission to attach the VM's NIC to a tap device (e.g. tap_$USER connected to the host bridge (e.g. br0). Note that the following will not persist between reboots:
> sudo ip tuntap add dev tap_${USER} mode tap user ${USER}
> sudo ip link set dev tap_${USER} up
> sudo ip link set tap_${USER} master br0
Reference: Hosting QEMU VMs with Public IP Addresses using TAP Interfaces
Retrieve a Linux installation iso image from a repository; we selected a Debian 11.5 ("Bullseye") iso image; it contains non-free firmware required to set up UEFI boot inside the VM:
> ISO_URL='https://cdimage.debian.org/cdimage/unofficial/non-free/cd-including-firmware/11.5.0-live+nonfree/amd64/iso-hybrid/'
> ISO_FILE='debian-live-11.5.0-amd64-standard+nonfree.iso'
> curl -k -L -s -o ${HOME}/qemu/img/${ISO_FILE} ${ISO_URL}/${ISO_FILE}
Set a name for the guest VM:
> VM_NAME="debian11"
Copy OVMF UEFI boot config overlay file to private workspace:
> cp /usr/share/OVMF/OVMF_VARS.fd ${HOME}/qemu/efi/${VM_NAME}.fd
Set vars for UEFI file paths:
EFI_CODE="/usr/share/OVMF/OVMF_CODE.secboot.fd"
EFI_VARS="${HOME}/qemu/efi/${VM_NAME}.fd"
EFI_SHELL="/usr/share/OVMF/UefiShell.iso"
Create an empty (sparse) 10G disk image in qcow2 format:
> qemu-img create -f qcow2 ${HOME}/qemu/img/${VM_NAME}.qcow2 10G
Set name and assign full host cpu capabilities with 2G RAM to guest VM:
-name ${VM_NAME} -cpu host -smp $(nproc) -m 2048M
Emulate Intel Q35 Express chipset, enable kvm and boot with OVMF UEFI images; EFI_CODE must be declared before EFI_VARS; suppress iPXE boot with -net none
; for ancient OSes use Intel 440fx chipset (does not support UEFI, legacy BIOS only):
-machine type=q35,accel=kvm -enable-kvm
-drive if=pflash,index=0,format=raw,read-only=on,file=${EFI_CODE}
-drive if=pflash,index=1,format=raw,file=${EFI_VARS}
-net none
Set hard disk and cdrom drives; do not use virtio drivers (Debian installer will default to scsi) else an EFI system partition (ESP) will not be created in the guest:
-cdrom ${HOME}/qemu/img/${ISO_FILE}
-hda ${HOME}/qemu/img/${VM_NAME}.qcow2
Output guest VM display to qxl-vga (enhanced VGA) device using qemu's built-in vnc server, listen for connections on port 6001 of all interfaces, no password, use US English keyboard; enable USB tablet to align mouse pointer with vnc display:
-device qxl-vga -display vnc=0.0.0.0:101 -k en-us
-device qemu-xhci -device usb-tablet
Reference: Qemu USB emulation
Create qemu monitor and serial port Unix sockets:
-chardev socket,id=console,path=${HOME}/qemu/run/${VM_NAME}.console,server,nowait
-serial chardev:console
-chardev socket,id=monitor,path=${HOME}/qemu/run/${VM_NAME}.monitor,server,nowait
-monitor chardev:monitor
References:
Set up a serial terminal/console in RHEL
QEMU - Send serial data from host to guest
Set up guest VM network; generate a unique MAC address; create guest NIC; do not use virtio driver for ancient OSes:
for COUNT in {1..6}; do
OCTET="$(tr -cd 1234567890ABCDEF </dev/urandom | head -c2)"
[[ $COUNT -lt 6 ]] && OCTET="${OCTET}:"
MACADDR="${MACADDR}${OCTET}"
done
-device virtio-net-pci,netdev=network,mac=${MACADDR}
For a public network (params set in guest):
-netdev tap,id=network,ifname=tap_${USER},script=no,downscript=no
For a private network (params set for $MACADDR):
-netdev user,id=network,net=10.0.2.0/24,host=10.0.2.2,dhcpstart=10.0.2.15,dns=10.0.2.3
The first entry in the host's /etc/resolv.conf file will become the guest's name server; if that's not a public (internet-accessible) name server, the guest will be unable to resolve domain names. In that case, edit the guest's /etc/resolv.conf file and replace the entry nameserver 10.0.2.3
with one or more public name servers, e.g nameserver 8.8.8.8
or nameserver 9.9.9.9
Create pidfile if starting the VM from a script:
-pidfile ${HOME}/qemu/run/${VM_NAME}.pid
Insert all params on the command line; use -daemonize
as last param to detach the process from stdin/stdout and run it in the background:
> /usr/libexec/qemu-kvm -name ${VM_NAME} ... -daemonize
Connect to the guest VM display:
> vncviewer localhost::6001 2>/dev/null &
The vnc server runs on the host not the guest. To connect to the guest VM display from a different computer replace localhost with the host's actual ip address; the host's firewall must permit connections on port 6001.
Use socat for an interactive connection to the VM serial port and Qemu monitor socket. The following will connect to the monitor socket, disable the default ^C and ^D quit/exit escape sequences and enable the telnet ^] quit/exit escape sequence:
> socat stdio,rawer,escape=0x1d unix-connect:${HOME}/qemu/run/${VM_NAME}.monitor
Reference: Socat, Telnet and Unix sockets
Use netcat or socat for scripted connections. The following will shut down the VM from the command line using netcat (nc) or socat:
> printf '%b' 'system_powerdown\n' | nc -U ${HOME}/qemu/run/${VM_NAME}.monitor
> printf '%b' 'system_powerdown\n' | socat stdio unix-connect:${HOME}/qemu/run/${VM_NAME}.monitor