I use Terraform with the Kubernetes Provider, which is also actively developed by HashiCorp itself.
Templating / injection of values has been much better, skipping the Helm Templating madness and relying on a set of tools that allow perform minting, security scans, generation of docs, unit tests and establish clear dependencies within Terraform, thanks to the graph model.
Helm Charts are a nice idea, but mistakes can happen really easy
This is the way. Remove Helm and Argo from your IaC entirely and manage as much as possible via Terraform with the hashicorp/kubernetes provider. It's simpler (fewer tools), and you also get:
- Clarity re: destruction of obsoleted/destroyed resources (rather than kubectl's "won't do it", Helm's "it depends on ten settings", and Argo's "I'll try my best but YMMV").
- Control over apply ordering if the k8s/tf default doesn't do it for you.
- Resource control as granular (or not, if you just want to write big multi-resource "kubernetes_manifest" blocks) as you want. You can move around, case-by-case, on the spectrum between "templated raw YAML copied from somewhere else" and "individual resources with (somewhat) strong typing/schema-awareness in code". As a bonus, if you do it fully granularly, there's no indirection via YAML happening at all, just per-resource Kubernetes API calls.
- A coherent story for moving ownership/grouping of k8s resources between different logical groups of stuff via terraform import/moved blocks.
- Vastly more accurate proposed-changes diff than Argo, Helm, or even Kubernetes itself can provide: tTerraform's core execution model is plan-as-canonical-changelist, while k8s/helm/argo added noop/proposed diffs as ancillary features of variable quality.
- The ability to mix in management of non-k8s resources (AWS/GCP/Azure/etc. stuff that k8s resources talk to), which is often simpler than deploying complex Kubernetes controllers that manage those same external resources. Controllers are great if you need lots of complex or self-serve management of external resources, but if you are only ever managing e.g. load balancers in one way in a few places, a big controller might be overkill versus doing it by hand.
The only big drawback of this approach is with CRDs. There's no way to have Terraform that deploys CRDs in the same plan as Terraform that refers to resources of those CRDs' types--not even if you conditionally "count = 0" deactivate management of the CRD resources based on variables or whatnot. To cope with this, you either have to get very good at targeted plan/applies (yuck), or plan/apply multiple Terraform modules in order (which is simple and a good practice, but results in more code and can be unwieldy at first).
All the other drawbacks I've heard to doing it this way are pretty silly, and boil down to:
1. "but everyone uses Argo/Helm!" Okay, lots of people smoke cigarettes too--and if you're deploying charts complex enough that you're having to get into the weeds with 'em, you've already gotten enough familiarity to easily port them into kubernetes-provider HCL anyway.
2. "I don't like Terraform/HCL". You do you, I guess, but 90% of the reasons people hate it boil down to either "you're using Terraform like it's 2016 and a lot of massive improvements were released circa 2018-2020", or "the Terraform model forces you to be rigorous and explicit rather than approximate and terse you're mad about it".
Relatedly, I was not impressed with the hashicorp/helm provider and routinely push for folks to go back to the regular Kubernetes provider instead. Architecturally the Helm provider is bad (let's indirect the already-too-complex templating constructs through another templating language! What could go wrong?), and its implementation is also not great--getting diagnostics/log output is harder than it should be, whether old resources are destroyed/replaced/updated-in-place is left left up to Helm itself in complex ways that break with the usual Terraform assumptions, and getting meaningful diffs is tricky (the "manifest" provider experiment exists but is experimental for a reason and causes terraform crashes--not just erroneous diff output--often).
Templating / injection of values has been much better, skipping the Helm Templating madness and relying on a set of tools that allow perform minting, security scans, generation of docs, unit tests and establish clear dependencies within Terraform, thanks to the graph model.
Helm Charts are a nice idea, but mistakes can happen really easy
reply