Mr.PlanB Logo

    Newsletter

    Subscribe our newsletter

    Get new infrastructure guides, comparison reports, and migration notes in your inbox.

    Infrastructure notes, guides, and new tools. Unsubscribe anytime.

    Back to Blog
    Kubernetes
    K3s
    Tutorial

    Setting Up a Kubernetes Cluster in Your Homelab

    January 10, 2024
    12 min read

    Running Kubernetes in your homelab is a good way to learn container orchestration, test applications, and build production-ready skills. In this guide, we'll deploy a K3s cluster on Proxmox with multiple nodes and high availability.

    Why K3s?

    K3s is a lightweight Kubernetes distribution that suits homelabs well. It uses only 512MB of RAM and installs as a single binary with no complex dependencies. It is still 100% compliant with upstream Kubernetes, and it includes Traefik, CoreDNS, and local storage out of the box.

    Architecture overview

    We'll create a 3-node cluster with 1 control plane node that manages the cluster and 2 worker nodes that run application workloads.

    ┌─────────────────┐
    │  Control Plane  │
    │   192.168.1.10  │
    └────────┬────────┘
             │
        ┌────┴────┐
        │         │
    ┌───▼───┐ ┌──▼────┐
    │Worker1│ │Worker2│
    │  .11  │ │  .12  │
    └───────┘ └───────┘
    

    Prerequisites

    • Proxmox VE installed and configured
    • 3 VMs or LXC containers (Ubuntu 22.04 recommended)
    • At least 2GB RAM per node (4GB recommended)
    • 2 CPU cores per node
    • 20GB storage per node

    Preparing the nodes

    Create VMs in Proxmox

    For each node, create a VM with:

    # CPU: 2 cores
    # RAM: 4GB
    # Disk: 20GB
    # Network: Bridge to vmbr0
    

    Install Ubuntu server

    1. Boot from Ubuntu Server ISO
    2. Complete the installation wizard
    3. Install OpenSSH server
    4. Update the system:
    sudo apt update && sudo apt upgrade -y
    

    Configure static IP addresses

    Give each node a fixed address by editing /etc/netplan/00-installer-config.yaml:

    network:
      version: 2
      ethernets:
        ens18:
          addresses:
            - 192.168.1.10/24  # Change for each node
          gateway4: 192.168.1.1
          nameservers:
            addresses: [192.168.1.1, 8.8.8.8]
    

    Apply the configuration:

    sudo netplan apply
    

    Disable swap

    Kubernetes requires swap to be disabled, so turn it off now and remove it from /etc/fstab:

    sudo swapoff -a
    sudo sed -i '/ swap / s/^/#/' /etc/fstab
    

    Configure hostnames

    Set a unique hostname on each of the three nodes:

    # Control plane
    sudo hostnamectl set-hostname k3s-master
    
    # Worker nodes
    sudo hostnamectl set-hostname k3s-worker1
    sudo hostnamectl set-hostname k3s-worker2
    

    Then update /etc/hosts on all nodes:

    192.168.1.10    k3s-master
    192.168.1.11    k3s-worker1
    192.168.1.12    k3s-worker2
    

    Installing K3s

    Install the control plane

    On the master node, run the K3s install script in server mode:

    curl -sfL https://get.k3s.io | sh -s - server \
      --write-kubeconfig-mode 644 \
      --disable traefik \
      --node-name k3s-master
    

    We disable Traefik here so we can install it manually later with a custom configuration. Verify the installation with:

    sudo kubectl get nodes
    

    Next, get the node token that the workers will use to join:

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

    Save this token; you'll need it for worker nodes.

    Install worker nodes

    On each worker node, run the install script in agent mode:

    curl -sfL https://get.k3s.io | K3S_URL=https://192.168.1.10:6443 \
      K3S_TOKEN=YOUR_NODE_TOKEN \
      sh -s - agent \
      --node-name k3s-worker1  # Change for each node
    

    Replace YOUR_NODE_TOKEN with the token from the master node.

    Verify the cluster

    Back on the master node, list the nodes to confirm the workers joined:

    kubectl get nodes
    

    You should see all three nodes in "Ready" state:

    NAME          STATUS   ROLES                  AGE   VERSION
    k3s-master    Ready    control-plane,master   5m    v1.27.3+k3s1
    k3s-worker1   Ready    <none>                 2m    v1.27.3+k3s1
    k3s-worker2   Ready    <none>                 2m    v1.27.3+k3s1
    

    Configure kubectl access

    Local machine access

    To manage the cluster from your own machine, copy the kubeconfig from the master node:

    # On master node
    sudo cat /etc/rancher/k3s/k3s.yaml
    

    Then create the config file on your local machine:

    mkdir -p ~/.kube
    # Paste the content and update the server IP
    nano ~/.kube/config
    

    Change server: https://127.0.0.1:6443 to server: https://192.168.1.10:6443, then test the connection from your local machine:

    kubectl get nodes
    

    Installing essential components

    Helm package manager

    Install Helm, the Kubernetes package manager, on your local machine:

    curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
    

    MetalLB load balancer

    MetalLB adds LoadBalancer support on bare metal. Install it with:

    kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.13.12/config/manifests/metallb-native.yaml
    

    Then create an IP pool configuration for MetalLB to hand out:

    # metallb-config.yaml
    apiVersion: metallb.io/v1beta1
    kind: IPAddressPool
    metadata:
      name: default
      namespace: metallb-system
    spec:
      addresses:
      - 192.168.1.200-192.168.1.250
    ---
    apiVersion: metallb.io/v1beta1
    kind: L2Advertisement
    metadata:
      name: default
      namespace: metallb-system
    spec:
      ipAddressPools:
      - default
    

    Save that as metallb-config.yaml and apply it to the cluster:

    kubectl apply -f metallb-config.yaml
    

    Traefik ingress controller

    Install Traefik with Helm, exposed through a LoadBalancer service:

    helm repo add traefik https://traefik.github.io/charts
    helm repo update
    
    helm install traefik traefik/traefik \
      --namespace traefik \
      --create-namespace \
      --set service.type=LoadBalancer
    

    Then look up the LoadBalancer IP that Traefik received:

    kubectl get svc -n traefik
    

    Cert-Manager

    Cert-Manager handles automatic TLS certificates, and you can install it from the release manifest:

    kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.3/cert-manager.yaml
    

    Longhorn storage

    Longhorn provides distributed block storage for Kubernetes. Install it with Helm:

    helm repo add longhorn https://charts.longhorn.io
    helm repo update
    
    helm install longhorn longhorn/longhorn \
      --namespace longhorn-system \
      --create-namespace
    

    To reach the Longhorn UI from your machine, forward its service port with kubectl:

    kubectl -n longhorn-system port-forward svc/longhorn-frontend 8080:80
    

    Then visit http://localhost:8080.

    Deploying a test application

    Create a test deployment with an nginx service behind a LoadBalancer:

    # nginx-deployment.yaml
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: nginx-demo
    spec:
      replicas: 3
      selector:
        matchLabels:
          app: nginx
      template:
        metadata:
          labels:
            app: nginx
        spec:
          containers:
          - name: nginx
            image: nginx:latest
            ports:
            - containerPort: 80
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: nginx-service
    spec:
      type: LoadBalancer
      selector:
        app: nginx
      ports:
      - port: 80
        targetPort: 80
    

    Deploy it:

    kubectl apply -f nginx-deployment.yaml
    kubectl get svc nginx-service
    

    You can then reach the application using the LoadBalancer IP that the service receives.

    Monitoring with Prometheus

    For monitoring, add the Prometheus community chart repository and install kube-prometheus-stack:

    helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
    helm repo update
    
    helm install prometheus prometheus-community/kube-prometheus-stack \
      --namespace monitoring \
      --create-namespace
    

    To reach Grafana from your machine, forward its service port:

    kubectl port-forward -n monitoring svc/prometheus-grafana 3000:80
    

    Log in to Grafana with the default credentials, admin / prom-operator.

    Best practices

    Resource limits

    Always set resource requests and limits:

    resources:
      requests:
        memory: "64Mi"
        cpu: "250m"
      limits:
        memory: "128Mi"
        cpu: "500m"
    

    Namespace organization

    Use namespaces to organize workloads:

    kubectl create namespace production
    kubectl create namespace development
    

    Backup strategy

    For regular backups, add the VMware Tanzu chart repository and install Velero:

    helm repo add vmware-tanzu https://vmware-tanzu.github.io/helm-charts
    helm install velero vmware-tanzu/velero \
      --namespace velero \
      --create-namespace
    

    Troubleshooting

    Pod not starting

    kubectl describe pod POD_NAME
    kubectl logs POD_NAME
    

    Node not ready

    kubectl describe node NODE_NAME
    sudo systemctl status k3s
    sudo journalctl -u k3s -f
    

    Network issues

    kubectl get pods -n kube-system
    kubectl logs -n kube-system COREDNS_POD
    

    Next steps

    • Implement GitOps with ArgoCD
    • Set up CI/CD pipelines
    • Configure horizontal pod autoscaling
    • Implement network policies
    • Deploy service mesh (Istio/Linkerd)

    Conclusion

    You now have a working Kubernetes cluster running in your homelab, which is a solid base for learning and experimenting with cloud-native technologies.

    Resources