PXE from an nspawn container
This post will cover the provisioning of a nspawn container and the installation of a tftp-hpa
serving pxelinux.0
. While this sounds simple enough, and there are millions of other posts just like this one, I will be covering how I set everything up in nspawn using my Edge Router Lite (ERL3) for DHCP.
Provisioning the container
-
Create a rootfs for the container.
machinectl clone xenial-base pxe-server
-
Generate a unit file for the new container config.
cat > /etc/systemd/nspawn/pxe-server.nspawn <<EOF
[Exec]
Boot=on
[Network]
VirtualEthernet=yes
Bridge=br0
EOF
-
Reload the systemd daemon.
systemctl daemon-reload
-
Ensure the container is started at system boot (optional)
systemctl enable systemd-nspawn@pxe-server
-
Start the new container.
machinectl start pxe-server
Installing the needed software
The first thing to do is access the container.
machinectl shell root@pxe-server -- /bin/bash
Before getting started export a couple of environment variables into the working shell. This is done to simplify commands and future references to variables set in configuration files.
export tftpboot_dir="/var/lib/tftpboot"
export tftp_server="172.16.24.90"
export tftp_ssh_key="XXX"
export image_url="http://releases.ubuntu.com/16.04.2/ubuntu-16.04.2-server-amd64.iso"
export image_name="$(basename ${image_url})"
export image_short_name="$(basename -s .iso ${image_name})"
NOTE: These variables are used in different places as simple references throughout this post. Be mindful of these options and make the needed changes to the configuration files as needed.
Package installation
The first thing to do is install the set of packages we need, then we'll configure it all.
apt install -y tftpd-hpa inetutils-inetd nginx p7zip-full p7zip-rar
Web Server setup
Create an NGINX config for the PXE files being served over the network. The default sites-enabled file, at /etc/nginx/sites-enabled/default
.
server {
listen 80 default_server;
listen [::]:80 default_server;
root /var/www/pxe;
location / {
autoindex on;
}
}
After the configuration file is in place, restart NGINX to ensure it takes effect.
systemctl restart nginx
TFTP-HPA setup
Before we're able to meaningfully use TFTP we'll need to create the defaults file for the service and then we'll need to setup inetd.
These are the default options I use which are contained within the /etc/default/tftpd-hpa
file.
TFTP_USERNAME="tftp"
TFTP_DIRECTORY="/var/lib/tftpboot"
TFTP_ADDRESS=":69"
TFTP_OPTIONS="--secure"
RUN_DAEMON="yes"
OPTIONS="-l -s /var/lib/tftpboot"
And here is my inetd configuration file located at /etc/inetd.conf
.
tftp dgram udp wait root /usr/sbin/in.tftpd /usr/sbin/in.tftpd -s /var/lib/tftpboot
With those changes taken care of, restart the services to ensure they've gone into effect:
systemctl restart tftpd-hpa
systemctl restart inetutils-inetd
PXE server setup
To get our web server serving the correct files used to PXE boot our hosts we're going to need to create a couple directories.
mkdir -p /var/www/pxe
mkdir -p /var/www/pxe/scripts
mkdir -p /var/www/pxe/networking
mkdir -p /var/www/pxe/iso
mkdir -p /var/www/pxe/images
mkdir -p "${tftpboot_dir}"
mkdir -p "${tftpboot_dir}/boot-screens"
mkdir -p "${tftpboot_dir}/preseed"
mkdir -p "${tftpboot_dir}/pxelinux.cfg"
Change directories to the /var/www/pxe
and download the latest stable pxelinux release. At the time of this writing 6.03 was the latest stable tarball. Once you've downloaded the file and extract the contents in place.
wget https://www.kernel.org/pub/linux/utils/boot/syslinux/syslinux-6.03.tar.gz
tar xf syslinux-6.03.tar.gz
Change directories into the newly created syslinux-6.03
directory so that we can begin hard linking files from this download into the appropriate locations.
ln -f /var/www/pxe/syslinux-6.03/bios/com32/elflink/ldlinux/ldlinux.c32 "${tftpboot_dir}/ldlinux.c32"
ln -f /var/www/pxe/syslinux-6.03/bios/core/pxelinux.0 "${tftpboot_dir}/pxelinux.0"
ln -f /var/www/pxe/syslinux-6.03/bios/com32/lib/libcom32.c32 "${tftpboot_dir}/boot-screens/libcom32.c32"
ln -f /var/www/pxe/syslinux-6.03/bios/com32/libutil/libutil.c32 "${tftpboot_dir}/boot-screens/libutil.c32"
ln -f /var/www/pxe/syslinux-6.03/bios/com32/menu/vesamenu.c32 "${tftpboot_dir}/boot-screens/vesamenu.c32"
Change directories into ${tftpboot_dir}
and create a couple files used to instruct our PXE server how to handle the normal boot process.
The first file we need is a default config which will load our normal menu. Create the file ${tftpboot_dir}/pxelinux.cfg/default
with the following contents:
path boot-screens
include boot-screens/menu.cfg
default boot-screens/vesamenu.c32
prompt 0
timeout 100
NOTE: In the
${tftpboot_dir}/pxelinux.cfg/default
file be mindful or trailing white space as it will break in unexpected ways if you have any.
Change directories to the ${tftpboot_dir}/boot-screens
and hard Link the default file to the syslinux.cfg file found in the boot-screens directory.
ln -f ../pxelinux.cfg/default syslinux.cfg
Image setup
Change your working directory to /var/www/pxe/iso
and download the image.
wget "${image_url}" -O "${image_name}"
Change directories to /var/www/pxe/images
, create the image directory, and extract the contents of the ISO in place.
mkdir "${image_short_name}"
pushd "${image_short_name}"
7z x "/var/www/pxe/iso/${image_name}"
popd
Bind mount the netboot-installer
directory into our tftpboot directory. We're using a bind mount here because a symbolic link won't work and I don't want to copy the files creating duplicates of things I already have.
mount -o bind "/var/www/pxe/images/${image_short_name}/install/netboot/ubuntu-installer" "${tftpboot_dir}/${image_short_name}"
To make the bind mount persistent you have two options:
- Save a line in the
/etc/fstab
file.
echo "/var/www/pxe/images/${image_short_name}/install/netboot/ubuntu-installer ${tftpboot_dir}/${image_short_name} none defaults,bind 0 0" | tee -a /etc/fstab
- Create a systemd unit file for the mount at
/etc/systemd/system/$(systemd-escape -p --suffix=mount "${tftpboot_dir}/${image_short_name}")
NOTE: the
systemd-escape
command is required when creating systemd "mount" unit files.
[Mount]
What=/var/www/pxe/images/${image_short_name}/install/netboot/ubuntu-installer
Where=${tftpboot_dir}/${image_short_name}
Type=bind
Basic boot menu
Create the menu configuration we'll use to local boot by default and provide provisioning options when a server is powered on. Within the same ${tftpboot_dir}/boot-screens
directory create the menu.cfg
file.
menu hshift 13
menu width 49
menu margin 8
menu tabmsg
menu title Boot Menu
label local
menu label ^Boot local hard drive
LOCALBOOT 0
label auto-ubuntu-16.04
menu label ^Ubuntu 16.04
kernel ${image_short_name}/amd64/linux
append biosdevname=0 net.ifnames=0 auto=true priority=critical vga=789 initrd=${image_short_name}/amd64/initrd.gz preseed/url=http://${tftp_server}/images/ubuntu-16.04.2-server-amd64/preseed/ubuntu-server.seed netcfg/choose_interface=eth0
menu end
NOTE: In the
${tftpboot_dir}/boot-screens/menu.cfg
file be mindful or trailing white space as it will break in unexpected ways if you have any.
Finally set the ownership of our directories.
find /var/www/pxe -type d -exec chmod -R 0755 {} \;
chown -R root:root "${tftpboot_dir}"
chown -R www-data:www-data /var/www
Setting up the ERL3
The ERL3, is the edge device responsible for all DHCP, DNS, and a few other services within my network. So to get PXE going within my home I'm going to configure the DHCP server to provide the boot file and then target the tftp server which is now running within our nspawn container.
First SSH to the ELR3 then edit the configuration
configure # drops you into the configuration terminal
Set the boot file.
set service dhcp-server shared-network-name LAN1 subnet ${ADDRESS_CIDR} bootfile-name pxelinux.0
Set the boot server.
set service dhcp-server shared-network-name LAN1 subnet ${ADDRESS_CIDR} bootfile-server ${ADDRESS_OF_TFTP_SERVER}
Set the TFTP server to the same as the bootfile-server (assuming they're the same in your case)
set service dhcp-server shared-network-name LAN1 subnet ${ADDRESS_CIDR} tftp-server-name ${ADDRESS_OF_TFTP_SERVER}
Now commit the changes and save
commit
save
exit
Once you've exited the configuration terminal, Log out of the ERL3.
exit
Testing it all out
Now that we've got our PXE server all setup and our DHCP server running let's fire up a machine on the same network and make sure it's all working.
Wrap-up
In this post, we spawned a container, created our tftp boot server serving pxelinux, set everything up for local boot by default with the option to install a base server OS, & configured a Ubiquity Edge Router to forward boot instructions to clients. In my next post, I'll cover an automated installation of Ubuntu which I use for both Host and VM provisioning. Stay tuned as I dive deeper into preseeds using Software RAID, setting up boot repositories, creating networks, and more.