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 an excellent 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 perfect for homelabs:

    • Lightweight: Uses only 512MB of RAM
    • Easy Installation: Single binary, no complex dependencies
    • Full Kubernetes: 100% compliant with upstream Kubernetes
    • Built-in Features: Includes Traefik, CoreDNS, and local storage

    Architecture Overview

    We'll create a 3-node cluster:

    • 1 Control Plane Node: Manages the cluster
    • 2 Worker Nodes: 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

    Edit /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 configuration:

    sudo netplan apply
    

    Disable Swap

    Kubernetes requires swap to be disabled:

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

    Configure Hostnames

    Set unique hostnames for each node:

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

    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 Control Plane

    On the master node, run:

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

    Note: We disable Traefik to install it manually later with custom configuration.

    Verify installation:

    sudo kubectl get nodes
    

    Get the node token for workers:

    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:

    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 Cluster

    On the master node:

    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

    Copy the kubeconfig from the master node:

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

    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

    Test connection:

    kubectl get nodes
    

    Installing Essential Components

    Helm Package Manager

    Install Helm on your local machine:

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

    MetalLB Load Balancer

    For bare-metal LoadBalancer support:

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

    Create IP pool configuration:

    # 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
    

    Apply configuration:

    kubectl apply -f metallb-config.yaml
    

    Traefik Ingress Controller

    Install Traefik with Helm:

    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
    

    Get Traefik LoadBalancer IP:

    kubectl get svc -n traefik
    

    Cert-Manager

    For automatic TLS certificates:

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

    Longhorn Storage

    Distributed block storage for Kubernetes:

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

    Access Longhorn UI:

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

    Visit http://localhost:8080

    Deploying a Test Application

    Create a test deployment:

    # 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:

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

    Access the application using the LoadBalancer IP.

    Monitoring with Prometheus

    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
    

    Access Grafana:

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

    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

    Regular backups with 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 fully functional Kubernetes cluster running in your homelab! This setup provides a solid foundation for learning and experimenting with cloud-native technologies.

    Resources