# K3s Installation: VM Setup and Cluster Bootstrap > **Series:** Keycloak + OIDC on K3s > **Type:** Supplement — Infrastructure Setup > **Related:** [[Keycloak (OIDC) on K3s]] --- ## Overview This document covers the infrastructure layer that the [[Keycloak (OIDC) on K3s]] lab runs on. Before deploying anything into Kubernetes, you need a platform to host the cluster (VM, container, etc.). In this lab we use a Ubuntu Server VM on a bare-metal Proxmox hypervisor. This guide is written to be platform-agnostic — the VM setup applies whether you are using Proxmox, VirtualBox, VMware, Hyper-V, or a cloud provider like DigitalOcean or Hetzner. > This guide was written using **Ubuntu Server 26.04 LTS (Resolute Raccoon)**. --- ## What is K3s K3s is a lightweight Kubernetes distribution from Rancher. It packages everything needed to run a cluster into a single binary — including a built-in ingress controller (Traefik) and a built-in load balancer (ServiceLB). This makes it ideal for homelabs, single-node setups, and learning environments where you want a real Kubernetes cluster without the complexity of a full production install. --- ## Part 1 — The Virtual Machine ### Recommended Specs These specs will comfortably run the Keycloak + OIDC lab. | Resource | Recommended | |---|---| | CPU | 4 cores | | RAM | 4 GB | | Disk | 40 GB | | OS | Ubuntu Server 24.04 LTS | > Use **Ubuntu Server**, not Ubuntu Desktop. The Server edition is headless — no GUI — which means significantly less RAM and CPU overhead, leaving more resources for K3s and your workloads. Ubuntu Desktop can work but wastes resources you will want for the cluster. ### Choosing a Hypervisor or Host The VM can run on anything that can host a Linux guest. Common options: **Local hypervisors (self-hosted)** - **Proxmox** — bare-metal hypervisor, good for a dedicated homelab server - **VirtualBox** — free, runs on Windows/Mac/Linux, easy to get started - **VMware Workstation / Fusion** — commercial, polished experience - **Hyper-V** — built into Windows 10/11 Pro, no extra install needed **Cloud providers (hosted VM)** - **DigitalOcean Droplet** — straightforward, $12–24/month for a suitable size - **Hetzner Cloud** — very cost effective for European users - **Linode / Akamai Cloud** — similar to DigitalOcean - **AWS EC2 / GCP / Azure** — all work, though more complex to set up for a simple lab > Cloud providers are a valid option for hosting your VM, but provisioning and configuring cloud infrastructure is outside the scope of this document. If you choose to go that route, your provider's documentation is the best place to start. From the point your VM is running and reachable, the K3s install steps below apply equally. ### The Most Important Networking Decision If you are running the VM **locally** (on your own machine or a home server), how you configure the VM's network adapter matters significantly. You want **bridged networking**, not NAT. **Bridged networking** gives the VM its own IP address on your local network — the same subnet as your laptop or desktop. Your machine can reach the VM directly by IP, which is required for the `/etc/hosts` approach used in this lab. ``` Your Router (e.g. 192.168.1.1) ├── Your Machine (192.168.1.10) └── VM (192.168.1.50) ← same subnet, directly reachable ``` **NAT networking** places the VM behind the hypervisor's internal network. Your machine cannot reach the VM's IP directly without setting up port forwarding rules for every service. ``` Your Router └── Your Machine └── VM (10.0.0.x) ← only reachable via port forwards ``` | | Bridged | NAT | |---|---|---| | VM gets a local network IP | Yes | No | | Reachable from your machine | Directly | Only via port forwards | | Works with /etc/hosts | Yes | Needs extra config | | Recommended for this lab | ✅ | ❌ | **How to set bridged networking:** - **VirtualBox:** VM Settings → Network → Adapter 1 → Attached to: Bridged Adapter → select your physical network interface - **VMware:** VM Settings → Network Adapter → Bridged - **Proxmox:** Set the network device to `vmbr0` in the VM hardware settings - **Hyper-V:** Use an External virtual switch connected to your physical adapter - **Cloud providers:** Not applicable — your VM already has a routable IP --- ### Ubuntu Server VM Preparation Once the VM is running with Ubuntu Server installed: ```bash # Update the system sudo apt update && sudo apt upgrade -y # Confirm the VM has a routable IP on your local network ip addr show ``` Look for an IP address on your main network interface (usually `eth0` or `ens18`) that falls in your home network subnet — typically `192.168.x.x`. This is the IP you will add to `/etc/hosts` on your local machine later. --- ## Part 2 — Installing K3s With the VM running and reachable, K3s installs via a single command. The installer downloads the binary, configures systemd, sets up kubectl, and starts the cluster automatically. ### Install ```bash curl -sfL https://get.k3s.io | sh - ``` After it completes: ```bash # Verify the node is ready sudo kubectl get nodes # Expected output: # NAME STATUS ROLES AGE VERSION # your-vm Ready control-plane,master 1m v1.x.x+k3s1 ``` ### Set Up kubectl Without sudo By default K3s writes its kubeconfig to `/etc/rancher/k3s/k3s.yaml` which requires root to read. Copy it to your user's home directory: ```bash mkdir -p ~/.kube sudo cp /etc/rancher/k3s/k3s.yaml ~/.kube/config sudo chown $USER:$USER ~/.kube/config # Verify kubectl works without sudo kubectl get nodes ``` ### Confirm K3s Is Running as a Service K3s registers itself as a systemd service and starts automatically on boot. ```bash # Check service status sudo systemctl status k3s # Confirm it starts on boot sudo systemctl is-enabled k3s # Should return: enabled ``` --- ## Part 3 — Verifying Built-in Components K3s ships with several components pre-installed that this lab depends on. Verify they are all running before deploying anything. ### Traefik — Ingress Controller ```bash kubectl get pods -n kube-system | grep traefik # Expect: traefik-xxx 1/1 Running ``` Traefik is the default ingress controller in K3s. It listens on port 80 (and 443) on the node and routes incoming HTTP requests to the correct service based on the `Host` header. This is what lets you access `keycloak.local` and `whoami.local` without specifying a port number — all apps share port 80, differentiated by hostname. ### ServiceLB — Load Balancer ```bash kubectl get pods -n kube-system | grep svclb # Expect: svclb-traefik-xxx 2/2 Running ``` ServiceLB (formerly Klipper) is K3s's built-in load balancer. It binds Traefik's ports to the VM's actual network interface, making the cluster reachable from outside the VM. Without this, Traefik would only be accessible from inside the VM itself. ### Verify Traefik Has an External IP ```bash kubectl get svc -n kube-system traefik ``` Expected output: ``` NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) traefik LoadBalancer 10.43.x.x 192.168.x.x 80:xxxxx/443:xxxxx ``` The `EXTERNAL-IP` column should show your VM's IP address on your local network. This confirms Traefik is bound to your VM's network interface and is reachable from outside. Note this IP — you will need it when setting up `/etc/hosts`. If `EXTERNAL-IP` shows `<pending>` after a few minutes, see the troubleshooting section below. ### CoreDNS — Internal Cluster DNS ```bash kubectl get pods -n kube-system | grep coredns # Expect: coredns-xxx 1/1 Running ``` CoreDNS handles DNS resolution inside the cluster. It is what allows pods to reach each other by name rather than IP address — for example, `oauth2-proxy` reaches Keycloak via `http://keycloak.keycloak.svc.cluster.local` using the pattern `<service-name>.<namespace>.svc.cluster.local`. --- ## Part 4 — Understanding the Networking Stack This is worth understanding before deploying anything — networking is where most issues originate in a local lab setup. ### How a Request Travels from local Browser to Pod ``` Your Machine /etc/hosts maps hostname → VM IP │ ▼ Network delivers packet to VM's network interface │ ▼ K3s ServiceLB has bound port 80 to the VM's interface │ ▼ Traefik receives the request Reads the Host header (e.g. keycloak.local) Matches against Ingress rules │ ▼ Routes to the correct internal ClusterIP service │ ▼ Service forwards to a matching pod │ ▼ Pod handles the request and responds ``` --- ## Useful Commands Reference ```bash # Check all nodes and their status kubectl get nodes # Check all pods across all namespaces kubectl get pods -A # Check Traefik's external IP kubectl get svc -n kube-system traefik # View K3s service logs sudo journalctl -u k3s -f # Restart K3s sudo systemctl restart k3s # Check K3s version k3s --version # View the kubeconfig cat ~/.kube/config ``` --- ## Troubleshooting ### Node shows NotReady after install ```bash sudo systemctl status k3s sudo journalctl -u k3s -n 50 ``` Usually a resource constraint (not enough RAM) or a network issue during binary download. Try rerunning the install command. If RAM is the issue, shut down other VMs and try again. ### EXTERNAL-IP stays as pending on Traefik ServiceLB cannot bind to a routable network interface. Check: ```bash # Are the svclb pods running? kubectl get pods -n kube-system | grep svclb # What IP does the VM have? ip addr show ``` If you are running locally, the most common cause is NAT networking on the VM. Switch to bridged networking in your hypervisor settings, reboot the VM, and check again. The VM must have an IP on your local network for ServiceLB to bind to. ### Cannot reach the VM IP from your local machine ```bash # From your local machine, ping the VM ping 192.168.x.x ``` If ping fails, the VM is not on the same network as your machine. Confirm bridged networking is configured correctly in your hypervisor. For VirtualBox, make sure you selected the correct physical network adapter under the bridged setting. ### kubectl: connection refused The kubeconfig points to `127.0.0.1` by default, which works when running kubectl directly on the VM. If you want to run kubectl from your local machine instead of SSHing in each time, copy the kubeconfig and update the server address: ```bash # On the VM cat ~/.kube/config # Copy the contents to your local machine at ~/.kube/config # Then replace 127.0.0.1 with your VM's actual IP ``` --- *Infrastructure layer for the Keycloak + OIDC on K3s lab series*