Raspberry Pi 3B+ diskless boot via ISCSI Link to heading
Raspios 64-bit recently went GA and I recently found new joy in my Raspberry Pi 3B+ and Raspberry Pi Zero 2 W devices.
Now feels like a great time to revisit diskless booting.
This is what I’m using:
- UniFi for DHCP
- QNAP NAS for TFTP and ISCSI
- Raspberry Pi 3B+
Install RaspiOS 64-bit Link to heading
Use rpi-imager
Enable USB Boot Mode Link to heading
The devices that I have are 4-6 years old. I’m not sure if new ones will have this set already or not.
Run this command, the “OTP dump”, to see if usb_boot_mode is enabled:
vcgencmd otp_dump | grep 17:
This is what my OTP dump looked like before usb_boot_mode enabled.
17:1020000a
If the output looks like this, get the latest firmware:
sudo rpi-update
I was a little nervous about the rpi-update
, but I’m also following my own notes.
*** Raspberry Pi firmware updater by Hexxeh, enhanced by AndrewS and Dom
*** Performing self-update
*** Relaunching after update
*** Raspberry Pi firmware updater by Hexxeh, enhanced by AndrewS and Dom
*** We're running for the first time
*** Backing up files (this will take a few minutes)
*** Backing up firmware
*** Backing up modules 5.10.63-v8+
#############################################################
WARNING: This update bumps to rpi-5.15.y linux tree
See: https://forums.raspberrypi.com/viewtopic.php?t=322879
'rpi-update' should only be used if there is a specific
reason to do so - for example, a request by a Raspberry Pi
engineer or if you want to help the testing effort
and are comfortable with restoring if there are regressions.
DO NOT use 'rpi-update' as part of a regular update process.
##############################################################
Would you like to proceed? (y/N)
*** Downloading specific firmware revision (this will take a few minutes)
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 173 100 173 0 0 678 0 --:--:-- --:--:-- --:--:-- 678
100 123M 0 123M 0 0 4052k 0 --:--:-- 0:00:31 --:--:-- 3014k
*** Updating firmware
*** Updating kernel modules
*** depmod 5.15.21-v8+
*** depmod 5.15.21-v7+
*** depmod 5.15.21-v7l+
*** depmod 5.15.21+
*** Updating VideoCore libraries
*** Using SoftFP libraries
*** Updating SDK
*** Running ldconfig
*** Storing current firmware revision
*** Deleting downloaded files
*** Syncing changes to disk
*** If no errors appeared, your firmware was successfully updated to fbcab73d5a20f592aa9f6b0e364757ef131dae27
*** A reboot is needed to activate the new firmware
After rebooting from the rpi-update
we still need to enable usb_boot_mode, just needed the latest firmware to do so.
Add this line to /boot/config.txt
it doesn’t matter where, but needs its own line. I put it at the top.
program_usb_boot_mode=1
Then reboot the device again. Now the OTP dump should look like this:
17:3020000a
Now that the OTP dump is set, it is safe to remove the program_usb_boot_mode=1
line from /boot/config.txt
file. There is no need to reboot, but if you remove the line, and reboot, you will see that the OTP dump still shows usb_boot_mode enabled.
Enable ISCSI module with initramfs tool Link to heading
These are the steps I took:
# Update repositories
sudo apt update
# Install available upgrades
sudo apt upgrade -y
# Install open-iscsi
sudo apt install open-iscsi
# Start open-iscsi to create the configs that we wil use later
sudo systemctl start open-iscsi
# Create initramfs flag file for the iscsi module
sudo touch /etc/iscsi/iscsi.initramfs
# Create a new initramfs image that includes the iscsi module from the current kernel
sudo update-initramfs -v -k `uname -r` -c
You should now have a initrd.img
in your /boot
directory.
ls -latr /boot/initrd.img*
The output should look like this:
-rwxr-xr-x 1 root root 10487750 Feb 11 10:59 /boot/initrd.img-5.15.21-v8+
We will refer to this new initrd.img
file later!
Add TFTP options to DHCP server Link to heading
At this point, the bootloader on our device will accept a network boot, similar to PXE, but not exactly the same.
Setup your DHCP server to point to your TFTP server. The DHCP protocol has Option 66
for TFTP Server. Basically, you want to tell your DHCP client (the Raspberry Pi 3B+) which server to connect to next, to get further instructions.
Setup TFTP Server Link to heading
The network boot process for the Raspberry Pi 3B+ is PXE-like. So it requires a slightly custom setup. It requires the serial number of the device, to be known in advance.
Grab the serial number of the device:
grep Serial /proc/cpuinfo
In my case it looks like this:
Serial : 000000002edd5668
On the TFTP server, create a directory for the device. In the case of my QNAP server, the TFTP root is at /share/tftp
but I’ll use the variable TFTP_ROOT
.
# Set the implementation specific TFTP_ROOT
export TFTP_ROOT=/share/tftp
# Create directory using the device serial number, without leading 0s
sudo mkdir $TFTP_ROOT/2edd5668
Now, from the TFTP server, copy over the /boot
partition that includes our new kernel.
sudo scp -r pi@2edd5668:/boot/* $TFTP_ROOT/2edd5668/
If you want to check your work, the directory should look something like this:
COPYING.linux* bcm2708-rpi-cm.dtb* bcm2710-rpi-3-b-plus.dtb* bcm2711-rpi-4-b.dtb* cmdline.txt* fixup4db.dat* initrd.img-5.15.21-v8+* kernel8.img* start4db.elf*
LICENCE.broadcom* bcm2708-rpi-zero-w.dtb* bcm2710-rpi-3-b.dtb* bcm2711-rpi-400.dtb* config.txt* fixup4x.dat* issue.txt* overlays/ start4x.elf*
bcm2708-rpi-b-plus.dtb* bcm2708-rpi-zero.dtb* bcm2710-rpi-cm3.dtb* bcm2711-rpi-cm4.dtb* fixup.dat* fixup_cd.dat* kernel.img* start.elf* start_cd.elf*
bcm2708-rpi-b-rev1.dtb* bcm2709-rpi-2-b.dtb* bcm2710-rpi-zero-2-w.dtb* bcm2711-rpi-cm4s.dtb* fixup4.dat* fixup_db.dat* kernel7.img* start4.elf* start_db.elf*
bcm2708-rpi-b.dtb* bcm2710-rpi-2-b.dtb* bcm2710-rpi-zero-2.dtb* bootcode.bin* fixup4cd.dat* fixup_x.dat* kernel7l.img* start4cd.elf* start_x.elf*
Now, because this is PXE-like, we need to copy the bootcode.bin
to the TFTP_ROOT
sudo cp $TFTP_ROOT/2edd5668/bootcode.bin $TFTP_ROOT/
At this point, without an SD Card installed, the Raspberry Pi 3B+ will:
- Ask the DHCP server for an IP address
- Our DHCP server will provide an IP address AND tell the device to visit the TFTP server for more info
- Our device will ask the TFTP server for $TFTP_ROOT/bootcode.bin
- Our device will try to boot from $TFTP_ROOT/2edd5668/
At this point though, our $TFTP_ROOT/2edd5668/
exactly matches what is on the SD Card in our device. We need to tell the device to use our new ISCSI enabled kernel.
I added the following line to the bottom of $TFTP_ROOT/2edd5668/config.txt
, but you should use your specific initrd.img
version.
initramfs initrd.img-5.15.21-v8+ followkernel
Create ISCSI LUN for the device Link to heading
For simplicity, I’m creating a 32GB LUN, with “thick” provisioning on my NAS, and mapping that to an ISCSI Target. I also used the serial number in the naming of the LUN and ISCI target.
To verify that your new ISCSI LUN is accessible we will connect to it from the Raspberry Pi 3B+.
# set the IP address of the ISCSI server
export ISCSI_IP=192.168.1.248
#
sudo iscsiadm -m discovery -t sendtargets -p $ISCSI_IP | grep 2edd5668
The output should contain the new ISCSI target.
192.168.1.248:3260,1 iqn.2004-04.com.qnap:ts-231p:iscsi.2edd5668.21250c
From above the TARGET_IQN is iqn.2004-04.com.qnap:ts-231p:iscsi.2edd5668.21250c
and I create an enviroment varialbe to store it.
export TARGET_IQN=iqn.2004-04.com.qnap:ts-231p:iscsi.2edd5668.21250c
Mount the ISCSI LUN to the Raspberry Pi Link to heading
Login to the ISCSI LUN, this example is not using CHAP authentication.
sudo iscsiadm -m node -l -T $TARGET_IQN -p $ISCSI_IP
Success should look similar to this:
Logging in to [iface: default, target: iqn.2004-04.com.qnap:ts-231p:iscsi.2edd5668.21250c, portal: 192.168.1.248,3260]
Login to [iface: default, target: iqn.2004-04.com.qnap:ts-231p:iscsi.2edd5668.21250c, portal: 192.168.1.248,3260] successful.
I validate the new ISCSI connection using sudo fdisk -l
from the Raspberry Pi 3B+ and look for something like this, showing the 32GB LUN attached via ISCSI
Disk /dev/sda: 32 GiB, 34359738368 bytes, 67108864 sectors
Disk model: iSCSI Storage
Units: sectors of 1 * 512: 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 1048576 bytes / 1048576 bytes
The LUN is showing up as /dev/sda
in this case.
# Format the LUN attached as /dev/sda to ext4
sudo mkfs.ext4 /dev/sda
mke2fs 1.46.2 (28-Feb-2021)
Creating filesystem with 8388608 4k blocks and 2097152 inodes
Filesystem UUID: f581fa52-f7ea-4c87-91cc-e236d64f63a6
Superblock backups stored on blocks:
32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632, 2654208,
4096000, 7962624
Allocating group tables: done
Writing inode tables: done
Creating journal (65536 blocks): done
Writing superblocks and filesystem accounting information: done
# Get the UUID of the LUN
sudo blkid /dev/sda
/dev/sda: UUID="f581fa52-f7ea-4c87-91cc-e236d64f63a6" BLOCK_SIZE="4096" TYPE="ext4"
# Mount the new disk
sudo mount /dev/sda /mnt
Copy the existing installation to the mounted LUN Link to heading
This step takes some time. We are copying all of the files from the /
partition of the SD card on the Raspberry Pi 3B+ to the network attached LUN. We are excluding the directories that are mounted during boot.
# Copy / partition to /mnt
sudo rsync -avhP --exclude /boot --exclude /proc --exclude /sys --exclude /dev --exclude /mnt / /mnt/
# Create directories that are mounted during boot
sudo mkdir /mnt/{dev,proc,sys,boot,mnt}
Remove SD Card details from /etc/fstab Link to heading
cat /mnt/etc/fstab
Before:
proc /proc proc defaults 0 0
PARTUUID=9633a3a2-01 /boot vfat defaults 0 2
PARTUUID=9633a3a2-02 / ext4 defaults,noatime 0 1
# a swapfile is not a swap partition, no line here
# use dphys-swapfile swap[on|off] for that
Remove the lines containing PARTUUID
which are referring to the SDCard.
After:
proc /proc proc defaults 0 0
# a swapfile is not a swap partition, no line here
# use dphys-swapfile swap[on|off] for that
Update the kernel configs on TFTP server Link to heading
Now that we have updated our filesystem. We need to revisit kernel that is getting loaded over the network. We need to tell it how to mount the /
filesystem via ISCSI.
The ISCSI Initiator, which identifies the Raspberry Pi 3B+ device, can be found at /etc/iscsi/initiatorname.iscsi
sudo cat /etc/iscsi/initiatorname.iscsi
## DO NOT EDIT OR REMOVE THIS FILE!
## If you remove this file, the iSCSI daemon will not start.
## If you change the InitiatorName, existing access control lists
## may reject this initiator. The InitiatorName must be unique
## for each iSCSI initiator. Do NOT duplicate iSCSI InitiatorNames.
InitiatorName=iqn.1993-08.org.debian:01:2ccb20a316
On the TFTP server, I’m modifying the $TFTP_ROOT/2edd5668/cmdline.txt
file.
I’m adding/updating these details for mounting the ISCSI drive:
- ip=::::2edd5668:eth0:dhcp
- hostname of the device
- root=UUID=f581fa52-f7ea-4c87-91cc-e236d64f63a6
- From the blkid command against the LUN
- ISCSI_INITIATOR=iqn.1993-08.org.debian:01:2ccb20a316
- ISCSI_TARGET_NAME=iqn.2004-04.com.qnap:ts-231p:iscsi.2edd5668.21250c
- ISCSI_TARGET_IP=192.168.1.248
- ISCSI_TARGET_PORT=3260
- rw
Before:
console=serial0,115200 console=tty1 root=PARTUUID=9633a3a2-02 rootfstype=ext4 fsck.repair=yes rootwait cgroup_enable=cpuset cgroup_enable=memory cgroup_enable=pids cgroup_memory=1 systemd.unified_cgroup_hierarchy=0
After:
console=serial0,115200 console=tty1 root=PARTUUID=9633a3a2-02 rootfstype=ext4 fsck.repair=yes rootwait cgroup_enable=cpuset cgroup_enable=memory cgroup_enable=pids cgroup_memory=1 systemd.unified_cgroup_hierarchy=0 ISCSI_INITIATOR=iqn.1993-08.org.debian:01:2ccb20a316 ISCSI_TARGET_NAME=iqn.2004-04.com.qnap:ts-231p:iscsi.2edd5668.21250c ISCSI_TARGET_IP=192.168.1.248 ISCSI_TARGET_PORT=3260 rw
This all needs to be on a single line.
Ready to reboot! Link to heading
From the Raspberry Pi 3B+ device:
# Unmount the drive
sudo umount /mnt
# Disconnect from ISCSI
sudo iscsiadm -m node --logout all
# Shutdown
sudo shutdown now
Remove the SD card and restart the device!
Summary Link to heading
If you were paying close attention, you may have notice that the cmdline.txt
also had some non-default settings in it. Those are there to allow k3s
to run as well.
This article is very specific to the Raspberry Pi 3B+ and using ISCSI. Look for a follow-up example using the Raspberry Pi 4.
Soon, I’ll document a diskless-pipeline for setting up Raspberry Pi 3B+ and Raspberry Pi 4, that includes using arkade
and k3sup
to configure the kubernetes abstraction layer across all of these homelab devices.