One can use the virt-install command to create virtual machines and install operating system on those virtual machines from the command line.
A sample command to install a VM:
virt-install
--name ps-dev-tmpl
--os-variant ubuntu22.04
--vcpus 2
--memory 6144
--location /data1/os-iso/ubuntu-22.04.4-live-server-amd64.iso,kernel=casper/vmlinuz,initrd=casper/initrd
--network bridge=virbr20,model=virtio,mac=0a:01:e1:20:de:01
--network network=virho10,model=virtio,mac=0a:01:e1:10:de:01
--disk size=28,pool=adm-images
--uuid=00000000-0000-0000-0000-0a01e120de01
--graphics none
--extra-args='console=ttyS0' --debug
--name (*required): Name option is used to uniquely identify a guest inside the hypervisor, this name doesn’t effect anything inside the guest, but is used by the hypervisor to uniquely identify guests running. This must be unique to each guest.
--os-varient (optional): This option is passed to optimize the guest configuration for specifi operating system type. If this isn’t set, it tries to autodetect the os variant.
--vcpus (optional): When specified with number, the virtual machine created will be assigned 2 vcpus at startup.
--memory (*required): This option defines the memory that can be used by the virtual machine.
--location (required): Location to a installable distribution image such as an iso. kernal=<path-to-kernal>
option is used to specify the kernal path inside the .iso image. And initrd=<path-to-initial-RAMdisk>
file used by boot loader to load off from a an initial RAMdisk which is specified to instruct boot loader from which it should start initiating the system as a whole.
--network <type:value, model:value,mac:value,>: Network option is used to define the interfaces to be used inside the guest VM. This option accepts arguments such as model which takes any value such as bridge(to specify bridge netwotk interface to connect to), network(to use an host only network), model with attributes such as virtio for virtual interface and any other type of interfaces supported by underlying hypervisor. Mac address option can be used to specify a static, custom mac address for the interface(if unspecified, defaults to a random mac address).
--disk <size=(G,B,M), path=>: Disk option can be used to specify an existing disk image in .img format or if unspecified it creates an disk image in .qcow2 format in default storage pool. Size option describes the size of disk to be created(can be specified in G,M,K for GB, MB,KB respectively) and path option can be used to specify the base installation medium in case of an existing disk image. You can use pool= option to specify a custom pool instead using default pool.
--uuid=<32 digit hexadecimal>: UUID field is used to uniquely identify a system/machine across datacenter or around the world. UUID should be atleast unique to the datacenter to function correctly.
--graphics: Graphics option is used to specify the graphical display configuration. It defaults to vnc if none is specified.
--console: The console option is used to connect the specified guest console to host machine. The tty0 option is meant to connect the default console of guest machine.
After running this command, a default console of the installing guest machine will be connected to the current terminal.If disconnected for some reason you can use virsh to connect to the previously disconnected guest machine to continue with the installation process
A sample xml file content to define a virtual machine with custom configurations:
<domain type='kvm' id='119'>
<name>ps-staging</name>
<uuid>00000000-0000-0000-0000-0a01e1200000</uuid>
<metadata>
<libosinfo:libosinfo xmlns:libosinfo="http://libosinfo.org/xmlns/libvirt/domain/1.0">
<libosinfo:os id="http://ubuntu.com/ubuntu/22.04"/>
</libosinfo:libosinfo>
</metadata>
<memory unit='KiB'>8388608</memory>
<currentMemory unit='KiB'>8388608</currentMemory>
<vcpu placement='static'>2</vcpu>
<resource>
<partition>/machine</partition>
</resource>
<os>
<type arch='x86_64' machine='pc-q35-6.2'>hvm</type>
<boot dev='hd'/>
</os>
<features>
<acpi/>
<apic/>
</features>
<cpu mode='host-passthrough' check='none' migratable='on'/>
<clock offset='utc'>
<timer name='rtc' tickpolicy='catchup'/>
<timer name='pit' tickpolicy='delay'/>
<timer name='hpet' present='no'/>
</clock>
<on_poweroff>destroy</on_poweroff>
<on_reboot>restart</on_reboot>
<on_crash>destroy</on_crash>
<pm>
<suspend-to-mem enabled='no'/>
<suspend-to-disk enabled='no'/>
</pm>
<devices>
<emulator>/usr/bin/qemu-system-x86_64</emulator>
<disk type='file' device='disk'>
<driver name='qemu' type='qcow2' discard='unmap'/>
<source file='/data2/adm-images/ps-staging.qcow2' index='2'/>
<backingStore/>
<target dev='vda' bus='virtio'/>
<alias name='virtio-disk0'/>
<address type='pci' domain='0x0000' bus='0x05' slot='0x00' function='0x0'/>
</disk>
<disk type='file' device='cdrom'>
<driver name='qemu'/>
<target dev='sda' bus='sata'/>
<readonly/>
<alias name='sata0-0-0'/>
<address type='drive' controller='0' bus='0' target='0' unit='0'/>
</disk>
<controller type='usb' index='0' model='qemu-xhci' ports='15'>
<alias name='usb'/>
<address type='pci' domain='0x0000' bus='0x03' slot='0x00' function='0x0'/>
</controller>
<controller type='pci' index='0' model='pcie-root'>
<alias name='pcie.0'/>
</controller>
<controller type='pci' index='1' model='pcie-root-port'>
<model name='pcie-root-port'/>
<target chassis='1' port='0x8'/>
<alias name='pci.1'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x0' multifunction='on'/>
</controller>
<controller type='pci' index='2' model='pcie-root-port'>
<model name='pcie-root-port'/>
<target chassis='2' port='0x9'/>
<alias name='pci.2'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x1'/>
</controller>
<controller type='pci' index='3' model='pcie-root-port'>
<model name='pcie-root-port'/>
<target chassis='3' port='0xa'/>
<alias name='pci.3'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x2'/>
</controller>
<controller type='pci' index='4' model='pcie-root-port'>
<model name='pcie-root-port'/>
<target chassis='4' port='0xb'/>
<alias name='pci.4'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x3'/>
</controller>
<controller type='pci' index='5' model='pcie-root-port'>
<model name='pcie-root-port'/>
<target chassis='5' port='0xc'/>
<alias name='pci.5'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x4'/>
</controller>
<controller type='pci' index='6' model='pcie-root-port'>
<model name='pcie-root-port'/>
<target chassis='6' port='0xd'/>
<alias name='pci.6'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x5'/>
</controller>
<controller type='pci' index='7' model='pcie-root-port'>
<model name='pcie-root-port'/>
<target chassis='7' port='0xe'/>
<alias name='pci.7'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x6'/>
</controller>
<controller type='pci' index='8' model='pcie-root-port'>
<model name='pcie-root-port'/>
<target chassis='8' port='0xf'/>
<alias name='pci.8'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x7'/>
</controller>
<controller type='pci' index='9' model='pcie-root-port'>
<model name='pcie-root-port'/>
<target chassis='9' port='0x10'/>
<alias name='pci.9'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x0' multifunction='on'/>
</controller>
<controller type='pci' index='10' model='pcie-root-port'>
<model name='pcie-root-port'/>
<target chassis='10' port='0x11'/>
<alias name='pci.10'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x1'/>
</controller>
<controller type='pci' index='11' model='pcie-root-port'>
<model name='pcie-root-port'/>
<target chassis='11' port='0x12'/>
<alias name='pci.11'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x2'/>
</controller>
<controller type='pci' index='12' model='pcie-root-port'>
<model name='pcie-root-port'/>
<target chassis='12' port='0x13'/>
<alias name='pci.12'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x3'/>
</controller>
<controller type='pci' index='13' model='pcie-root-port'>
<model name='pcie-root-port'/>
<target chassis='13' port='0x14'/>
<alias name='pci.13'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x4'/>
</controller>
<controller type='pci' index='14' model='pcie-root-port'>
<model name='pcie-root-port'/>
<target chassis='14' port='0x15'/>
<alias name='pci.14'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x5'/>
</controller>
<controller type='sata' index='0'>
<alias name='ide'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x1f' function='0x2'/>
</controller>
<controller type='virtio-serial' index='0'>
<alias name='virtio-serial0'/>
<address type='pci' domain='0x0000' bus='0x04' slot='0x00' function='0x0'/>
</controller>
<interface type='bridge'>
<mac address='0a:01:e1:20:00:00'/>
<source bridge='virbr20'/>
<target dev='vnet227'/>
<model type='virtio'/>
<alias name='net0'/>
<address type='pci' domain='0x0000' bus='0x01' slot='0x00' function='0x0'/>
</interface>
<interface type='network'>
<mac address='0a:01:e1:10:00:00'/>
<source network='virho10' portid='97b7870c-5280-444b-a3ee-33ed3c603234' bridge='virho10' macTableManager='libvirt'/>
<target dev='vnet228'/>
<model type='virtio'/>
<alias name='net1'/>
<address type='pci' domain='0x0000' bus='0x02' slot='0x00' function='0x0'/>
</interface>
<serial type='pty'>
<source path='/dev/pts/8'/>
<target type='isa-serial' port='0'>
<model name='isa-serial'/>
</target>
<alias name='serial0'/>
</serial>
<console type='pty' tty='/dev/pts/8'>
<source path='/dev/pts/8'/>
<target type='serial' port='0'/>
<alias name='serial0'/>
</console>
<channel type='unix'>
<source mode='bind' path='/var/lib/libvirt/qemu/channel/target/domain-119-ps-staging/org.qemu.guest_agent.0'/>
<target type='virtio' name='org.qemu.guest_agent.0' state='disconnected'/>
<alias name='channel0'/>
<address type='virtio-serial' controller='0' bus='0' port='1'/>
</channel>
<input type='mouse' bus='ps2'>
<alias name='input0'/>
</input>
<input type='keyboard' bus='ps2'>
<alias name='input1'/>
</input>
<audio id='1' type='none'/>
<memballoon model='virtio'>
<alias name='balloon0'/>
<address type='pci' domain='0x0000' bus='0x06' slot='0x00' function='0x0'/>
</memballoon>
<rng model='virtio'>
<backend model='random'>/dev/urandom</backend>
<alias name='rng0'/>
<address type='pci' domain='0x0000' bus='0x07' slot='0x00' function='0x0'/>
</rng>
</devices>
<seclabel type='dynamic' model='apparmor' relabel='yes'>
<label>libvirt-00000000-0000-0000-0000-0a01e1200000</label>
<imagelabel>libvirt-00000000-0000-0000-0000-0a01e1200000</imagelabel>
</seclabel>
<seclabel type='dynamic' model='dac' relabel='yes'>
<label>+64055:+108</label>
<imagelabel>+64055:+108</imagelabel>
</seclabel>
</domain>
The above is a sample XML file dumped from a running VM. One can edit this file as per requirements in the respective tags and can define a domain(VM) using the below command.
To define a domain from the above xml file. Save the content in a .xml file and run:
virsh define <filename>.xml
To edit a defined domain from its xml file:
virsh edit <filename>.xml
Changes made using this method will be effected in the next boot of the running guest.
Step-1: Make copy of the host machine’s disk to avoid loosing data: First we need to make sure of removing any machine specifics from the host machine. To do this safelt we first make a copy of the target disk to another directory:
cp /data1/guest-images/guest-1.qcow2 /tmp/backups/guest-images/guest-1.qcow2
Step-2: Before preparing the disk file to remove any machine specifics, we need to shutdow the domain(vm) first with name defined earlier.
virsh shutdown <guestname>
You can use destroy ti forcefully shutdown the domai.
virsh destroy <guestname>
To remove a VM completely from the hypervisor.
virsh undefine <guestname>
Step-3: Now use virt-sysprep
utility to remove any machine specifice such as ssh host keys, machine-id, disk uuids, fingerprints etc.
Make sure you install this utility libguestfs-tools-c
which provides the virt-sysprep utility
apt install libguestfs-tools-c
To prepare a template froma domain to clone:
virt-sysprep -a <diskfile>
To check for changes, we can compare with our original disk file which is previously backed up.
virt-diff -a <templated-disk-file-in-assigned-pool> -A <original file>
Step-4: Create a clone of the domain:
virt-clone --original <original-guest-name> --name <name-to-be-assigned-to-new-domain> --auto-clone
Using virsh to list running VMs
virsh list
**Using virsh to list all existing VMs
virsh list --all
virsh can be used to connect to a serial console of a guest vm which is running:
virsh console <guestname>
Start a stopped vm using virsh
virsh start <guestname>
virsh can be used to create and manage networks too which is described earlier in this documentation
To define a storage pool with a specified directory to actually store the disk images(default is to store at /var/lib/libvert/images)
virsh pool-define-as guest_images_dir dir --target "/guest_images"
To edit a existing pool’s configuration:
virsh pool-edit <poolname>
To auto-start a pool:
virsh pool-autostart <poolname>
To list all existing pools:
virsh pool-list
To view active pools:
virsh pool-list --all