External Publication
Visit Post

Setting Up a Raspberry Pi Cluster with ClusterHAT (Part 3: Kubernetes)

blog.php-systems.com May 27, 2026
Source

In Part 1, we built the Raspberry Pi ClusterHAT hardware and configured networking and SSH access.

In Part 2, we automated cluster management with Ansible and created a scalable management workflow.

Now it’s time for the fun part: deploying Kubernetes.

For this cluster, I chose:

  • K3s for lightweight Kubernetes
  • k3sup for simplified installation and node joining
  • NFS shared storage hosted from the controller node

By the end of this guide, you’ll have:

  • A functioning Kubernetes cluster
  • Shared persistent storage
  • Worker node labels
  • The ability to deploy your own container workloads

Step 1: Configure Shared Storage with NFS

Before deploying Kubernetes, it’s useful to create shared storage accessible by all nodes.

This is especially useful for:

  • Shared container images
  • Persistent application data
  • Logs
  • Lightweight development workflows

Install the NFS Server on the Controller

On the controller node:

sudo apt-get install -y nfs-kernel-server

Create the storage directory:

sudo mkdir -p /media/Storage
sudo chown nobody:nogroup /media/Storage
sudo chmod -R 777 /media/Storage

Configure Exports

Edit /etc/exports:

sudo nano /etc/exports

Add:

/media/Storage 172.19.181.0/24(rw,sync,no_root_squash,no_subtree_check)

Apply the export configuration:

sudo exportfs -a

Step 2: Mount NFS Storage on Worker Nodes

On every worker node (p1 - p4), install the NFS client:

sudo apt-get install -y nfs-common

Create the mount directory:

sudo mkdir -p /media/Storage
sudo chown nobody:nogroup /media/Storage
sudo chmod -R 777 /media/Storage

Configure Automatic Mounting

Edit /etc/fstab:

sudo nano /etc/fstab

Add:

172.19.181.254:/media/Storage /media/Storage nfs defaults 0 0

Mount everything:

sudo mount -a

Verify the mount works by creating a test file and ensuring it appears across all nodes.

If you hit errors:

  • Double-check /etc/fstab
  • Verify /etc/exports
  • Confirm the controller firewall allows NFS traffic

Alternatively: Setup NFS Setup via Ansible

Once manual testing succeeds, automate it.

Here’s a simplified Ansible approach.


Controller Play

- name: Configure NFS server
  hosts: controllers
  become: yes

  tasks:
    - name: Install NFS server
      apt:
        name: nfs-kernel-server
        state: present

    - name: Create storage directory
      file:
        path: /media/Storage
        state: directory
        mode: '0777'

    - name: Configure exports
      lineinfile:
        path: /etc/exports
        line: '/media/Storage 172.19.181.0/24(rw,sync,no_root_squash,no_subtree_check)'

    - name: Reload exports
      command: exportfs -a

Worker Play

- name: Configure NFS clients
  hosts: workers
  become: yes

  tasks:
    - name: Install NFS client
      apt:
        name: nfs-common
        state: present

    - name: Create mount directory
      file:
        path: /media/Storage
        state: directory
        mode: '0777'

    - name: Configure fstab
      lineinfile:
        path: /etc/fstab
        line: '172.19.181.254:/media/Storage /media/Storage nfs defaults 0 0'

    - name: Mount storage
      command: mount -a

At this point, your cluster has shared persistent storage available everywhere.


Misstep: Installing K3s with k3sup

I initially attempted to automate K3s installation using the existing Ansible role.

Install the collection:

ansible-galaxy collection install vandot.k3sup

I also had to modify the Python interpreter inside:

~/.ansible/collections/ansible_collections/vandot/k3sup/plugins/modules/k3sup.py

The module was trying to use:

/usr/bin/env python

Running manually worked fine, but the role itself continued to cause issues during installation.

In the end, using k3sup directly was significantly simpler.


Step 3: Install the Kubernetes Controller

On the controller node, run:

k3sup install --local

Once complete:

export KUBECONFIG=`pwd`/kubeconfig
kubectl get node

You should now see the controller node in the cluster.


Step 4: Join the Worker Nodes

Join each worker node:

for i in $(seq 1 1 4); do
  k3sup join \
    --ip 172.19.181.$i \
    --server-ip 172.19.181.254 \
    --user pi
done

Verify the cluster again:

export KUBECONFIG=`pwd`/kubeconfig
kubectl get node

You should now see:

  • Controller node
  • p1
  • p2
  • p3
  • p4

All reporting as Ready.


Step 5: Label GPIO-Capable Nodes

Because these nodes interact with physical GPIO hardware, adding labels makes workload scheduling easier.

Example:

kubectl label nodes p1 gpio=true
kubectl label nodes p2 gpio=true
kubectl label nodes p3 gpio=true
kubectl label nodes p4 gpio=true

You can later target workloads using node selectors.


Step 6: Deploy a Custom Workload

One of the first workloads I deployed was a custom blinkt container for Raspberry Pi LEDs. The entire project can be found here: blinkt-cpu.zip


Build the Image

Build your container image locally:

docker build -t blinkt-cpu .

Export the image:

docker save blinkt-cpu > blinkt-cpu.tar

If you don't have docker installed, you will need to build the image on a device with the same architecture and copy it over to the control node. Alternatively, if you trust me, the image can be found here: blinkt-cpu.tar

Copy the image to the shared NFS mount:

cp blinkt-cpu.tar /media/Storage/

Import the Image into K3s

On each node:

sudo k3s ctr images import /media/Storage/blinkt-cpu.tar

Once imported, your Kubernetes deployments can reference the image directly.


Example GPIO Node Selector

Example deployment snippet:

nodeSelector:
  gpio: "true"

This ensures GPIO-dependent workloads only run on appropriate hardware.


Final Thoughts

At this point, you now have:

  • A Raspberry Pi Kubernetes cluster
  • Shared NFS-backed storage
  • Automated configuration with Ansible
  • Worker node scheduling labels
  • A platform for experimenting with distributed systems

This setup is surprisingly capable for:

  • Home labs
  • CI/CD experimentation
  • Edge computing
  • IoT orchestration
  • Learning Kubernetes safely and cheaply

And perhaps most importantly—it’s fun.


Additional Resources


That wraps up this three-part Raspberry Pi ClusterHAT Kubernetes series. From bare SD cards to a functioning Kubernetes cluster, you now have a powerful miniature platform ready for experimentation.

Discussion in the ATmosphere

Loading comments...