🚀 Why I Started This Project
You know that feeling when you’ve just unboxed a couple of Raspberry Pi boards and your mind races with all the possibilities? That was me.
Two Raspberry Pi 4s, a mini rack, and a network switch were staring at me, and the idea hit:
“Let’s build a K3s cluster, and on top of it run Jenkins, Grafana, and Prometheus.”
Not for production, but for learning, for fun, and for the sheer thrill of watching tiny computers act like a mini data center.
🛠️ The Cluster Setup
First things first, I set up K3s on my Raspberry Pi nodes. I used one Pi as the master and the other as a worker node.
đź”§ Installing Jenkins with Helm (The Persistent, Ingress-Ready Way)
When I started setting up Jenkins, I didn’t want to just spin it up temporarily, I wanted real persistence for my jobs and configurations, proper RBAC with a Service Account, and an Ingress so I could access it without endless port-forwards.
So I followed the official Jenkins Kubernetes documentation step-by-step, adapting it for my Raspberry Pi K3s environment.
Step 1, Create a PersistentVolume
I created a local path PV on my master node so Jenkins could store all its data:
apiVersion: v1
kind: PersistentVolume
metadata:
name: jenkins-pv
spec:
capacity:
storage: 5Gi
accessModes:
- ReadWriteOnce
hostPath:
path: /data/jenkins
Applied it with:
kubectl apply -f jenkins-pv.yaml
in values.yaml:
persitance:
storageClass: jenkins-pv
(Lesson learned: local PVs on Raspberry Pi mean “don’t wipe that SD card” unless you enjoy starting over.)
Step 2, Create a Service Account
Jenkins needs proper permissions to manage pods and builds inside the cluster.
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: jenkins
namespace: jenkins
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
annotations:
rbac.authorization.kubernetes.io/autoupdate: "true"
labels:
kubernetes.io/bootstrapping: rbac-defaults
name: jenkins
rules:
- apiGroups:
- '*'
resources:
- statefulsets
- services
- replicationcontrollers
- replicasets
- podtemplates
- podsecuritypolicies
- pods
- pods/log
- pods/exec
- podpreset
- poddisruptionbudget
- persistentvolumes
- persistentvolumeclaims
- jobs
- endpoints
- deployments
- deployments/scale
- daemonsets
- cronjobs
- configmaps
- namespaces
- events
- secrets
verbs:
- create
- get
- watch
- delete
- list
- patch
- update
- apiGroups:
- ""
resources:
- nodes
verbs:
- get
- list
- watch
- update
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
annotations:
rbac.authorization.kubernetes.io/autoupdate: "true"
labels:
kubernetes.io/bootstrapping: rbac-defaults
name: jenkins
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: jenkins
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: Group
name: system:serviceaccounts:jenkins
kubectl apply -f jenkins-sa.yaml
in values.yaml:
serviceAccount:
create: false
name: jenkins
Step 3, Customize Helm Values for Ingress
I didn’t want to rely on kubectl port-forward forever, so I enabled an Ingress in my Helm values file.
values.yaml:
controller:
jenkinsUriPrefix: "/jenkins"
ingress:
enabled: true
paths:
- path: "/jenkins"
pathType: "Prefix"
backend:
service:
name: "jenkins"
port:
number: 8080
apiVersion: "networking.k8s.io/v1"
ingressClassName: traefik
Step 4, Install Jenkins via Helm
helm repo add jenkins https://charts.jenkins.io
helm repo update
helm install jenkins jenkins/jenkins -f values.yaml
Step 5, Access Jenkins
Since I had Ingress enabled, I just updated my local /etc/hosts:
<MASTER_NODE_IP> jenkins.local
Now Jenkins is available at:
http://jenkins.local
Get the admin password:
kubectl exec --namespace jenkins -it svc/jenkins -c jenkins \
-- /bin/cat /run/secrets/additional/chart-admin-password
đź’ˇ What I Learned:
- The “quick install” is fine for demos, but taking the time to set up PV, RBAC, and Ingress makes your deployment feel real.
- On Raspberry Pis, persistence is precious, one SD card failure can wipe out your CI/CD brain.
- Ingress saved me from juggling
kubectl port-forwardcommands and made Jenkins feel like part of a real infrastructure.
📊 Installing Prometheus & Grafana
Prometheus and Grafana are best friends, metrics and visualization.
Add repo
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo add grafana https://grafana.github.io/helm-charts
helm repo update
Install Prometheus
helm install prometheus prometheus-community/prometheus --namespace monitoring --create-namespace
Install Grafana
helm install grafana grafana/grafana --namespace monitoring
Access Grafana
Get admin password:
kubectl get secret --namespace monitoring grafana -o jsonpath="{.data.admin-password}" | base64 --decode
Forward port:
kubectl port-forward svc/grafana 3000:3000 -n monitoring
Open: http://localhost:3000
đź§ The Challenges I Faced
- ARM Compatibility: Some container images just don’t have ARM builds. I had to check chart values and override image tags for ARM64 versions.
- Resource Limits: 4GB Pi RAM is not a lot, tweaking memory requests/limits in Helm values was necessary to avoid OOM kills.
- Networking: A single misconfigured
LoadBalancerservice in K3s can stall everything. Switching toNodePortduring testing helped. - Pod Start Time: On Pis, be ready for minutes of startup time for heavier apps like Jenkins.
🌱 What I Learned
- Helm is magic, one command and you’ve got a whole app running in Kubernetes.
- Small hardware teaches big discipline, limited resources force you to think about efficiency.
- Metrics are addictive, once Grafana dashboards light up with Prometheus data, you want to monitor everything.
🎯 Final Thoughts
Was it overkill to run Jenkins, Prometheus, and Grafana on Raspberry Pis? Absolutely.
Did I learn more in a weekend than in a month of theory? Also absolutely.
If you’ve got some Raspberry Pis gathering dust, set them free, make them work, make them struggle, make yourself debug at 2AM because a pod won’t start. That’s where the magic happens.
đź’¬ Have you tried something similar? Share your Raspberry Pi Kubernetes war stories with me.