By following this article to the end, you should be able to configure and use PXE and Kickstart server tools for Virtual Machine provisioning on KVM. KVM is an open source full virtualization solution for hardware architectures that support hardware virtualization. A library called libvirt provides a common API for managing KVM virtualization solution.
To create a fully automated installation of Virtual Machine on KVM, we need a network installation server that’s configured with TFTP, PXE, and a Kickstart file for initiating OS installation by booting from the network.
Preboot eXecution Environment (PXE) technology provides a mechanism for bootstrapping a computer using a network server. The computer where the operating system is installed should have a network interface that supports PXE, and the system firmware must enable PXE support.
The following list of services must be provided by the network boot infrastructure:
- TFTP Server – Provides the boot images to start the installer, with the command-line options.
- DHCP server – Provides initial client network configurations, and the location of TFTP server with a usable boot image
- Kickstart and Media server – This can be HTTP, FTP, or NFS server to provide the installation media and a shared Kickstart file for OS installation.
PXE Booting Process
The following series of actions takes place when using PXE for automated OS installation.
- A client machine at boot, through its network interface card, sends a DHCPDISCOVER broadcast packet in the network with required PXE options
- Configured DHCP server on the network will respond with a DHCPOFFER. The offer contains the IP address the client will get, and information about location of the PXE server.
- The client then responds with a DHCPREQUEST – requesting for IP assignment
- DHCP server sends a DHCPACK, with this is the TFTP (Trivial FTP) server URL of a network boot loader
- Client downloads the boot file from TFTP server and loads it (pxelinux.0)
- The loaded boot loader gets configuration file and Kickstart files from TFTP and HTTP/FTP/NFS server respectively. The files instructs it how to download and start OS installer.
- The files are used to boot the client
Configuring the PXE Boot Environment
Our setup is based on these configuration options.
| System type | Hostname | IP Address |
| DHCP/TFTP/HTTP Server | bastion-server.kvmlab.net | 192.168.177.2 |
| Kickstart server | bastion-server.kvmlab.net | 192.168.177.2 |
| Virtual Machine deployed via PXE | vm0.kvmlab.net | Assigned by DHCP Server |
KVM Hypervisor is required installed and configured on your host system. Check the guides in the following links for complete setup steps:
- Install KVM Hypervisor on Ubuntu
- How To Install KVM Hypervisor on Debian
- How To Install KVM on Fedora
- Install KVM on RHEL 8 / CentOS 8 / Rocky Linux 8 / AlmaLinux 8
- Install KVM on Arch Linux / Manjaro
Step 1 – Create Virtual Network (optional, you can use existing network without DHCP)
A bridge used should not have DHCP function as this will be handled by a DHCP Server virtual machine that we’ll install. These operations are to be performed on your KVM hypervisor host.
Ensure bridge utility package is installed.
### RHEL Based systems ###
sudo yum -y install vim bridge-utils
### Debian Based systems ###
sudo apt update
sudo apt install vim bridge-utils
Create a new virtual network creation configuration file.
vim br-pxe-net.xml
Modify these contents. You can replace IP information and bridge name to your liking and paste them inside the file.
<network>
<name>br-pxe</name>
<forward mode='nat'>
<nat>
<port start='1024' end='65535'/>
</nat>
</forward>
<bridge name='br-pxe' stp='on' delay='0'/>
<ip address='192.168.177.1' netmask='255.255.255.0'>
</ip>
</network>
Create a virtual network using this file file created; modify if need be:
$ sudo virsh net-define --file br-pxe-net.xml
Network br-pxe defined from br-pxe-net.xml
Enable automatic starting on the bridge created.
$ sudo virsh net-autostart br-pxe
Network br-pxe marked as autostarted
Then ensure the bridge interface is online
$ sudo virsh net-start br-pxe
Network br-pxe started
Confirm that the bridge is available and active:
$ sudo virsh net-list
Name State Autostart Persistent
--------------------------------------------
br-pxe active yes yes
default active yes yes
$ brctl show
bridge name bridge id STP enabled interfaces
br-pxe 8000.525400d64edf yes
virbr0 8000.525400929ef5 yes
OVS Bridge
If using Open vSwitch bridge instead of Linux bridge, network script configuration file will have contents similar to below
$ sudo vim /etc/sysconfig/network-scripts/ifcfg-br-pxe
DEVICE=br-pxe
BOOTPROTO=none
ONBOOT=yes
TYPE=OVSBridge
DEVICETYPE=ovs
USERCTL=yes
PEERDNS=yes
IPV6INIT=no
IPADDR=192.168.177.1
PREFIX=24
Start the interface and check IP configurations:
$ sudo ifup br-pxe
$ ip ad show dev br-pxe
12: br-pxe: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default qlen 1000
link/ether 52:54:00:d6:4e:df brd ff:ff:ff:ff:ff:ff
inet 192.168.78.1/24 brd 192.168.78.255 scope global br-pxe
valid_lft forever preferred_lft forever
Allowing nating using IPtables firewall:
sudo iptables -t nat -A POSTROUTING -o enp0s31f6 -s 192.168.78.1/24 -j MASQUERADE
Step 2 – Create Bastion Virtual Machine (PXE,TFTP,HTTP and Kickstart Server)
Here we create a Virtual Machine that runs services to support network boot infrastructure.
- Apache httpd web server
- DHCP Server
- PXE/TFTP services
Install libguestfs-tools package which provides virt-builder command line tool.
### RHEL Based systems ###
sudo yum -y install libguestfs-tools
### Fedora Linux systems ###
sudo dnf -y install guestfs-tools
### Debian Based systems ###
sudo apt -y install libguestfs-tools
List available OS templates:
sudo virt-builder -l
In this guide we’ll use Fedora 35 template. Let’s create
sudo virt-builder fedora-35 --format qcow2 \
--size 30G -o /var/lib/libvirt/images/kvm-bastion-server.qcow2 \
--root-password password:StrongRootPassw0rd
Where:
- fedora-35 is the template used to create a new virtual machine
- /var/lib/libvirt/images/kvm-bastion-server.qcow2 is the path to VM qcow2 image
- StrongRootPassw0rd is the root user password
Command execution output:
[ 0.6] Downloading: http://builder.libguestfs.org/fedora-35.xz
############################################################################################################################################################################################### 100.0%############################################################################################################################################################################################### 100.0%
[ 14.7] Planning how to build this image
[ 14.7] Uncompressing
[ 18.8] Resizing (using virt-resize) to expand the disk to 30.0G
[ 42.7] Opening the new disk
[ 47.2] Setting a random seed
[ 47.2] Setting passwords
[ 48.2] Finishing off
Output file: /var/lib/libvirt/images/kvm-bastion-server.qcow2
Output size: 30.0G
Output format: qcow2
Total usable space: 30.0G
Free space: 28.9G (96%)
Create Virtual Machine from generated image.
Using Linux bridge:
sudo virt-install \
--name kvm-bastion-server \
--ram 2048 \
--vcpus 2 \
--disk path=/var/lib/libvirt/images/kvm-bastion-server.qcow2 \
--os-type linux \
--os-variant rhel8.0 \
--network bridge=br-pxe \
--graphics none \
--serial pty \
--console pty \
--boot hd \
--import
Using openVSwitch bridge: Ref How To Use Open vSwitch Bridge on KVM Virtual Machines
sudo virt-install \
--name bastion-server \
--ram 2048 \
--disk path=/var/lib/libvirt/images/kvm-bastion-server.qcow2 \
--vcpus 2 \
--os-type linux \
--os-variant rhel8.0 \
--network=bridge:br-pxe,model=virtio,virtualport_type=openvswitch \
--graphics none \
--serial pty \
--console pty \
--boot hd \
--import
Our bastion VM creation process is started. Once ready login with root and assigned password:
...omitted output ...
[ OK ] Reached target Preparation for Network.
Starting Network Manager...
[ OK ] Started Network Manager.
[ OK ] Reached target Network.
Starting Network Manager Wait Online...
Starting Permit User Sessions...
Starting Hostname Service...
[ OK ] Finished Permit User Sessions.
Starting Hold until boot process finishes up...
Starting Terminate Plymouth Boot Screen...
[ 4.160192] IPv6: ADDRCONF(NETDEV_CHANGE): enp1s0: link becomes ready
Fedora Linux 35 (Thirty Five)
Kernel 5.14.10-300.fc35.x86_64 on an x86_64 (ttyS0)
fedora login: root
Password: <INPUT-ROOT-USER-PASSWORD>
Reset root password:
[[email protected] ~]# passwd
Changing password for user root.
New password:
Retype new password:
passwd: all authentication tokens updated successfully.
You will notice the Virtual Machine doesn’t have IP address. The reason is that we disabled DHCP service in our natted bridge.
[[email protected] ~]# ip ad
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: enp1s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether 52:54:00:0d:c3:34 brd ff:ff:ff:ff:ff:ff
inet6 fe80::5054:ff:fe0d:c334/64 scope link noprefixroute
valid_lft forever preferred_lft forever
$ nmcli con show
NAME UUID TYPE DEVICE
enp1s0 50bdb463-8d8f-4053-9e3d-b3bbea6f5b1e ethernet --
Let’s create assign an IP address to the interface using NMCLI tool.
sudo nmcli con mod enp1s0 \
ipv4.method manual \
ipv4.address 192.168.177.2/24 \
ipv4.gateway 192.168.177.1 \
ipv4.dns 192.168.177.1 \
connection.autoconnect yes
Restart the interface to ensure it uses configured settings:
$ sudo nmcli con down enp1s0 && sudo nmcli con up enp1s0
Connection 'enp1s0' successfully deactivated (D-Bus active path: /org/freedesktop/NetworkManager/ActiveConnection/5)
Connection successfully activated (D-Bus active path: /org/freedesktop/NetworkManager/ActiveConnection/6)
Check network configs:
$ nmcli con show
NAME UUID TYPE DEVICE
enp1s0 50bdb463-8d8f-4053-9e3d-b3bbea6f5b1e ethernet enp1s0
$ ip ad
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: enp1s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether 52:54:00:0d:c3:34 brd ff:ff:ff:ff:ff:ff
inet 192.168.177.2/24 brd 192.168.177.255 scope global noprefixroute enp1s0
valid_lft forever preferred_lft forever
inet6 fe80::5054:ff:fe0d:c334/64 scope link noprefixroute
valid_lft forever preferred_lft forever
Next we set the correct hostname of our Bastion server
sudo hostnamectl set-hostname bastion-server.kvmlab.net
Set correct timezone as well
sudo timedatectl set-timezone Africa/Nairobi
sudo chronyc sources
Enable Virtual Machine automatic start if you want on KVM:
sudo virsh autostart kvm-bastion-server
Step 3 – Setup DHCP Server for PXE Booting environment
Perform OS upgrade before deploying other services. The server being used here in this guide is Fedora.
sudo dnf -y upgrade
sudo dnf -y install git vim wget curl bash-completion tree tar libselinux-python3 firewalld
Reboot the server after the upgrade is done.
sudo reboot
Install DHCP server package
sudo dnf -y install dhcp-server
Move original DHCP server configuration file to backup:
sudo mv /etc/dhcp/dhcpd.conf{,.bak}
Create a new DHCP server configuration file
sudo vim /etc/dhcp/dhcpd.conf
Modify and paste the following data into the file
authoritative;
ddns-update-style interim;
default-lease-time 14400;
max-lease-time 14400;
allow booting;
allow bootp;
#Default gateway
option routers 192.168.177.1;
#DHCP network broadcast address
option broadcast-address 192.168.177.255;
#DHCP network subnet mask
option subnet-mask 255.255.255.0;
#IP address of DNS server
option domain-name-servers 192.168.177.1;
#Default NTP server
option ntp-servers time.google.com;
#Default domain name
option domain-name "kvmlab.net";
subnet 192.168.177.0 netmask 255.255.255.0 {
range 192.168.177.20 192.168.177.50;
# this is PXE specific
filename "pxelinux.0";
#PXE Server IP Address
next-server 192.168.177.2;
}
Save the configs and start dhcpd service:
$ sudo systemctl enable --now dhcpd
Created symlink /etc/systemd/system/multi-user.target.wants/dhcpd.service → /usr/lib/systemd/system/dhcpd.service.
Check service status to confirm it started without errors:
$ systemctl status dhcpd
● dhcpd.service - DHCPv4 Server Daemon
Loaded: loaded (/usr/lib/systemd/system/dhcpd.service; enabled; vendor preset: disabled)
Active: active (running) since Fri 2022-02-04 20:31:01 EAT; 23s ago
Docs: man:dhcpd(8)
man:dhcpd.conf(5)
Main PID: 3187 (dhcpd)
Status: "Dispatching packets..."
Tasks: 1 (limit: 2328)
Memory: 9.9M
CPU: 13ms
CGroup: /system.slice/dhcpd.service
└─3187 /usr/sbin/dhcpd -f -cf /etc/dhcp/dhcpd.conf -user dhcpd -group dhcpd --no-pid
Feb 04 20:31:01 bastion.kvmlab.net dhcpd[3187]: Config file: /etc/dhcp/dhcpd.conf
Feb 04 20:31:01 bastion.kvmlab.net dhcpd[3187]: Database file: /var/lib/dhcpd/dhcpd.leases
Feb 04 20:31:01 bastion.kvmlab.net dhcpd[3187]: PID file: /var/run/dhcpd.pid
Feb 04 20:31:01 bastion.kvmlab.net dhcpd[3187]: Source compiled to use binary-leases
Feb 04 20:31:01 bastion.kvmlab.net dhcpd[3187]: Wrote 0 leases to leases file.
Feb 04 20:31:01 bastion.kvmlab.net dhcpd[3187]: Listening on LPF/enp1s0/52:54:00:0d:c3:34/192.168.177.0/24
Feb 04 20:31:01 bastion.kvmlab.net dhcpd[3187]: Sending on LPF/enp1s0/52:54:00:0d:c3:34/192.168.177.0/24
Feb 04 20:31:01 bastion.kvmlab.net dhcpd[3187]: Sending on Socket/fallback/fallback-net
Feb 04 20:31:01 bastion.kvmlab.net dhcpd[3187]: Server starting service.
Feb 04 20:31:01 bastion.kvmlab.net systemd[1]: Started DHCPv4 Server Daemon.
Allow DHCPD service on the firewall
sudo firewall-cmd --add-service=dhcp --permanent
sudo firewall-cmd --reload
Step 4 – Configure TFTP and PXE environment
Install TFTP server and syslinux packages on the bastion host.
sudo dnf -y install tftp-server syslinux
Start and enable TFTP service using systemctl command:
sudo systemctl enable --now tftp.socket
Allow tftp service on firewall:
sudo firewall-cmd --add-service=tftp --permanent
sudo firewall-cmd --reload
Copy initial configurations files and create required directories:
sudo cp /usr/share/syslinux/pxelinux.0 /var/lib/tftpboot/
sudo cp /usr/share/syslinux/{menu.c32,vesamenu.c32,ldlinux.c32,libcom32.c32,libutil.c32} /var/lib/tftpboot/
sudo mkdir /var/lib/tftpboot/pxelinux.cfg
The pxelinux.cfg directory contains the default configuration file used by any system PXE-booting from this TFTP service.
Step 5 – Install and configure httpd web server
A web server is needed to host installation media files used during PXE boot process. For this guide we’ve chosen Apache httpd as a web server of choice.
sudo dnf -y install httpd
Let’s edit httpd configuration file and set Listen port to 8080. This is to avoid any conflicts with other applications that can be hosted on the same server using port 80.
$ sudo vi /etc/httpd/conf/httpd.conf
#Around line 45
Listen 8080
Start and enable httpd service
sudo systemctl enable --now httpd
Allow port 8080 if you have firewalld service active on the server.