Raspberry Pi - iSCSI RootFS

Introduction

The steps below are to enable booting a Raspberry Pi from an SD card that holds an initrd enabled kernel. Once the kernel boots and hands over to the initrd a wireless network link is brought up and then the root filesystem is mounted from an iSCSI target.

Once the root filesystem is mounted there is no further interaction with the SD card.

The system is a little less responsive to filesystem operations as everything has to traverse the wireless link but otherwise functions as a normal Raspberry Pi installation.

This method could be modified to use an NFS root file system too. Using the ip= kernel command line options won't work with a WiFi interface because the kernel seems to try to bring up an interface before it even gets to the boot scripts in the initramfs, and the kernel doesn't know anything about wpa_supplicant.

For all of this I built the kernel on my desktop machine using the cross-compile tool chain. This cuts down on the time it takes but it means at some steps in the process below you need to copy files to/from the RPi. See Raspberry Pi Kernel Compilation for a good set of instructions on building the kernel.

So far this works, but shutdown or reboot from the command line don't quite work. Everything shuts down smoothly but then hangs at the end of the process with a message about running pstree. The Raspberry Pi will have to be power cycled to reboot it.

I suspect this is because there is some state information from the initramfs relating to iscsi that needs to be migrated across before the switch_root (or run-init) command is called at the end of the initramfs init script.

If anyone has any suggestions about cleaning up the reboot/shutdown process I'd love to hear them.

This was all done using Raspbian.


1. Build the latest kernel

This is probably not really needed. Make sure you include iscsi support as built-in and install it with all the modules onto the RPi. Make sure you can boot this kernel successfully.

Note: To get a working initrd/initramfs I had to build the kernel to include the initramfs image in the kernel image, steps further down explain

2. Create /etc/initramfs-tools/hooks/wlan

This script to copies wpa_supplicant, config files and modules into the initramfs image.

#!/bin/sh

PREREQ=""

prereqs()
{
	echo "$PREREQ"
}

case $1 in
# get pre-requisites
prereqs)
	prereqs
	exit 0
	;;
esac

if [ ! -e /etc/wpa_supplicant/wpa_supplicant.conf ]; then
	exit 0
fi

. /usr/share/initramfs-tools/hook-functions

copy_exec /sbin/wpa_action /sbin
copy_exec /sbin/wpa_cli /sbin
copy_exec /sbin/wpa_supplicant /sbin

mkdir -p $DESTDIR/etc/network/
mkdir -p $DESTDIR/etc/wpa_supplicant/

cp /etc/wpa_supplicant/action_wpa.sh $DESTDIR/etc/wpa_supplicant/
cp /etc/wpa_supplicant/functions.sh $DESTDIR/etc/wpa_supplicant/
cp /etc/wpa_supplicant/ifupdown.sh $DESTDIR/etc/wpa_supplicant/
cp /etc/wpa_supplicant/wpa_supplicant.conf $DESTDIR/etc/wpa_supplicant/
cp /etc/network/interfaces $DESTDIR/etc/network/

for x in crc_ccitt rt2800usb rt2800lib rt2x00usb rt2x00lib mac80211 cfg 80211 rfkill led_class; do
	manual_add_modules ${x}
done

3. Create /etc/initramfs-tools/scripts/local-top/wlan

#!/bin/sh
# Boot script to bring up wlan interface using wpa_supplicant before rest of boot process

PREREQ="udev"
prereqs()
{
	echo "$PREREQ"
}

case $1 in
	prereqs)
        	prereqs
                exit 0
                ;;
	esac

# Begin real processing below this line
if [ ! -x "/sbin/wpa_supplicant" ]; then
	panic "wpa_supplicant executable not found"
fi

. /conf/initramfs.conf

