A skeptic's first contact with Kubernetes (2024)
blog.davidv.dev
blog.davidv.dev
It's very error prone to describe a hierarchy of versioned software packages and configuration with shell scripts and yaml. Or use text files in git as a database. Or use the CI system as a bad substitute for a proper service (in order to do a simple thing, you must first create the world from scratch). And so on. Mainstream industry knows types, programming languages, relational databases, graphs and you name it, but those have not yet penetrated sufficiently into the infrastructure world. The understanding of the value of having types has been paid in sweat and blood when developing software and databases. Even a basic CRUD application with a basic SQL database will run rings around these Frankenstein monsters: in safety, speed, and extendability.
We have a multitude of configuration/markup languages all doing the exact same thing.
We have solved this problem in the 60s: S-Expressions. Should have just used that for everything. Configuration, code, websites, you name it. Infinitely extendable, easy to parse.
They say they don't want it but every configurmation format eventually ends up becoming turing complete at some point.
Cue also has a lot of schema validation which is better than dynamic languages.
That seems to be everyone's initial goal, but then it inevitably gets leaky.
I think the dominant split here that forces everything into a weird mismash is that nobody really wants to have to understand arbitrary programming language configs for third-party stuff, they want to read config and know what's going on. BUT for their own stuff, they find reasons to want more power and expressibility...
This is very much true. And until they do get leaky, you have to wrap them in all sorts of weird templates or other forms of abstraction.
This is one of the reasons I used to really like Chef's config language. It was a Ruby DSL, which meant that anything the DSL couldn't do natively, you could just use Ruby right there.
Of course, this is easily misused, but if you were careful it was as powerful as it was elegant.
If it compiles, it’s almost certainly correct.
And if you really love sexps, then writing a bunch of arrays in JSON is also viable. If you asked me 10 years ago I might've complained about JSON, but now I think we messed up by having a bunch of other formats, tbh. I have my own TUI JSON editor that can render in tree view, miller tree view, flat view, and inferred table view, and it makes editing JSON very easy too.
Oh and duckdb can query against compressed json and jsonl at GB/s
A reliability engineer's nightmare, and a common one, is stepping into a company whose deployment pipeline is built in-house, with dynamic bespoke software. What happens when Jeff leaves to build his own startup and absolutely no one knows how that Node.js script shells out to SSH to call docker on a remote server? I can tell you what happens: everything works fine for awhile and then suddenly doesn't for awhile.
On the other hand, you join a company with K8s and plain-jane YAML and can instantly grok the entire system and fix prod on your first day while the S-team sweats behind you, watching every key stroke. The seconds I have to read some javascript or bash or perl are precious seconds the brand is suffering.
In software engineering, flexibility and abstraction is a benefit: the product must pivot and evolve. In reliability engineering we want dead simple, tried and true. You want to be able to step outside your SF office and say "Someone help me with EKS, my DevOps guy died last night in a car crash!" and have 4 people with wild hair take their weird off-brand not-air-pods out of their head and say "uhhh sure okay".
Does all of this change with AI? Yes. Did it make sense a year ago for business continuity reasons? Yes. Was it ever beautiful where the rubber hit the road? No.
Why aren't tires as complex as wiring harnesses?
The problem is that that's not true in practice. It's a myth, and always has been for all but the most trivial setups. Infrastructure is now highly dynamic and, like old cities, becomes more complex, calcified, and non-uniform with age.
Infrastructure configuration is an excellent example of the blurriness of code-vs-configuration. I'm now several decades into my career, and I'd rather have pure code generate my configurations than some Frankenstein's monster of "mostly config but lots of code in there when it gets too complicated" UX. Config file formats always evolve into this mess eventually, and a lot of my fellow practitioners don't seem to recognize this problem, leaving the cycle to continue.
The whole problem on the infrastructure side is that the APIs are slow and you don't have exclusive access. Building the CRUD app would be a million times harder if SQL calls took 100x longer to execute and other people could randomly reach into the database and change the schema and data at will - can your basic CRUD app gracefully handle somebody shoving newlines at the end of usernames? Odds are that the first time, no, not until you put a trim() call. And then you cry in pain at needing to put trim() calls everywhere. And then you gnash your teeth at people reporting performance problems because of all the defensive checks you put in because all trust has been thrown out. And then you find out that trim() was deprecated and will now be replaced by strim() that handles Unicode a little differently, and you need to port your code before End of Life to stay compliant.
A basic CRUD application with a basic SQL database doesn't solve the problem, because you don't seem to appreciate that there already is a database - however your vendor stores configuration - and it's your job to deal with it. Adding another database on top of it would make the problem worse.
If you are infrastructure software minded, you might store the data in files that are stored in git. For users to read the data, they can perhaps browse the repo or clone it. For some more complex questions you might have a document which tells the user how to fire a CI pipeline that runs some code over the data and the user has to read the pipeline logs to get the information. The CI pipeline will probably run in a container which will produce a large log and take a long time to start.
If you are software software minded, you store the values in a database, make a service with a REST or some other API and create a browser UI. User can browse the data in a table view and perform searches and perhaps there's some more complex business logic executed on the backend to get answers to some more complex questions.
In this case, both systems can easily do the job. But the solutions are very different.
I’ve done some NGO-related work and in that context, they talk about “appropriate technology” - technology that’s suited to the local population, among other things. I’ve started to see tools like Helm and languages like Go as appropriate technology for many teams, but that doesn’t mean I’m going to switch to a pedal-powered water pump in my own house.
But no one does that because it is significantly uglier. So I guess we don't deserve sanity because we don't want our programs to be ugly?
And plenty more exist today. But helm got popular first, so due to the collective action problem it's going to take the industry forever to switch off it.
Yes, but it might throw errors because the client library you're using is too old and attempts to define fields that no longer exist in your newer infrastructure API. You do not find these errors at compile-time, but rather at runtime, and only after both serialization and API request execution finish and you get an error from the API, in the best case, or a silent failure misconfiguring your infrastructure, at worst.
The only time an infra engineer would trust the types is if the vendor themself supplied them, or there's a mainstream community effort to keep them up to date, or they're maintained in-house with procedures to try to keep them up-to-date with new releases. At the end of the day, the API takes a string, not some protobuf or CORBA or God knows what binary serialized monster dujour...
Good news! The Kubernetes project has a documented compatibility strategy (https://kubernetes.io/docs/concepts/overview/kubernetes-api/...) and publishes new client library versions for every release. There's endpoints in the API to interrogate the API's versions and supported fields/features for every resource. Additionally, most API types will accept old versions and automatically convert them to the newer versions, wherever this is possible.
> At the end of the day, the API takes a string, not some protobuf or CORBA or God knows what binary serialized monster dujour...
This is not necessarily correct. Kubernetes API supports JSON, YAML, protobuf or CBOR over the wire, and all become in-memory structs on the server.
Well. Don’t use a client library that is too old then? Like you do in any other kind of software deployment, where you update your client libraries together with the upstream API you talk to?
Yeah, sure, I'll file that away with the NPM "tens of thousands of tiny dependencies is OK" thinking. Constant dependency upgrades is a nightmare. There's a reason why shell + Helm is popular - they're stable.
I've done some experiments using Apple's Pkl and CNCF's KCL, which look to solve these problems with a simple "configuration language".
I don't think either have very widespread adoption, though I did hear a while back that Apple is using Pkl internally.
I don't even mind text interpolation in helm that much. Yes, it's horrific, but it really doesn't become problematic until you are having an external system render the text being sent to helm that might be processed again after the fact with Kustomize. Your text might hit literally 3-4 different YAML rendering pipelines before it finally hits your cluster. Insanity
Hashicorp had a similar idea to this configuration language you are mentioning. They named it HCL. We do know how that turned out, but maybe that was more questionable design rather than problems endemic to the approach.
What takes effort for humans, takes effort for AI too.
And yeah, HCL was a horrible design. Anything else would have been better, Cue, jsonnet, dhall, pkl, etc. (I'm not sure which of those existed when HCL was created.)
If your agent is doing this you need to get a much better agent. It should call `helm lint` to find those errors.
For a single source of truth for YAML/JSON such as for Kubernetes manifests, any of the ones you list are fine though and a vast improvement on using Go templates for it.
(Yes I've used all of them - and indeed did the first public talk about Pkl two days after it was open-sourced, after several years of using it internally at Apple).
String interpolation is a pox on the ecosystem, especially given how YAML was only originally a debug helper...
Because it's probably an impossible to answer question hinging on each person's pet definition of "stable".
In one example of an unstable cluster, pods are "regularly" crashing — but at a really low frequency, like, each pod crashes onces every few days. They're OOM'ing — a bug in the workload, not really the cluster's fault — but the control loop pattern still ensures the failure is corrected in the sense that the pod will either be restarted or evicted and re-created.
(But yes, you absolutely want monitoring to pick up on that. In the specific example here, I'd say it's "just monitor pod failures", and then you get not only OOMs, but all the other ways a workload might crash too.)
I've actually had more problems when the control loops get stuck. (Which they can, almost always because you've asked for the impossible. But sometimes it is hard for the various pieces to say more than "no solution". Why no solution is usually a human-level deduction.)
Since Kubernetes 1.23, there's support for using "custom metrics" to control HPAs: https://kubernetes.io/blog/2026/07/14/custom-metrics-exporte...
Is it somewhat convoluted to setup? Absolutely! But it's possible to use relatively robust and well supported built in controllers with custom metrics.
Kubernetes™
... and while searching for where I found that quote, it seems someone vibecoded that metaphor literally: https://github.com/Yscale-sh/Kubagachi
I have come to precisely the opposite conclusion. I can count all of the helm charts that didn't make me immediately want to cry with one hand... and I'd have at least 2 fingers still free.
If you have "base, and then we change three things per environment", Kustomize is about as good as it gets using simple patches.
at $dayJob, we have templates wrapping templates and not a ton of tests on anything. Lots of implicit defaults that are not documented anywhere other than in the master/root chart defaults. As far as I can tell, this was how policy was meant to be enforced rather than something like OPA.
Real talk, I could not for the life of me figure out how to use Kustomize. I spent a day or so poring over the documentation, and while I could recreate their hello world type examples, I couldn't figure out why they worked or how to make it work for our needs. Meanwhile, Helm immediately made sense and I had a chart banged out for the software I was trying to set up in under an hour.
I'm sure someone will tell me it's a skill issue, but Kustomize was simply way harder to use than Helm (or the documentation was worse, not sure which). So we wound up using Helm.
Anyway, I love that you are/were skeptical of Kubernetes, but then looked into k8s more; we need more people like that in the world.
I think you did a great job explaining the internals of Kubernetes and then comparing it to "not" k8s. e.g. when you said the following:
> Compared to "classic" clusters, a Service provides a similar abstraction to the combination of:
** N nodes running Keepalived + NGINX (with a dynamic set of backends)
** CNAME records for the virtual (Keepalived) IP
It's a great point because a lot of anti-k8s people just say "Just put your service on a few VMs and put a LB in front of it". But if you don't just pay someone else to do it (ALB,NLB,etc) you gotta do something like Keepalived + NGINX like you mentioned, and then you are one step into creating your own k8s.I also love how you still brought up issues with k8s. For as much love and experience I have of k8s, I feel like there is some odd behavior it has as defaults. e.g. the default is no cpu or mem requests (don't get me started on storage). I've worked many places where an engineers app that "shouldn't" use a ton of resources used up enough to cause the k8s worker node to have issues, which then caused other pods to fail.
Kubernetes can be driven with tofu and hcl.. make it operator and even driven.. use any of many sdks, cdks to drive it, or add a completely different layer for faas or orchestration if that’s the relevant vocab for your problem space. It all runs on k8s.. that’s kinda the point.
you're supposed to write your own.
> Why are the storage and networking implementations "out of tree" (CNI / CSI)?
> Given the above question, why is there explicit support for Cloud providers?
the in-tree stuff came first, and they have been extracting it in pieces for years now.
At least some years ago, I could cope by viewing it as "resume fodder" but that doesn't even matter anymore.
But no, apparently '{' characters are scary or something.
I think you found the answer to that below - KEDA
I think any of the "JSON + functions" configuration languages mentioned in this thread (there are many) would be just as good.
Writing configurations in a modular way is really neat to be able to do. Rather than having to manage a single YAML file with the resource manifest, you can construct the resource by combining different parts of it.
e.g. rather than { image = "backend", tags = ["backend"] }, { image = "cache", tags = ["backend"] }, you could factor out the `tags`. -- Doing this from a "JSON + functions" language makes more sense than Helm's approach, from my experience.
The module system that NixOS uses builds an abstraction over this: each module declares like { options, config, output }. So the module author defines the option & output, and the module users declare the config, then the system knows how to render the configuration.