6.1 KiB
Stage 1: Bootstrap
This project is a four-stage tutorial, each stage building on the last:
docs/01-bootstrap.md(this doc) — one-time host setup: KVM/libvirt, an unprivileged user to run everything as, and pushing this repo to Forgejo. Nothing here is specific to k3s, Flux, or Terraform — every later stage depends on it.docs/02-k3s.md— build one VM by hand and get a real k3s cluster running on it. No Flux yet, no Terraform — just a working cluster and a kubeconfig that talks to it.docs/03-flux.md— bootstrap Flux against that cluster and deploy this repo's apps through it, including what Helm chart management looks like under GitOps (podinfo, headlamp) versus plain manifests (hello-app), and the CI loop that builds and auto-deploys hello-app.docs/04-tofu.md— graduate from the one hand-built VM to a proper 3-node cluster provisioned by Terraform/OpenTofu, and point the same Flux config at it.
Each stage says exactly which earlier steps it actually depends on, rather than assuming you need everything done up front — stage 2, for instance, only needs steps 1-2 below.
Assumes: the T630 is an existing Debian box already running other
self-hosted services — this project installs alongside those as ordinary
packages (qemu-kvm/libvirt), not a hypervisor OS replacing Debian.
Forgejo is already running and reachable at https://git.boglabob.com, and
you can point DNS records under boglabob.com at hosts on your network
(directly, or via whatever reverse proxy/tunnel already gets
git.boglabob.com there).
1. Install KVM/libvirt on the T630
Ordinary packages, no reboot into an installer, nothing else on the box is touched:
# on the T630
sudo apt update
sudo apt install -y qemu-kvm libvirt-daemon-system libvirt-clients virtinst
# confirm hardware virtualization is available (T630's Xeons support it)
sudo kvm-ok
2. Create the unprivileged 'k8s' user
One dedicated, no-sudo user for everything this project touches: driving
virsh/kubectl/flux/tofu against libvirt, and running the Forgejo
Actions runner later (docs/03-flux.md step 6). It needs group membership
to talk to libvirt — that's a one-time root action; nothing it does
afterwards needs sudo.
sudo useradd -m -s /bin/bash k8s # one-time, needs root to create the user itself
sudo usermod -aG libvirt,kvm k8s
sudo loginctl enable-linger k8s # lets its services keep running after logout
# as k8s, from here on (sudo -iu, not su -, since k8s has no password set):
sudo -iu k8s
ssh-keygen -t ed25519 -C "k3s-homelab" -f ~/.ssh/id_ed25519 # only needed if you'll SSH in as k8s day-to-day
virsh -c qemu:///system list --all # sanity check: should run with no permission error, no sudo
Do the rest of this project logged in as k8s on the T630 itself (ssh k8s@t630) — VM IPs live on a private libvirt network that's only directly
reachable from the T630, so this is the simplest place to run
kubectl/flux/tofu from. (If you'd rather drive Terraform from your
own workstation instead once you reach stage 4, see the libvirt_uri
comment in terraform/terraform.tfvars.example — you'll then need an SSH
tunnel for kubectl/flux to reach node IPs.)
3. Generate the secret k3s needs
openssl rand -hex 32 # -> k3s_token
This is the shared token agent nodes use to join a k3s server — irrelevant for stage 2's single-node VM (a lone server needs no one to join it), but generate it now while you're doing one-time setup; stage 4's Terraform-provisioned multi-node cluster is what actually uses it.
4. Push this repo to Forgejo
Doing this before any cluster exists (rather than after) means k8s can
get the repo with a plain git clone later, instead of needing a one-off
copy handed to it — and any future change to this repo just needs a
git pull on the T630. Repo/owner used throughout this project:
codegit/cloud-demo (already baked into apps/hello-app/deployment.yaml
and image-automation.yaml's image references — no placeholder-swapping
needed).
-
On Forgejo (
https://git.boglabob.com), ascodegit: + → New Repository → namecloud-demo. Leave it empty — don't initialize with a README/.gitignore/license, since this repo already has its own. Visibility (public/private) is your call; either works, since access fork8s/Flux/CI goes through the tokens below regardless. -
Locally, wherever you're editing this repo (
maq):git init # if not already git add . git commit -m "initial scaffold" git remote add origin https://git.boglabob.com/codegit/cloud-demo.git git push -u origin main -
Generate two access tokens (
Settings → Applications → Generate New Token), scoped as narrowly as Forgejo's token UI allows to repository read/write:k8s-readonly— read-only. Used only fork8s's own manualgit clone/pullon the T630 — never leaves that box, isn't used by anything automated. Not needed untildocs/04-tofu.md(that's the first stage that clones this repo onto the T630 rather than editing it from your workstation).flux-write— read/write. Used once, as aflux bootstrapargument (docs/03-flux.mdstep 2); Flux stores it as a Kubernetes Secret inside the cluster from then on (ImageUpdateAutomation's commits back, in the hello-app section of that stage, reuse that same in-cluster Secret) — it's never written tok8s's filesystem at all.
Using HTTPS tokens instead of
k8s's SSH key (~/.ssh/id_ed25519, from step 2) sidesteps an open question: Forgejo's git-SSH port isn't reachable from this desktop through your router (see the SSH troubleshooting earlier in this project's history), and whether it's reachable fromk8son the T630 itself was never actually confirmed either. HTTPS (443, via Caddy) is already proven to work, so both tokens use that instead. Copy both token values now — Forgejo only shows them once.
Next: docs/02-k3s.md — you only need steps 1-2 above to start it.