๐ Building My K3s Cluster from Scratch: A Hands-On Journey
Over a few days I built a K3s cluster from the ground up. It was a deep dive into lightweight Kubernetes: networking hiccups, control-plane setup, and dashboard deployment. Below is a concise, practical walkthrough of what I did, how I debugged issues, and what I learned.
๐งฑ Phase 1 โ Master node setup
Install K3s on the master node:
curl -sfL https://get.k3s.io | sh -
This installs the K3s server, starts the control plane, and generates a join token for worker nodes.
Verify the service is running:
sudo systemctl status k3s
Get the join token for worker nodes:
sudo cat /var/lib/rancher/k3s/server/node-token
๐งฉ Phase 2 โ Worker node setup
On a worker node, attempt to join the cluster:
curl -sfL https://get.k3s.io | K3S_URL=https://192.168.50.120:6443 K3S_TOKEN=<token> sh -
When the join failed, I diagnosed connectivity with:
curl -k https://192.168.50.120:6443
ip a
I discovered the master node IP had changed. To recover from a failed agent install, clean up the previous agent and re-run the join with the correct IP:
sudo systemctl stop k3s-agent
sudo systemctl disable k3s-agent
sudo rm -f /usr/local/bin/k3s
sudo rm -rf /etc/rancher/k3s /var/lib/rancher/k3s /var/lib/kubelet /etc/systemd/system/k3s-agent.service
sudo systemctl daemon-reload
curl -sfL https://get.k3s.io | K3S_URL=https://master-node:6443 K3S_TOKEN=<token> sh -
Success โ the worker joined the cluster.
๐ Phase 3 โ Deploy a sample app
Create a simple NGINX deployment and expose it:
kubectl create deployment nginx-demo --image=nginx
kubectl expose deployment nginx-demo --port=80 --type=NodePort
kubectl get svc nginx-demo
Now the app is reachable from outside the cluster on the assigned NodePort.
๐ Phase 4 โ Install the Kubernetes Dashboard
Apply the recommended dashboard manifest:
kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.7.0/aio/deploy/recommended.yaml
Create an admin service account and bind it to cluster-admin (run with appropriate kubeconfig):
sudo kubectl --kubeconfig=/etc/rancher/k3s/k3s.yaml apply -f - <<EOF
apiVersion: v1
kind: ServiceAccount
metadata:
name: admin-user
namespace: kubernetes-dashboard
EOF
sudo kubectl --kubeconfig=/etc/rancher/k3s/k3s.yaml apply -f - <<EOF
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: admin-user
roleRef:
kind: ClusterRole
name: cluster-admin
apiGroup: rbac.authorization.k8s.io
subjects:
- kind: ServiceAccount
name: admin-user
namespace: kubernetes-dashboard
EOF
sudo kubectl --kubeconfig=/etc/rancher/k3s/k3s.yaml apply -f - <<EOF
apiVersion: v1
kind: Secret
metadata:
name: admin-user-token
namespace: kubernetes-dashboard
annotations:
kubernetes.io/service-account.name: admin-user
type: kubernetes.io/service-account-token
EOF
sudo kubectl --kubeconfig=/etc/rancher/k3s/k3s.yaml -n kubernetes-dashboard describe secret admin-user-token
The last command prints the token used to log into the dashboard UI.
๐ Phase 5 โ Expose the dashboard
Edit the dashboard service to change it from ClusterIP to NodePort and set a port (example uses 31863):
sudo kubectl --kubeconfig=/etc/rancher/k3s/k3s.yaml -n kubernetes-dashboard edit svc kubernetes-dashboard
# change type: ClusterIP -> type: NodePort and add nodePort: 31863
Then access the dashboard at:
https://master-node:31863
Log in with the token retrieved earlier.
๐ง What I learned
- How K3s installs the control plane and how workers join the cluster.
- Diagnosing IP drift and connectivity problems.
- Scheduling and exposing workloads with Kubernetes services (ClusterIP vs NodePort).
- Installing and securing the Kubernetes Dashboard with a service account and token.
This exercise was a practical exploration of Kubernetes fundamentals. I built a working cluster, troubleshooted real problems, and ended up with a visual dashboard to inspect workloads. Next steps: automate node provisioning and add monitoring.