christoph ender's

blog

tuesday the 22nd of october, 2024

simple raspi for k8s setup

Since I've been setting up a lot of kubernetes installations on raspberry pi machines and still doing a lot of these I've been looking for a speedier way to get the hardware up and running. There's already the raspberry pi imager which helps a lot, but I've been looking for something which automizes even more steps. I ended up putting a small shell script together which allows writing the image in a single step and have everything in place to jump right in using ssh and start with the k8s setup itself.

For every machine there's a separate configuration file. That means the following setup steps can be implemented:

  1. Enable ssh login with the correct authorized_keys file in place, so there's no need to login with a predefined password first in order to install the list of authorized keys.
  2. The machine now already has a static ip configuration in place.
  3. Allow ssh login for root, since for my kind of setup this is the only one I need. This saves all the steps required to allow root login.
  4. After installing all cgroup-like options which are required for kubernetes have been already written to cmdline.txt.

Here's an example for this kind of configuration:

raspi-01.cfg
RASPI_HOSTNAME="raspi-01"
RASPI_DOMAINNAME="example.org"
RASPI_NETWORK_CONFIG="auto eth0
iface eth0 inet static
  address 192.0.2.10/24
  gateway 192.0.2.1
  dns-nameservers 192.0.2.20 192.0.2.21
  dns-search example.org"
RASPI_AUTHORIZED_KEYS="ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIh/Lm3JzOYqNL2dHWAOJnTjuJhdx76VtkuPS26/OYiI my-public-ssh-key"

Invocation of the script requires a raspbian OS image, the device name for the raspi's sd card, usb stick or similar, and the name of the configuration file:

./simple-raspi-setup.sh \
 2024-07-04-raspios-bookworm-arm64-lite.img \
 /dev/sdX \
 raspi-01.cfg

The simple-raspi-setup.sh looks like this:

Please take care since the device provided as the second parameter – /dev/sdX in the example above – will be overwritten and cannot be restored afterwards.

simple-raspi-setup.sh
#!/bin/bash

set -e            # Exit immediately if a command exits with a non-zero status.
set -o pipefail   # Prevents errors in a pipeline from being masked.
set -u            # Fail when using undefined values.

if [[ $# -ne 3 ]] ; then
  echo "Syntax: ${0} <image> <sdcard-devicename> <cfg-file>"
  exit 1
fi

ISO_IMAGE=${1}
SDCARD_DEVICE=${2}
CFG_FILENAME=${3}
source ${CFG_FILENAME}

CMDLINE_OPTIONS="cgroup_memory=1 cgroup_enable=memory noswap"

if [[ $(grep ${SDCARD_DEVICE} /proc/mounts | wc -l) -gt 0 ]]; then
  echo "${SDCARD_DEVICE} is already mounted."
  exit 1
fi

function log() {
  echo [`date "+%Y-%m-%d %H:%M:%S"`] $*
}

log "Starting raspi setup."
log "Hostname: \"${RASPI_HOSTNAME}\"."
log "Network config: \"${RASPI_NETWORK_CONFIG}\"."

# the directory of the script
SCRIPT_DIR=$(cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &>
/dev/null && pwd)

# the temp directory used
# omit the -p parameter to create a temporal directory in the default location
WORK_DIR=$(mktemp -d)
log "Working dir is \"${WORK_DIR}\"."

# check if tmp dir was created
if [[ ! "$WORK_DIR" || ! -d "$WORK_DIR" ]]; then
  echo "Couldn't create tmp dir \"${WORK_DIR}\”."
  exit 1
fi

function cleanup {
  log "Unmounting \"${WORK_DIR}\"."
  umount ${WORK_DIR}

  rm -rf "${WORK_DIR}"
  echo
  log "Deleted temp working directory \"${WORK_DIR}\"."
}

# register the cleanup function to be called on the EXIT signal
trap cleanup EXIT

log "Writing image to \"${SDCARD_DEVICE}\"."
dd if=${ISO_IMAGE} of=${SDCARD_DEVICE} status=progress bs=1048576

log "Mounting \"${SDCARD_DEVICE}1\" to \"${WORK_DIR}\"."
mount ${SDCARD_DEVICE}1 ${WORK_DIR}

log "Creating empty \"ssh\" file for headless login."
touch ${WORK_DIR}/ssh

log "Appending options \"${CMDLINE_OPTIONS}\" to \"cmdline.txt\"."
CURRENT_CMDLINE=$(cat ${WORK_DIR}/cmdline.txt)
echo "${CURRENT_CMDLINE} ${CMDLINE_OPTIONS}" > ${WORK_DIR}/cmdline.txt

log "Unmounting \"${WORK_DIR}\"."
umount ${WORK_DIR}

log "Mounting \"${SDCARD_DEVICE}2\" to \"${WORK_DIR}\"."
mount ${SDCARD_DEVICE}2 ${WORK_DIR}

log "Writing network config."
echo "${RASPI_NETWORK_CONFIG}" >> ${WORK_DIR}/etc/network/interfaces

log "Setting hostname to \"${RASPI_HOSTNAME}\"."
echo "${RASPI_HOSTNAME}" > ${WORK_DIR}/etc/hostname
sed -i "s/^127.0.1.1.*/127.0.1.1	${RASPI_HOSTNAME}.${RASPI_DOMAINNAME} ${RASPI_HOSTNAME}/" ${WORK_DIR}/etc/hosts

log "Enabling root ssh login."
sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin prohibit-password/' ${WORK_DIR}/etc/ssh/sshd_config

log "Writing root's \".ssh/authorized_key\" file."
mkdir -p ${WORK_DIR}/root/.ssh
chmod 700 ${WORK_DIR}/root/.ssh
echo ${RASPI_AUTHORIZED_KEYS} > ${WORK_DIR}/root/.ssh/authorized_keys

# This will remove the login banner saying:
# "Please note that SSH may not work until a valid user has been set up."
log "Removing default raspi ssh valid user warning."
rm ${WORK_DIR}/etc/ssh/sshd_config.d/rename_user.conf

sync

log "Done."

exit 0