Giter Club home page Giter Club logo

pi-cloud-init's Introduction

Minimal Raspberry Pi OS including cloud-init

This repo features a custom Raspberry Pi OS Lite image including cloud-init built with pi-gen.

TL;DR: How to use the image โš™๏ธ

  1. Download the image from GitHub:

    ARCH=armhf # either armhf or aarch64 (experimental)
    curl -sSL -o 2021-05-10-raspios-buster-$ARCH-lite-cloud-init.zip https://github.com/timebertt/pi-cloud-init/releases/download/2021-05-10/2021-05-10-raspios-buster-$ARCH-lite-cloud-init.zip && \
    unzip -o 2021-05-10-raspios-buster-$ARCH-lite-cloud-init.zip
  2. Mount the boot partition (of the .img file) to add user-data, meta-data and optionally network-config files to the root of it. Unmount it again, once added.

  3. Flash image to SD card using balena etcher, Raspberry Pi Imager or similar.

  4. Insert SD card into Pi and power up! ๐Ÿš€

Rationale ๐Ÿ’ก

Raspberry Pi OS is fairly easy to setup. Though, if you want to setup a Raspberry Pi cluster like Alex Ellis and the community have been doing for five years+, you will have to repeat some manual configuration steps like copying SSH keys, networking setup and so on for each Pi.

cloud-init is the de-facto standard for solving the same problem on cloud instances. It's baked into most cloud images, which makes cloud instances discover initialization and configuration data from the cloud provider's metadata service.

cloud-init can be used for bootstrapping Raspberry Pis as well, making it easy to configure networking, packages and remote access consistently across a fleet of Pis. Unfortunately, Raspberry Pi OS doesn't ship with cloud-init out of the box and thus requires manual installation and configuration.

This repo features a custom image based on Raspberry Pi OS Lite built with pi-gen with cloud-init preinstalled, which allows to pass initialization and configuration data (in form of meta-data, user-data and network-config files) to Pis via the boot image flashed to an SD card. This makes it easy to bootstrap multiple Pis in a plug-and-play fashion without attaching a monitor or manually SSHing into each one of them.

Why not simply use Ubuntu Server? โ“

Ubuntu Server comes with cloud-init preinstalled and also features a Raspberry Pi Image. It can be leveraged in a similar fashion to the image built by this project to bootstrap Pis. Though, in my tests Ubuntu Server already consumed more than 300MB of precious memory on my Pis without anything installed. Therefore I started building a custom image based on Raspberry Pi OS Lite, which consumes only roughly 60MB of memory out of the box.

Why not use k3os? โ“

k3os is a minimal OS designed to run k3s and be managed by kubectl. It also features a cloud-init-like configuration file, which can be used to easily configure hosts. Unfortunately, there are currently no official RPi images, but you can build one on your own by leveraging this project. In my tests, k3os performed quite well with some additional overhead in CPU and Memory usage. I chose to build my own images based on Raspberry Pi OS, because of the lower resource usage, the familiarity with Debian-based distros, flexibility in bootstrapping my clusters (not tied to k3s) and the endless resources on Raspberry Pi OS.

How to build the image ๐Ÿšง

