{
  "$type": "site.standard.document",
  "bskyPostRef": {
    "cid": "bafyreic5d7uho7uxwok7p77hewvyskfgpl7io2u6liq7hnawtslcmbxdzy",
    "uri": "at://did:plc:5y2ps7xhcqmc2d63b73ui72s/app.bsky.feed.post/3mmu3m63xl6y2"
  },
  "coverImage": {
    "$type": "blob",
    "ref": {
      "$link": "bafkreigoeti3h5pauowt6jbjpvxxv22sjiqzwoquovccntpm4ictzalxl4"
    },
    "mimeType": "image/jpeg",
    "size": 148418
  },
  "description": "Learn how to deploy Kubernetes on a Raspberry Pi ClusterHAT cluster using K3s and k3sup, configure shared NFS storage, label nodes, and deploy your first container workloads.",
  "path": "/setting-up-a-raspberry-pi-cluster-with-clusterhat-part-3-kubernetes/",
  "publishedAt": "2026-05-27T18:00:34.000Z",
  "site": "https://blog.php-systems.com",
  "tags": [
    "Part 1",
    "Part 2",
    "blinkt-cpu.zip",
    "blinkt-cpu.tar",
    "https://github.com/alexellis/k3sup"
  ],
  "textContent": "In Part 1, we built the Raspberry Pi ClusterHAT hardware and configured networking and SSH access.\n\nIn Part 2, we automated cluster management with Ansible and created a scalable management workflow.\n\nNow it’s time for the fun part: deploying Kubernetes.\n\nFor this cluster, I chose:\n\n  * **K3s** for lightweight Kubernetes\n  * **k3sup** for simplified installation and node joining\n  * **NFS shared storage** hosted from the controller node\n\n\n\nBy the end of this guide, you’ll have:\n\n  * A functioning Kubernetes cluster\n  * Shared persistent storage\n  * Worker node labels\n  * The ability to deploy your own container workloads\n\n\n\n* * *\n\n# Step 1: Configure Shared Storage with NFS\n\nBefore deploying Kubernetes, it’s useful to create shared storage accessible by all nodes.\n\nThis is especially useful for:\n\n  * Shared container images\n  * Persistent application data\n  * Logs\n  * Lightweight development workflows\n\n\n\n* * *\n\n## Install the NFS Server on the Controller\n\nOn the controller node:\n\n\n    sudo apt-get install -y nfs-kernel-server\n\nCreate the storage directory:\n\n\n    sudo mkdir -p /media/Storage\n    sudo chown nobody:nogroup /media/Storage\n    sudo chmod -R 777 /media/Storage\n\n* * *\n\n## Configure Exports\n\nEdit `/etc/exports`:\n\n\n    sudo nano /etc/exports\n\nAdd:\n\n\n    /media/Storage 172.19.181.0/24(rw,sync,no_root_squash,no_subtree_check)\n\nApply the export configuration:\n\n\n    sudo exportfs -a\n\n* * *\n\n# Step 2: Mount NFS Storage on Worker Nodes\n\nOn every worker node (`p1` - `p4`), install the NFS client:\n\n\n    sudo apt-get install -y nfs-common\n\nCreate the mount directory:\n\n\n    sudo mkdir -p /media/Storage\n    sudo chown nobody:nogroup /media/Storage\n    sudo chmod -R 777 /media/Storage\n\n* * *\n\n## Configure Automatic Mounting\n\nEdit `/etc/fstab`:\n\n\n    sudo nano /etc/fstab\n\nAdd:\n\n\n    172.19.181.254:/media/Storage /media/Storage nfs defaults 0 0\n\nMount everything:\n\n\n    sudo mount -a\n\nVerify the mount works by creating a test file and ensuring it appears across all nodes.\n\nIf you hit errors:\n\n  * Double-check `/etc/fstab`\n  * Verify `/etc/exports`\n  * Confirm the controller firewall allows NFS traffic\n\n\n\n* * *\n\n# Alternatively: Setup NFS Setup via Ansible\n\nOnce manual testing succeeds, automate it.\n\nHere’s a simplified Ansible approach.\n\n* * *\n\n## Controller Play\n\n\n    - name: Configure NFS server\n      hosts: controllers\n      become: yes\n\n      tasks:\n        - name: Install NFS server\n          apt:\n            name: nfs-kernel-server\n            state: present\n\n        - name: Create storage directory\n          file:\n            path: /media/Storage\n            state: directory\n            mode: '0777'\n\n        - name: Configure exports\n          lineinfile:\n            path: /etc/exports\n            line: '/media/Storage 172.19.181.0/24(rw,sync,no_root_squash,no_subtree_check)'\n\n        - name: Reload exports\n          command: exportfs -a\n\n* * *\n\n## Worker Play\n\n\n    - name: Configure NFS clients\n      hosts: workers\n      become: yes\n\n      tasks:\n        - name: Install NFS client\n          apt:\n            name: nfs-common\n            state: present\n\n        - name: Create mount directory\n          file:\n            path: /media/Storage\n            state: directory\n            mode: '0777'\n\n        - name: Configure fstab\n          lineinfile:\n            path: /etc/fstab\n            line: '172.19.181.254:/media/Storage /media/Storage nfs defaults 0 0'\n\n        - name: Mount storage\n          command: mount -a\n\nAt this point, your cluster has shared persistent storage available everywhere.\n\n* * *\n\n# Misstep: Installing K3s with k3sup\n\nI initially attempted to automate K3s installation using the existing Ansible role.\n\nInstall the collection:\n\n\n    ansible-galaxy collection install vandot.k3sup\n\nI also had to modify the Python interpreter inside:\n\n\n    ~/.ansible/collections/ansible_collections/vandot/k3sup/plugins/modules/k3sup.py\n\nThe module was trying to use:\n\n\n    /usr/bin/env python\n\nRunning manually worked fine, but the role itself continued to cause issues during installation.\n\nIn the end, using `k3sup` directly was significantly simpler.\n\n* * *\n\n# Step 3: Install the Kubernetes Controller\n\nOn the controller node, run:\n\n\n    k3sup install --local\n\nOnce complete:\n\n\n    export KUBECONFIG=`pwd`/kubeconfig\n    kubectl get node\n\nYou should now see the controller node in the cluster.\n\n* * *\n\n# Step 4: Join the Worker Nodes\n\nJoin each worker node:\n\n\n    for i in $(seq 1 1 4); do\n      k3sup join \\\n        --ip 172.19.181.$i \\\n        --server-ip 172.19.181.254 \\\n        --user pi\n    done\n\nVerify the cluster again:\n\n\n    export KUBECONFIG=`pwd`/kubeconfig\n    kubectl get node\n\nYou should now see:\n\n  * Controller node\n  * `p1`\n  * `p2`\n  * `p3`\n  * `p4`\n\n\n\nAll reporting as `Ready`.\n\n* * *\n\n# Step 5: Label GPIO-Capable Nodes\n\nBecause these nodes interact with physical GPIO hardware, adding labels makes workload scheduling easier.\n\nExample:\n\n\n    kubectl label nodes p1 gpio=true\n    kubectl label nodes p2 gpio=true\n    kubectl label nodes p3 gpio=true\n    kubectl label nodes p4 gpio=true\n\nYou can later target workloads using node selectors.\n\n* * *\n\n# Step 6: Deploy a Custom Workload\n\nOne 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\n\n* * *\n\n## Build the Image\n\nBuild your container image locally:\n\n\n    docker build -t blinkt-cpu .\n\nExport the image:\n\n\n    docker save blinkt-cpu > blinkt-cpu.tar\n\nIf 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\n\nCopy the image to the shared NFS mount:\n\n\n    cp blinkt-cpu.tar /media/Storage/\n\n* * *\n\n## Import the Image into K3s\n\nOn each node:\n\n\n    sudo k3s ctr images import /media/Storage/blinkt-cpu.tar\n\nOnce imported, your Kubernetes deployments can reference the image directly.\n\n* * *\n\n# Example GPIO Node Selector\n\nExample deployment snippet:\n\n\n    nodeSelector:\n      gpio: \"true\"\n\nThis ensures GPIO-dependent workloads only run on appropriate hardware.\n\n* * *\n\n# Final Thoughts\n\nAt this point, you now have:\n\n  * A Raspberry Pi Kubernetes cluster\n  * Shared NFS-backed storage\n  * Automated configuration with Ansible\n  * Worker node scheduling labels\n  * A platform for experimenting with distributed systems\n\n\n\nThis setup is surprisingly capable for:\n\n  * Home labs\n  * CI/CD experimentation\n  * Edge computing\n  * IoT orchestration\n  * Learning Kubernetes safely and cheaply\n\n\n\nAnd perhaps most importantly—it’s fun.\n\n* * *\n\n# Additional Resources\n\n  * k3sup GitHub Repository\nhttps://github.com/alexellis/k3sup\n\n\n\n* * *\n\nThat 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.",
  "title": "Setting Up a Raspberry Pi Cluster with ClusterHAT (Part 3: Kubernetes)",
  "updatedAt": "2026-05-27T18:00:37.720Z"
}