4.1 KiB
Quickstart: manual cluster (no Terraform)
Gets a real k3s cluster running today, by hand, so you can learn/practice
Flux and GitOps immediately instead of waiting on the Terraform/libvirt
provider work in docs/SETUP.md to be sorted out. One throwaway VM, created
directly with virt-install rather than the dmacvicar/libvirt Terraform
provider. When the Terraform track is ready, tear this down (last section)
and provision the "real" 3-node cluster properly instead.
Shares steps 1–4 of docs/SETUP.md as prerequisites — do those first if you
haven't:
- Step 1: KVM/libvirt installed on the T630.
- Step 2: the unprivileged
k8suser exists, with~/.ssh/k3s_homelabgenerated. - Step 3:
openssl rand -hex 32isn't needed here (no agents joining, so no cluster token) — skip it. - Step 4:
cloud-demopushed to Forgejo, with theflux-writetoken generated.
Everything below runs as k8s on the T630 (sudo -iu k8s).
1. Create the VM
mkdir -p ~/vms
curl -L -o ~/vms/noble-base.img \
https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img
# a writable overlay on top of the base image, sized up to 20G
qemu-img create -f qcow2 -F qcow2 -b ~/vms/noble-base.img ~/vms/k3s-manual.qcow2 20G
Write the cloud-init user-data — paste in the contents of
~/.ssh/k3s_homelab.pub where marked:
cat > ~/vms/k3s-manual-user-data.yaml <<'EOF'
#cloud-config
hostname: k3s-manual
manage_etc_hosts: true
users:
- name: k3s
groups: sudo
shell: /bin/bash
sudo: ALL=(ALL) NOPASSWD:ALL
ssh_authorized_keys:
- PASTE ~/.ssh/k3s_homelab.pub CONTENTS HERE
package_update: true
packages:
- curl
runcmd:
- curl -sfL https://get.k3s.io | sh -s - server
- mkdir -p /home/k3s/.kube
- k3s kubectl config view --raw > /home/k3s/.kube/config
- chown -R k3s:k3s /home/k3s/.kube
EOF
This is a single, standalone server — no join token, no agents, no custom network (uses libvirt's default NAT network + DHCP). A k3s server node schedules workloads on itself by default, so this alone is a complete, usable cluster.
virt-install \
--name k3s-manual \
--memory 2048 \
--vcpus 2 \
--disk ~/vms/k3s-manual.qcow2 \
--import \
--os-variant ubuntu24.04 \
--network network=default \
--cloud-init user-data=~/vms/k3s-manual-user-data.yaml \
--graphics none \
--noautoconsole
2. Find its IP and get kubectl talking to it
Give cloud-init ~2 minutes to finish installing k3s after the VM boots.
virsh domifaddr k3s-manual # note the IP under the default network
ssh k3s@<VM_IP> sudo cat /etc/rancher/k3s/k3s.yaml \
| sed "s/127.0.0.1/<VM_IP>/" > ~/.kube/config-manual
export KUBECONFIG=~/.kube/config-manual
kubectl get nodes # expect 1 Ready node
3. Bootstrap Flux against Forgejo
Same repo, same token, same target path as the full guide would use — nothing about this is cluster-specific:
flux bootstrap git \
--url=https://git.boglabob.com/codegit/cloud-demo \
--branch=main \
--path=clusters/homelab \
--username=codegit \
--password=<FLUX_WRITE_TOKEN> \
--token-auth \
--kubeconfig ~/.kube/config-manual
4. Continue with the shared steps
From here, docs/SETUP.md steps 8 onward apply exactly as written,
regardless of how the cluster was created — verifying the podinfo GitOps
loop, registering the Forgejo Actions runner, exposing apps through Caddy,
the Dashboard, remote kubectl access. Just use ~/.kube/config-manual as
the kubeconfig throughout instead of the Terraform-provisioned one.
Tearing this down
Once the Terraform track in docs/SETUP.md is ready and you tofu apply
the real 3-node cluster, remove this one:
# as k8s
virsh destroy k3s-manual # stop it
virsh undefine k3s-manual --remove-all-storage # delete VM + its disk
rm ~/.kube/config-manual
rm -rf ~/vms
Nothing else needs cleaning up — Flux's state lived entirely inside that
VM's cluster and goes away with it. The Forgejo repo, deploy tokens, and
the Forgejo Actions runner registration are all cluster-independent and
carry over to the real cluster unchanged; just re-run flux bootstrap
against its kubeconfig once it's up.