for conf in conf/conf.d/*; do
        [ -f ${conf} ] && . ${conf}
done
. /scripts/functions


log_begin_msg "Starting wpa_supplicant for ${DEVICE}"
	/sbin/wpa_supplicant -B -Dwext -i${DEVICE} -C /run/wpa_supplicant/ -c /etc/wpa_supplicant/wpa_supplicant.conf
	sleep 5
	if [ ! -e "/run/wpa_supplicant/${DEVICE}" ]; then
		log_failure_msg "wpa_supplicant failed for ${DEVICE}"
		panic "wpa_supplicant failed for ${DEVICE}"
	fi
log_end_msg

log_begin_msg "Starting IP configuration for ${DEVICE}"

	for ROUNDTTT in 2 3 4 6 9 16 25 36 64 100; do

		# The NIC is to be configured if this file does not exist.
		# Ip-Config tries to create this file and when it succeds
		# creating the file, ipconfig is not run again.
		for x in /run/net-"${DEVICE}".conf ; do
			[ -e "$x" ] && break 2
		done

		ipconfig -t ${ROUNDTTT} "${DEVICE}"

	done

	# source ipconfig output
	if [ -n "${DEVICE}" ]; then
		# source specific bootdevice
		. /run/net-${DEVICE}.conf
	else
		# source any interface...
		# ipconfig should have quit after first response
		. /run/net-*.conf
	fi

log_end_msg

exit 0

4. Create /etc/iscsi/iscsi.initramfs

# iscsi name for the RPi
ISCSI_INITIATOR=iqn.1993-08.lan.internal.rpi:01:6df87742251d
# iscsi target (eg: your NAS)
ISCSI_TARGET_NAME=iqn.2004-04.com.qnap:ts-419pii:iscsi.rpi.d55618
ISCSI_TARGET_IP=192.168.1.100

5. Fix Init Script Pre-Requisites

Change /usr/share/initramfs-tools/scripts/local-top/iscsi to depend on wlan. This is because the iscsi init script is called before the root file system mount is attempted (from pre_mount in scripts/functions) and iscsi calls configure-networking before it does anything. So the wlan interface needs to be up before configure-networking() is called as it doesn't know anything about wireless networking.

PREREQ="wlan"

6. Edit /etc/initramfs-tools/initramfs.conf

MODULES=dep
COMPRESS=gzip
DEVICE=wlan0
BOOT=local

7. Rebuild the initramfs on the RPi

sudo update-initramfs -v -c -k $(uname -r)

Note: You might need to add the relevant modules for your WiFi USB adaptor to /etc/initramfs-tools/modules although the update-initramfs script is pretty good about guessing which ones to include based on what is currently loaded.

8. Rebuild the kernel

This time include the initramfs you just built on the Raspberry Pi in the kernel. The kernel compile will be faster this time as everything was built in the first step.

This requires unpacking the cpio archive that is the initramfs and then pointing the kernel configuration to the path containing the initramfs image. See iSCSI Root for the steps required.

Note: If you are cross-compiling you'll need to copy the initramfs image to your build machine, unzip & un-cpio it, then do the kernel build, then copy the kernel+initramfs image back to the RPi

9. Copy system to iSCSI

Follow the steps at iSCSI Root to mount your iscsi target from an existing system and then copy your working system across to the iSCSI target in preparation for booting from it.

  • Don't forget to edit /etc/fstab on the iscsi image.
  • Don't forget to remove ifplugd and open-iscsi using update-rc.d
  • I also removed networking.

10. Edit /boot/cmdline.txt

  • Alter to root=/dev/sda1 (or whatever device your iscsi target comes up as)
  • Add iscsi_auto

11. Change /boot/config.txt

Alter the kernel line so that it is to pointing to your kernel+initrd image.

12. Reboot and cross your fingers

You should be able to completely remove /boot from /etc/fstab at this point and then remove the SD card. Everything will be sourced from the iscsi target.

If you leave the SD card in you can access the root file system on the SD card with something like:

sudo mkdir /oldroot
sudo mount /dev/mmcblk0p2 /oldroot

Related Article