๐Ÿš€ 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.


Related Posts