10.11 - Install and First Cluster

Three options. Pick ONE as your main cluster.

  • Option A: native k3s on NixOS (recommended). A real systemd-managed cluster, uses its own containerd (no Docker), most reliable on your box.
  • Option B: k3d, k3s in Docker. Best for simulating a MULTI-NODE cluster (server + agents) on one machine. Has caveats under rootless Docker.
  • Option C: k3sup, for a remote VPS. Mentioned for completeness, not needed for this week.

Add to your NixOS system config (configuration.nix, not home-manager):

{ pkgs, ... }:
{
  services.k3s = {
    enable = true;
    role = "server";            # "server" = control plane + worker in one
    # token = "changeme";       # set this when you later add agent nodes
    # Write kubeconfig world-readable so you can use it without sudo:
    extraFlags = ''
      --write-kubeconfig-mode 644
      --disable traefik
      --disable servicelb
    '';
  };

  environment.systemPackages = with pkgs; [ kubectl k9s ];
  # Add k3d too if you also want multi-node simulation (Option B):
  # environment.systemPackages = with pkgs; [ kubectl k3d ];
}

Why --disable traefik --disable servicelb: k3s’s Traefik ingress controller and its built-in LoadBalancer (servicelb) both want to bind host ports 80/443, which Caddy already owns. For week 1 you use kubectl port-forward and NodePort (30000-32767 range) instead, which never touch 80/443. On the networking day you can re-enable Traefik on alternate ports (10.41 has the recipe).

Apply and verify:

sudo nixos-rebuild switch --flake ~/nix#<your-host>

systemctl status k3s                 # should be active (running)
sudo journalctl -u k3s -f            # watch it boot; Ctrl-C to stop following

Make kubectl usable without sudo (NixOS k3s already passes –write-kubeconfig-mode 644 from above, so this is a fallback):

sudo cp /etc/rancher/k3s/k3s.yaml ~/.kube/config 2>/dev/null
# if ~/.kube doesn't exist yet: mkdir -p ~/.kube && sudo cp ...
sudo chown "$USER" ~/.kube/config
chmod 600 ~/.kube/config

Sanity checks (run all three; all must succeed):

kubectl get nodes -o wide          # one node, STATUS=Ready
kubectl get pods -A                # coredns + metrics-server + local-path
kubectl get svc -A                 # kubernetes service (ClusterIP)
kubectl cluster-info               # control plane is reachable

Fish niceties:

alias k kubectl
kubectl completion fish | source

Option B: k3d (multi-node simulation in Docker)

Useful later in the week and for any “cluster of 3 nodes” experiments. k3s runs inside Docker containers, so your host system stays untouched and you can destroy/recreate clusters freely.

nix shell nixpkgs#k3d -c k3d --help   # or add k3d to systemPackages

k3d cluster create lab --servers 1 --agents 2
# waits ~60s, then:
kubectl get nodes -o wide              # 3 nodes: k3d-lab-server-0, -agent-0, -agent-1
kubectl get pods -A

k3d cluster delete lab                 # full teardown, instant reset

For ingress experiments later you can publish a host port to Traefik:

k3d cluster create lab --servers 1 --agents 2 --port "8080:80@loadbalancer"

Rootless Docker caveat: k3d’s k3s containers want privileged mode and certain kernel features. Under rootless Docker this sometimes works and sometimes hangs (pods stuck in ContainerCreating). If k3d cluster create succeeds but pods never become Ready, don’t fight it: use Option A as your main cluster. k3d is a nice-to-have for simulating multi-node, not a requirement.

Option C: k3sup (remote VPS, skip for now)

nix shell nixpkgs#k3sup -c k3sup install --ip <vps-ip> --user root
# sets up k3s over SSH and drops a kubeconfig on your machine

First pod, first deployment (Day 1 exercise)

# 1. Imperative quick test (fine for a smoke test)
kubectl run hello --image=nginx:alpine
kubectl get pods
kubectl logs hello
kubectl delete pod hello

# 2. Declarative, the way you will actually work
kubectl create deployment hello --image=nginx:alpine --replicas=2
kubectl get deployments,replicasets,pods
kubectl get pods -o wide
kubectl port-forward deployment/hello 8080:80   # open http://localhost:8080
kubectl exec -it deployment/hello -- bash
kubectl logs deployment/hello --tail=20
kubectl delete deployment hello

Troubleshooting the install

Symptom Likely cause Fix
kubectl says connection refused API not up yet, or wrong kubeconfig journalctl -u k3s -f; check kubectl config current-context
get nodes works but pods stuck image pull on first run is slow wait 60-120s, kubectl get pods -A again
Traefik pod crashlooping port 80/443 already taken (Caddy) you disabled traefik above; if not, sudo k3s kubectl -n kube-system delete deploy traefik or reinstall with the flag
kubeconfig permission denied file owned by root run the chown/chmod block above
Can’t reach NodePort from host firewall k3s opens 30000-32767 via firewalld only on some distros; on NixOS add networking.firewall.allowedTCPPortRanges = [{ from = 30000; to = 32767; }]

Next: 10.12 - Core Concepts for the mental model, then 10.21 - Week 1 Plan for the day-by-day schedule.


This site uses Just the Docs, a documentation theme for Jekyll.