So about that Docker Swarm post: I moved to k3s
August 15, 2020
Last November I wrote a guide on configuring Docker Swarm. Two weeks later, Mirantis bought Docker’s enterprise business and gave Swarm what sounded like a two-year countdown. They’ve since softened that to “we’ll keep supporting it,” but the writing is on the wall, and the wall is written in YAML.
So this summer I finally did the thing I’d been avoiding since 2017: I learned Kubernetes properly. The trick that made it not miserable is that I didn’t install Kubernetes. I installed k3s.
k3s: Kubernetes without the ceremony
Rancher’s k3s is a certified Kubernetes distribution compressed into a single ~50MB binary. It swaps etcd for SQLite, strips the legacy cloud-provider baggage, and ships batteries included (Traefik as ingress, out of the box). Install is genuinely this:
curl -sfL https://get.k3s.io | sh -
sudo k3s kubectl get nodeThirty seconds later there’s a working single-node “cluster” on my €5 Hetzner VPS, idling at around 500MB of RAM. That’s the entire moat between “Kubernetes is for companies with a platform team” and “Kubernetes runs my hobby junk” — and it turns out the moat was just packaging.
The migration
My Swarm stack was this blog, a couple of side projects, and — because the theme of this year apparently insists — the GPT-2 commit-message server from April, still faithfully dealing me chores every morning.
kompose will mechanically convert a compose file into manifests, but I recommend writing them by hand at least once, because the pain is educational. A Swarm service becomes three objects: a Deployment (what to run), a Service (how to reach it), an Ingress (how the outside reaches the Service). Add cert-manager and Let’s Encrypt certificates become just another object the cluster keeps renewed for me — the first moment where the K8s way felt better rather than merely fancier.
Lessons collected the honest way:
- Set resource limits immediately. The GPT-2 pod happily ate the whole node until the OOM killer introduced itself.
resources.limits.memory: 900Miand we’re friends again. - The Traefik v1 that ships with current k3s reads standard Ingress objects fine; ignore old tutorials full of custom annotations.
- Helm 3 (tiller is gone, good riddance) for other people’s software, plain manifests in git for mine.
- You will write a stupid amount of YAML. There’s no enlightenment coming on this one; buy a better keyboard.
The part where it finally clicked
Here’s the mental model I wish someone had led with, instead of diagrams of pods.
Swarm — and honestly most of the tools I grew up on — is imperative: you issue commands, it executes them, done. docker service scale web=3 is an action.
Kubernetes is not an orchestrator that runs your commands. It’s a database of desired state with robots attached. You write down a fact you’d like to be true — “three replicas of this exist, reachable at this name, with valid TLS” — and control loops compare that desire to reality, forever, and push reality toward it. Node dies at 3am? Reality drifted from the spec; the loop corrects it. Nobody, including you, issued a command.
You don’t tell Kubernetes what to do. You tell it what should be true.
And I can’t stop noticing that this is the same shape as my complaint about language models this spring — that GPT-2 executes text-continuation without any notion of your intent, and that the missing piece is a system trained to pursue what you meant. Kubernetes is what it looks like when infrastructure makes that leap: from “execute my steps” to “converge on my intent.” Declarative beats imperative for the same reason in both worlds — steps go stale, intent doesn’t. Apparently 2020 is the year I learn this lesson once per quarter in a different domain.
Do you need this?
No. Let’s be adults: for one VPS of hobby projects, docker-compose plus a systemd unit is fewer moving parts and I’d defend that choice in court. I did it because Swarm’s future is cloudy, because the industry has voted and K8s won, and because the reconciliation-loop model is the actually transferable knowledge — it’s the same idea underneath Terraform, GitOps, and every “controller” you’ll meet for the next decade.
But I’ll admit the real reason: there is something deeply pleasing about a server that repairs itself while I sleep. The commit-message bot went down twice this month. Both times, by the time the uptime alert reached my phone, the pod had already been rescheduled and the alert resolved itself.
The robot assigns me chores, and other robots keep the robot alive. The system works.