You can build the image yourself and customize the build along the way by following these steps.

  1. Setup a Debian VM using vagrant which will build the image. This provides a clean build environment and additionally works on a Linux as well as macOS.

    # what image to build: either amrhf (default) or aarch64 (experimental)
    export ARCH=armhf
    vagrant up --provision
  2. Start pi-gen build in the VM. This is going to take some time...

    vagrant ssh
    ./pi-cloud-init/build.sh

    When you want to rebuild from scratch (e.g. because you want to build for another arch), you can run

    vagrant ssh
    CLEAN=1 ./pi-cloud-init/build.sh
  3. Transfer produced image to the host machine and unzip. This requires the vagrant-scp plugin, install it first by running:

    vagrant plugin install vagrant-scp
    zip_file=$(date +%Y-%m-%d)-raspios-buster-$ARCH-lite-cloud-init.zip && \
    vagrant scp raspios-builder:/home/vagrant/pi-cloud-init/$zip_file $zip_file && \
    unzip -o "$zip_file"
  4. Customize user-data.yaml, meta-data.yaml and network-config.yaml for the instance you're setting up.

  5. Mount boot partition to inject user-data, meta-data and network-config. (It's assuming a macOS machine, but you should be able to accomplish the same using mount and umount on Linux.)

    img_file="${zip_file%.zip}.img" && \
    volume="$(hdiutil mount "$img_file" | egrep -o '/Volumes/.+')" && \
    cp meta-data.yaml "$volume"/meta-data && \
    cp user-data.yaml "$volume"/user-data && \
    cp network-config.yaml "$volume"/network-config && \
    device="$(mount | grep "$volume" | cut -f1 -d' ' | egrep -o '/dev/disk.')" && \
    diskutil umountDisk "$device" && \
    diskutil eject "$device"
  6. Optionally, you can verify the image and cloud-init functionality using dockerpi (only works with armhf images). It start a Docker container with QEMU in it emulating a Pi. This way you can already verify, that the image and the provided user-data is working without flashing a new SD card everytime.

    docker run -it -v $PWD/$img_file:/sdcard/filesystem.img lukechilds/dockerpi:vm
    ...
    cloud-init[96]: Cloud-init v. 20.2 running 'init-local' at Mon, 08 Mar 2021 19:54:02 +0000. Up 53.20 seconds.
    ...
    cloud-init[380]: Cloud-init v. 20.2 running 'init' at Mon, 08 Mar 2021 19:54:42 +0000. Up 93.34 seconds.
    ...
    cloud-init[568]: Cloud-init v. 20.2 running 'modules:config' at Mon, 08 Mar 2021 19:55:48 +0000. Up 159.10 seconds.
    ...
    cloud-init[620]: Cloud-init v. 20.2 running 'modules:final' at Mon, 08 Mar 2021 19:56:05 +0000. Up 175.50 seconds.
    cloud-init[620]: Cloud-init v. 20.2 finished at Mon, 08 Mar 2021 19:56:08 +0000. Datasource DataSourceNoCloud [seed=/dev/sda1][dsmode=net].  Up 179.17 seconds
  7. Now, flash the image including cloud-init data to SD card, using balena etcher, Raspberry Pi Imager or similar.

  8. Finally, SSH into your Pi and verify cloud-init functionality. By default, the pi user is locked and SSH password authentication is disabled, so make sure to add a custom user with ssh_authorized_keys to your user-data.

    ssh your-user@your-pi
    less /var/log/cloud-init-output.log

Next steps ๐Ÿƒ

Play around with user-data ๐ŸŽฎ

Find some more user-data examples under examples:

More fun ๐ŸŽ‰

Spin up a Kubernetes cluster ๐Ÿค“

examples/k3s-cluster contains some user-data files for spinning up a Kubernetes cluster using k3s. Use the set of user-data files in raspberry-0 for bootstrapping your cluster (i.e. for the first Pi). After that, join any number of servers and agents using the user-data files in raspberry-1.

The examples already include configuration for:

  • kube-vip in layer 2 mode (ARP) for API server load balancing
    • provides an HA control plane endpoint to perform rolling updates on k3s servers (drain, install, reboot one by one)
    • only used for control plane load balancing
  • MetalLB in layer 2 mode (ARP) for LoadBalancer Services
    • also includes LoadBalancer IP allocation address ranges specified in configuration ConfigMap (built-in and easier to configure than kube-vip)
    • not used for control plane load balancing (not supported, ref metallb/metallb#168)
    • klipper-lb is disabled, as it can't provide a dedicated LoadBalancer IP on your local network (just uses host ports to make LoadBalancer services accessible)

Get the kubeconfig from raspberry-0 and modify it to use the API server LoadBalancer IP:

mkdir -p $HOME/.kube/configs/home && \
ssh [email protected] sudo cat /etc/rancher/k3s/k3s.yaml > $HOME/.kube/configs/home/pi-cluster.yaml && \
export KUBECONFIG=$HOME/.kube/configs/home/pi-cluster.yaml && \
kubectl config rename-context default pi-cluster && \
kubectl config set clusters.default.server https://192.168.0.30:6443

Check if all Nodes were bootstrapped and got ready:

$ kubectl get no -owide
NAME          STATUS   ROLES                       AGE     VERSION        INTERNAL-IP    EXTERNAL-IP   OS-IMAGE                       KERNEL-VERSION   CONTAINER-RUNTIME
raspberry-0   Ready    control-plane,etcd,master   18m     v1.20.5+k3s1   192.168.0.20   <none>        Debian GNU/Linux 10 (buster)   5.10.17-v8+      containerd://1.4.4-k3s1
raspberry-1   Ready    control-plane,etcd,master   11m     v1.20.5+k3s1   192.168.0.21   <none>        Debian GNU/Linux 10 (buster)   5.10.17-v8+      containerd://1.4.4-k3s1
raspberry-2   Ready    control-plane,etcd,master   9m13s   v1.20.5+k3s1   192.168.0.22   <none>        Debian GNU/Linux 10 (buster)   5.10.17-v8+      containerd://1.4.4-k3s1

pi-cloud-init's People

Contributors

geoff-coppertop avatar timebertt avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

pi-cloud-init's Issues

dhcpcd is still running.

Thanks for doing the work to get RaspiOS lite running with cloud-init! The 64-bit image is almost exactly what I need.

When trying to provision a unit with a static IP per your example I'm seeing the device get both a static IP and a dynamic one assigned, I saw this with ip address show. A little more digging shows that the dhcpcd service is still running and is why the second dynamic IP is being assigned. Locally I was able to stop this by running sudo systemctl stop dhcpcd and sudo systemctl mask dhcpcd to prevent the service from restarting, doing so forces network-config to be the sole source of configuration.

While I think you could add these commands in the user-data I think a more generalized answer would be good but I'm not quite sure where to add this in the scripts. I'd be happy to help test this further and will keep poking myself.

Docker Support

Any plans to support a docker vagrant environment? The virtualbox dependency breaks for anyone using an M1 architecture.

rfkill: cannot open /dev/rfkill

Looks like permissions are not being set correctly for /dev/rfkill on boot. If I call rfkill as would done on boot, or during an ssh connect, as part of the /etc/profile.d/wifi-check.sh script I get the following errors,

rfkill: cannot open /dev/rfkill: Permission denied
rfkill: cannot read /dev/rfkill: Bad file descriptor

Looking at /dev/rfkill when this happens I see that only root has permissions and that it's accessible by the root group. In this circumstance the permission errors make sense since my user isn't root or in the root group.

thomasga@rpi-carbon-00:~ $ ls -lah /dev/rfkill
crw------- 1 root root 10, 242 May 11 04:21 /dev/rfkill

Experimentally I found that I can clear the error until the next reboot by calling sudo rfkill this causes the permissions on /dev/rfkill to change to root:netdev and my user is part of the netted group (shown below).

thomasga@rpi-carbon-00:~ $ sudo rfkill
thomasga@rpi-carbon-00:~ $ ls -lah /dev/rfkill
crw-rw-r-- 1 root netdev 10, 242 May 11 04:25 /dev/rfkill

I haven't gone back to a version of the software that had dhcpcd unmasked but I didn't see this error in that case so something in the boot sequence with dhcpcd active is more better...

user-data, meta-data, and network-config filenames

The Readme.md instructions said, "Mount the boot partition (of the .img file) to add user-data, meta-data and optionally network-config files to the root of it. Unmount it again, once added."

Are those filenames simply user-data, meta-data and network-config without the suffixes or are they user-data.yaml, meta-data.yaml and optionally network-config.yaml as listed in this project's root and examples directories?

Failed install of core packages

When trying to build the an image, step "Installing core packages..." fails:

I: Installing core packages...
W: Failure trying to run: chroot "/home/vagrant/pi-gen/work/stage0/rootfs" dpkg --force-depends --install /var/cache/apt/archives/base-files_10.3+rpi1+deb10u11_armhf.deb
W: See /home/vagrant/pi-gen/work/stage0/rootfs/debootstrap/debootstrap.log for details (possibly the package base-files is at fault)
rmdir: failed to remove '/home/vagrant/pi-gen/work/stage0/rootfs/debootstrap': Directory not empty
[22:46:11] bootstrap failed: please check /home/vagrant/pi-gen/work/stage0/debootstrap.log

The log contains:

Setting up base-files (10.3+rpi1+deb10u11) ...
dpkg: error processing package base-files (--install):
 installed base-files package post-installation script subprocess was killed by signal (Segmentation fault)
Errors were encountered while processing:
 base-files

I'm not sure whether this a fault of the upstream builder or this script, but because I can't find something upstream and it seems I can just build an image with pi-gen on bare metal I suspect that this might be an issue with the build script/vagrant.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.