Part 8: A cluster of things I already owned
The metal, then. With the 42U of beauty from my student years long gone and far overpowered by time, the first version of this cluster was a Raspberry Pi 4 with Kubernetes' control plane on an SD card, and a 2013-era Intel NUC that used to run Eucalyptus. This is madness, we all know that, and while an upgrade was the fix, the defence is the lesson.
Factory-made everything
What makes Talos different from every other homelab Linux isn't only the immutability - I covered that. It's that the entire onboarding path is manufactured for you, in every sense of the word. Talos ships the OS itself from their "factory" as a signed, versioned image for basically any platform you can name: x86_64, arm64, bare metal, cloud, Pi. You pick your platform, your revision, your extensions, and you get an image that is the operating system. There is nothing to install in the classical sense - you flash it, boot it, and the node sits there waiting for instructions over its API like a very patient appliance.
And the Kubernetes part - the part every other route makes you earn - is simply included. If you've ever done this the artisanal way, you know the ritual: install a distro, tune the kernel, configure containerd, then kubeadm init, then debug why the CNI didn't come up, then the control plane endpoint dance, then certificates, then the fifty small flags that the tutorial you followed in 2021 doesn't mention anymore. kubeadm is a good tool, but "good tool you must operate correctly" is homelab drag with a Kubernetes accent. With Talos, talosctl gen config hands you the machine configs and the cluster config in one shot, the node boots with everything wired - etcd, kube-apiserver, containerd, the CNI choice of your config - and the control plane appears. Not "eventually, after an evening of sed." It appears. The first time I watched it happen I kept waiting for the part where it goes wrong, because fifteen years of Linux had taught me that the part where it goes wrong is mandatory. It didn't come. I found that suspicious for about a week.
One genuinely nerdy detail that I'll only mention because this blog is already self-indulgent: Talos doesn't boot into a normal userspace and then start Kubernetes. It boots a minimal init, mounts an immutable rootfs, and launches the whole Kubernetes stack as system services from signed images - with a kernel kexec into a freshly provisioned environment on first boot in some paths, so the machine that ends up running your control plane is not literally the machine that first spun. The OS you booted is not the OS you run. That is the philosophy in one sentence, and it's the reason upgrades can be atomic: the running system is an artifact, not a history.
Tailscale, built in
The second thing that won me over is integration with Tailscale - and I need to be precise, because "Talos + Tailscale" means two different things and the difference matters. Talos ships the Tailscale client as a first-class extension in the machine config. That gives you node-to-node networking: the nodes reach each other over a WireGuard mesh, across networks, without me touching firewall rules or trusting my LAN's flatness. This is about the cluster's plumbing. It is entirely separate from the Tailscale operator, the Kubernetes-native side that hands out IPs and publishes services into your tailnet - that's a story for a later post, and it's a good one, but don't conflate the two. One is how the nodes talk to each other. The other is how other things talk to the cluster.
For me, node-to-node was quietly enormous, though not for the reason you might guess. My network is not the problem - far from it. The VLANs are deliberate, the interfaces each have a purpose, nothing about the physical layout is accidental. The reason Tailscale went into the machine config is future-proofing. I want to be able to provision VMs elsewhere - any cloud, on demand, automatically - and have them join this cluster without me redesigning anything. Labels and selectors are how I intend to keep that sane: pods land where they need to be, and more importantly, where they should not. Longhorn is bound to the rack nodes by labels, so replication cannot creep somewhere it was never meant to go. Workers joining from anywhere is only half the requirement, though - the control plane has to be reachable from anywhere too, and that is where Tailscale does the real work: the mesh makes the API server reachable from any network a worker ends up on, and its ACL policy lets me enforce proper access control over that WireGuard network rather than trusting whatever subnet a node happens to sit behind. That plan hasn't materialized yet. This is the groundwork.
The hardware, and the madness
So: what did I actually run?
The control plane was a Raspberry Pi 4, booted from an SD card. Let me sit with that a moment, because I know exactly what you're thinking and you're right. Running etcd - the component whose entire job is to never, ever lose state, and whose write performance defines your API server's latency - on flash storage whose wear characteristics are famously terrible, whose write endurance is measured in unknowns, and which will absolutely corrupt itself under sustained load... is madness. I knew it when I did it. My defence is this: I already owned the Pi 4, already in a drawer, and drew about as much power as a nightlight. For a learning cluster - "does my design hold up, does my config surface work, can I actually operate this thing" - it was the correct choice, as a choice. What it was not, and I want to be honest about the distinction, was a long-term substrate for anything I cared about.
The worker was better: a 2013-generation i3 NUC, which had spent its previous life running Eucalyptus testing back when I was a TAM and OpenStack's sparring partner was still a punchline. Close to over 10 years old, but 16GB of RAM, and a CPU that predates half the Kubernetes ecosystem's assumptions about modern instructions. But it had a real SSD, real x86, and - this is the part that matters - a history. There's something fitting about a machine that once ran one of Kubernetes' spiritual ancestors coming back to run the real thing.
And it worked fine. For a long while, even. Cluster came up, workloads ran, upgrades happened, the Tailscale mesh held, the immutable-config discipline stuck. This is the trap, by the way - the first homelab cluster always works fine, and "works fine" is the most dangerous state a homelab can be in, because it's the state in which you stop treating it as a prototype and start treating it as infrastructure. I ran this thing long enough that my own e-mail began to depend on it.
Which is, of course, exactly the moment you're not supposed to be running your e-mail on a Pi 4 with etcd on an SD card and a 2013 NUC as the only worker. There is no redundancy. There is no meaningful failure mode for "the SD card wears out" except a full control-plane restore from backups I was confident about the way you're confident about things you've never actually tested under pressure. It was McGyver infrastructure - brilliant, cheap, held together with intent and luck, and completely unacceptable as the substrate for the thing my household actually runs on.
However, my years of planning for failure did bring something: the eBFP CNI with BGP support, the placement rules and otherwise settings that allow for future expansion to be seemless, were in place, ready for the next step.
And so I did what you do when McGyver infrastructure starts carrying real weight: I took it up a notch. That's the next post - what it actually took to turn a drawer-parts cluster into something I'd put my mail on, without pretending I suddenly had a datacentre budget.