How to Build a Multi-Node Kubernetes Cluster on Ubuntu Server with K3s: Installation, Traefik Ingress, and Load Balancing

Ubuntu tutorial - IT technology blog
Ubuntu tutorial - IT technology blog

When people mention Kubernetes (K8s), developers often imagine a massive, resource-heavy system that consumes dozens of gigabytes of RAM and demands a dedicated operations team. But what if you only have 2–3 low-cost VPS instances (with 1–2 GB RAM) running Ubuntu Server? K3s is the perfect solution: lightweight, fast, fully equipped with self-healing and load balancing capabilities, all without bottlenecking your hardware resources.

1. Quick Start: Build a Multi-Node K3s Cluster in 5 Minutes

The minimal lab setup requires 2 VPS instances or Ubuntu Server virtual machines (22.04 or 24.04 LTS) connected over a local network:

  • Master Node (Server): IP 192.168.1.10 (minimum 1 vCPU, 1.5GB RAM)
  • Worker Node (Agent): IP 192.168.1.11 (minimum 1 vCPU, 1GB RAM)

Step 1: Install K3s on the Master Node

SSH into the Master machine and run a single command:

curl -sfL https://get.k3s.io | sh -

K3s will automatically download the binary, initialize the systemd service, and pre-configure kubectl. This process typically takes less than 30 seconds.

Next, retrieve the secret token required to authorize the Worker Node to join the cluster:

sudo cat /var/lib/rancher/k3s/server/node-token

(Copy this token string, for example: K10abcd1234...::server:5678efgh)

Step 2: Join the Worker Node to the Cluster

Open a terminal on the Worker machine and execute the following command (replace with your actual Master IP and Token):

curl -sfL https://get.k3s.io | K3S_URL=https://192.168.1.10:6443 K3S_TOKEN="YOUR_TOKEN_STRING" sh -

Step 3: Verify Cluster Status

Switch back to the Master Node and check the list of nodes in the cluster:

sudo k3s kubectl get nodes

Once both nodes show a Ready status, your K8s cluster is ready to accept workloads:

NAME       STATUS   ROLES                  AGE    VERSION
master     Ready    control-plane,master   3m     v1.30.2+k3s1
worker01   Ready    <none>                 45s    v1.30.2+k3s1

2. How Does K3s Work and Why Is It So Lightweight?

Standard Kubernetes is like an 18-wheel semi-truck: incredibly powerful, but bulky and fuel-hungry. When you only need to transport a few small packages between branches, driving an 18-wheeler is massive overkill.

Rancher Labs designed K3s like a nimble pickup truck. The development team stripped out redundant cloud provider drivers, replaced the heavy etcd database with SQLite/kine, and packaged everything into a single binary under 100MB.

Thanks to this architecture, the Master Node consumes only about 450MB–512MB of RAM at idle. You can confidently run it on budget $5–$10/month VPS plans from Hetzner, Linode, or local cloud providers.

Cluster Architecture & Component Roles

  • Server Node (Control Plane): Receives deployment commands from users, monitors Pod health, and schedules containers onto the appropriate nodes.
  • Agent Node (Worker): Pulls container images and runs Pods via the built-in containerd runtime.
  • Traefik Ingress Controller: The default traffic entry point (Reverse Proxy & Load Balancer). All incoming requests on ports 80/443 pass through Traefik before being routed to individual containers.

3. Hands-On: Configuring Ingress and Load Balancing

We will deploy a sample web application with 3 replicas distributed across nodes to test the real-world load balancing mechanism.

Create Deployment and Service

Create the app-demo.yaml file on the Master Node:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-demo
  labels:
    app: web-demo
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web-demo
  template:
    metadata:
      labels:
        app: web-demo
    spec:
      containers:
      - name: whoami
        image: traefik/whoami
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: web-demo-service
spec:
  type: ClusterIP
  selector:
    app: web-demo
  ports:
  - port: 80
    targetPort: 80

Apply the configuration to the cluster:

sudo k3s kubectl apply -f app-demo.yaml

Configure Ingress for Domain Routing

Next, create the ingress-demo.yaml file to route the domain demo.itfromzero.local to the newly created Service:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: web-demo-ingress
spec:
  ingressClassName: traefik
  rules:
  - host: demo.itfromzero.local
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: web-demo-service
            port:
              number: 80

Apply the Ingress:

sudo k3s kubectl apply -f ingress-demo.yaml

Verify Real-World Load Balancing

Send test requests to the Master Node using cURL:

curl -H "Host: demo.itfromzero.local" http://192.168.1.10

With each request, the Hostname field in the response will alternate between Pod names (e.g., from web-demo-6d4c5-abc1 to web-demo-6d4c5-xyz2). Traefik Ingress automatically distributes traffic evenly using Round-Robin across Pods running on both Master and Worker nodes.

4. Production Tips for Running K3s on VPS

Here are 4 practical optimizations to keep your K3s cluster stable over the long run without running out of RAM or exposing security risks:

  • Manage the Cluster from Your Local Machine: The cluster configuration file is located at /etc/rancher/k3s/k3s.yaml. Copy this file to your local machine at ~/.kube/config and replace 127.0.0.1 with the Master Node’s public IP. You can then immediately manage your cluster using k9s, Lens, or VS Code without needing to SSH directly.
  • Disable Unused Components: If you already have Nginx running on the host or want to save an extra 150MB of RAM, you can disable the default Traefik and Local Storage during installation:
    curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="--disable traefik --disable local-storage" sh -
  • Configure Strict Firewall (UFW) Rules: On the Master Node, only expose port 6443 to the Worker’s internal IP. Keep ports 80, 443 open publicly for incoming web traffic. Avoid exposing port 6443 to the public internet to protect the Kubernetes API Server from automated scans.
  • Back Up Control Plane Data Regularly: The internal K3s database is stored at /var/lib/rancher/k3s/server/db/. Set up a nightly cronjob to compress this directory and sync it to S3 or remote cloud storage. In the event of a server hardware failure, you can restore your cluster in minutes.
Share: