Using Vagrant to create and deploy libvirt virtual machines
These commands will build and deploy a vagrant RHEL10 box from an existing .qcow2 disk.
Configure the template VM and disk, shut it down and create a Vagrant box, add it to local registry.
Install packages on template VM
On the template VM:
# make sure these are installed
dnf install firewalld flatpak lvm2 vdo stratis-cli stratisd \
nfs-utils autofs vim bash-completion tar \
policycoreutils-python-utils chrony psmisc acl \
man-pages bind-utils nano
Configure user vagrant
Create vagrant user and set password, ssh key
# Create the user and set the default password
useradd -m -s /bin/bash vagrant
echo "vagrant:vagrant" | chpasswd
# Setup passwordless sudo access
echo "vagrant ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/vagrant
chmod 0440 /etc/sudoers.d/vagrant
# Add the insecure public key for the initial connection
mkdir -p /home/vagrant/.ssh
chmod 700 /home/vagrant/.ssh
curl -L https://raw.githubusercontent.com/hashicorp/vagrant/master/keys/vagrant.pub -o /home/vagrant/.ssh/authorized_keys
chmod 600 /home/vagrant/.ssh/authorized_keys
chown -R vagrant:vagrant /home/vagrant/.ssh
# Fix security labels for the directory
restorecon -Rv /home/vagrant/.ssh
Configure the system in the template disk
Fix network - use predictable interface names, add dhcp for eth0
# use eth0, more predictable to get eth0 network up for vagrant
sed -i 's/GRUB_CMDLINE_LINUX=\"/GRUB_CMDLINE_LINUX=\"net.ifnames=0 biosdevname=0 /' /etc/default/grub
grub2-mkconfig -o /boot/grub2/grub.cfg
sed -i '/^options / s/$/ net.ifnames=0 biosdevname=0/' /boot/loader/entries/*.conf
# 1. remove the existing profiles
rm -f /etc/NetworkManager/system-connections/*.nmconnection
# 2. create eth0 profile
cat <<EOF > /etc/NetworkManager/system-connections/eth0.nmconnection
[connection]
id=eth0
type=ethernet
autoconnect=true
autoconnect-priority=100
[ethernet]
[ipv4]
method=auto
[ipv6]
addr-gen-mode=default
method=auto
[proxy]
EOF
# 3. enforce strict permissions required by networkmanager
chmod 600 /etc/NetworkManager/system-connections/eth0.nmconnection
# 4. confirm the profile is clean
cat /etc/NetworkManager/system-connections/eth0.nmconnection
Generalise the installation
# Clear package manager cache
dnf clean all
# Remove network and machine identifiers
rm -f /etc/udev/rules.d/70-persistent-net.rules
truncate -s 0 /etc/machine-id
# clear the bash history
rm -f /root/.bash_history
rm -f /home/vagrant/.bash_history
history -c
# shutdown the machine for packaging
poweroff
Build the box
Build the box (make sure you adjust the .qcow2 disk paths)
# export disk variables
export disk_path="/mnt/data/ISO/"
export disk_name="rhel10.1.qcow2"
export disk_name_final="rhel10.1-final.qcow2"
# 1. delete the current box from vagrant
vagrant box remove custom/rhel10 --provider libvirt
# 2. move back to your build directory
mkdir ~/vagrant_build
cd ~/vagrant_build
# 3. get the actual virtual size of your disk in bytes, convert to gigabytes
VSIZE=$(sudo qemu-img info "${disk_path}${disk_name}" | grep "virtual size" | grep -oP '\(\K\d+(?=\s+bytes\))')
VSIZE_GB=$(( (VSIZE + 1024**3 - 1) / 1024**3 ))
# 4. recreate the metadata.json with the required size and format
cat <<EOF > metadata.json
{
"provider": "libvirt",
"format": "qcow2",
"virtual_size": $VSIZE_GB
}
EOF
# 5. recreate the Vagrantfile template
cat <<EOF > Vagrantfile
ENV['VAGRANT_DEFAULT_PROVIDER'] = 'libvirt'
Vagrant.configure("2") do |config|
config.vm.provider :libvirt do |lv|
lv.loader = "/usr/share/OVMF/OVMF_CODE_4M.fd"
lv.disk_bus = "virtio"
lv.nic_model_type = "virtio"
end
end
EOF
# we will compress it to save space
sudo -E LIBGUESTFS_DISABLE_NETWORK=1 virt-sparsify --compress \
"${disk_path}${disk_name}" "${disk_path}${disk_name_final}"
sudo chown $USER:$USER "${disk_path}${disk_name_final}"
# 6. build the newer box file (install pigz in your host first)
tar -I pigz -cvf rhel10-custom.box \
metadata.json \
Vagrantfile \
-C "$disk_path" \
--transform "s/$disk_name_final/box.img/" \
"$disk_name_final"
# 7. add it back
vagrant box add custom/rhel10 rhel10-custom.box
Test the box with a VM
To test
mkdir ~/test_rhel10 && cd ~/test_rhel10
rm -rf .vagrant/
vagrant init custom/rhel10
vagrant up
Examples of Vagrantfile
Example using offline box
Vagrant.configure("2") do |config|
config.vm.box = "custom/rhel10"
nodes = [
["rhel1", "192.168.50.13"],
["rhel2", "192.168.50.14"]
]
nodes.each do |name, private_ip|
config.vm.define name do |node|
node.vm.hostname = name
# eth1: Configured interface for VM network only
node.vm.network "private_network",
ip: private_ip,
libvirt__network_name: "rhcsa-internal-net"
# eth2: Unconfigured interface for manual practice
node.vm.network "private_network",
libvirt__network_name: "rhcsa-practice-net",
type: "dhcp",
auto_config: false
node.vm.provider :libvirt do |lv|
lv.storage_pool_name = "qemu-storage"
lv.loader = "/usr/share/OVMF/OVMF_CODE_4M.fd"
lv.disk_bus = "virtio"
lv.nic_model_type = "virtio"
lv.memory = 2048
lv.cpus = 2
lv.channel :type => 'unix', :target_name => 'org.qemu.guest_agent.0', :target_type => 'virtio'
# Optional extra disks and attach ISO as CDROM
lv.storage :file, size: '10G', device: 'vdb'
lv.storage :file, size: '10G', device: 'vdc'
lv.storage :file,
device: :cdrom,
bus: :sata,
type: :raw,
path: '/mnt/data/ISO/rhel-10.1-x86_64-dvd.iso'
end
# delete bash history for root
config.vm.provision "shell", inline: <<-SHELL
find /root -name '.bash_history' -delete
history -c
SHELL
end
end
end
Example using box from vagrant registry
ENV['VAGRANT_DEFAULT_PROVIDER'] = 'libvirt'
Vagrant.configure("2") do |config|
config.vm.box = "georgeabr-linux/rhel10"
config.vm.box_version = "1.0.1"
nodes = [
["rhel1", "192.168.50.13"],
["rhel2", "192.168.50.14"]
]
nodes.each do |name, private_ip|
config.vm.define name do |node|
node.vm.hostname = name
# eth1: Configured interface for VM network only
node.vm.network "private_network",
ip: private_ip,
libvirt__network_name: "rhcsa-internal-net"
# eth2: Unconfigured interface for manual practice
node.vm.network "private_network",
libvirt__network_name: "rhcsa-practice-net",
type: "dhcp",
auto_config: false
node.vm.provider :libvirt do |lv|
lv.storage_pool_name = "qemu-storage"
lv.loader = "/usr/share/OVMF/OVMF_CODE_4M.fd"
lv.disk_bus = "virtio"
lv.nic_model_type = "virtio"
lv.memory = 2048
lv.cpus = 2
lv.channel :type => 'unix', :target_name => 'org.qemu.guest_agent.0', :target_type => 'virtio'
# Optional extra disks and attach ISO as CDROM
lv.storage :file, size: '10G', device: 'vdb'
lv.storage :file, size: '10G', device: 'vdc'
lv.storage :file,
device: :cdrom,
bus: :sata,
type: :raw,
path: '/mnt/data/ISO/rhel-10.1-x86_64-dvd.iso'
end
# delete bash history for root
# configure serial console, use Ctrl+a/e to navigate in grub menu, Ctrl+k to delete
config.vm.provision "shell", inline: <<-SHELL
grubby --update-kernel=ALL --args="console=ttyS0,115200"
systemctl enable --now serial-getty@ttyS0.service
find /root -name '.bash_history' -delete
history -c
SHELL
end
end
end
Getting guest information
You can use the below to get various guest information
# ping the guest
virsh qemu-agent-command $(virsh list --name | grep rhel1) '{"execute":"guest-ping"}'
# get networks details
virsh qemu-agent-command $(virsh list --name | grep rhel1) '{"execute":"guest-network-get-interfaces"}'
# freeze and unfreeze filesystem
virsh qemu-agent-command $(virsh list --name | grep rhel1) '{"execute":"guest-fsfreeze-freeze"}'
# unfreeze
virsh qemu-agent-command $(virsh list --name | grep rhel1) '{"execute":"guest-fsfreeze-thaw"}'
# List all available commands supported by the guest agent
virsh qemu-agent-command $(virsh list --name | grep rhel1) '{"execute":"guest-info"}'
# return all supported guest commands
virsh qemu-agent-command $(virsh list --name | grep rhel1) '{"execute":"guest-info"}' | jq .
# commands only
virsh qemu-agent-command $(virsh list --name | grep rhel1) '{"execute":"guest-info"}' | jq '.return.supported_commands[] | select(.enabled == true) | .name'
Connect to the guest console
We have enabled the console in the guest in the Vagrantfile
# use Ctrl+] to exit
virsh console $(virsh list --name | grep rhel1)
Troubleshooting
You can mount the disk and manipulate the files inside
sudo guestfish --rw -a /mnt/data/ISO/rhel10.1-final.qcow2 -i
Upload to public Vagrant registry
You can upload the resulting box here
https://portal.cloud.hashicorp.com/