30.1 Flux Architecture: Source Controller, Kustomize Controller, Helm Controller
Alright, let’s pull back the curtain on Flux’s architecture. Forget the marketing fluff; we’re here to talk about the moving parts that actually do the work. At its core, Flux isn’t a single monolithic application. It’s a collection of specialized controllers, each with a single, well-defined job. This is a brilliant design choice because it means you can use just the parts you need and understand exactly what’s failing when (not if) something goes sideways.
The three you’ll become intimately familiar with are the Source, Kustomize, and Helm controllers. Think of them as an assembly line: Source fetches the raw materials, and then either Kustomize or Helm takes those materials and builds the final product into your cluster.
The Bouncer: Source Controller
First up, the Source Controller. Its entire raison d’être is to pull code from somewhere (Git, S3, a bucket) and make it available for the other controllers to use. It’s the club bouncer who checks the list and brings the guest (your manifest) to the door. It does not deploy anything itself. It just fetches and verifies.
You define a GitRepository resource, which is essentially a pointer to a repo, a branch, and any credentials needed. The controller will periodically (or via webhook) pull that branch, package it up as a .tar.gz file, and store it in-cluster as an artifact. The beauty here is that every artifact is immutable and is checksummed with a SHA256, so the next controller in line always knows exactly what it’s getting.
Here’s a basic example. Notice the interval: this is Flux’s heartbeat. Every minute, it’s going to check that repo for new commits.
apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: GitRepository
metadata:
name: my-app-source
namespace: flux-system
spec:
interval: 1m0s
url: https://github.com/my-org/my-app-repo
ref:
branch: main
secretRef:
name: github-credentials # A secret you've created elsewhere
Pitfall: The most common “oh crap” moment here is getting the authentication wrong. If your GitRepository is in a Error state, run kubectl describe gitrepository my-app-source. Nine times out of ten, the error message will tell you exactly why it can’t clone the repo (bad credentials, missing permissions, network issue).
The Native Tailor: Kustomize Controller
Once the Source Controller has the raw YAML, the Kustomize Controller takes over. If you’re a pure-Kubernetes-YAML purist, this is your jam. Its job is to take a path within your source artifact that contains either plain YAMLs or a kustomization.yaml file, apply any Kustomize magic (patches, generators, etc.), and then kubectl apply the result directly to the cluster.
You link the two controllers with a Kustomization resource (note the spelling—it’s “Kustomization”, not “Kustomization”. I don’t know why either, and yes, it’s annoying). This resource points to the GitRepository source and the path within it.
apiVersion: kustomize.toolkit.fluxcd.io/v1beta2
kind: Kustomization
metadata:
name: my-app-deployment
namespace: flux-system
spec:
interval: 5m0s # Check for new artifacts every 5 mins
path: "./kubernetes/overlays/production" # Path within the repo
sourceRef:
kind: GitRepository
name: my-app-source
prune: true # Holy grail feature: deletes resources removed from the repo
validation: client # Uses server-side validation if set to 'server'
healthChecks:
- apiVersion: apps/v1
kind: Deployment
name: my-app
namespace: production
Why this rules: The prune flag is GitOps incarnate. Your cluster state becomes your Git state. Delete a file from Git, and the controller will delete the corresponding resource from the cluster. It’s terrifying and beautiful. Always test your changes in a non-prod environment first.
The Chart Guru: Helm Controller
Not everyone wants to hand-craft 200 YAML files for a Helm chart. The designers knew this, so we have the Helm Controller. It’s specifically designed to work with Helm charts, whether they’re sourced from a Helm repository or from a Git repo.
Instead of a Kustomization, you create a HelmRelease resource. This brilliant piece of config does two things: 1) it tells the Source Controller where to find the chart (via a separate HelmRepository or GitRepository source), and 2) it contains the values you want to pass to that chart. It’s like delegating the installation to a expert who reads the manual every time.
apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: HelmRepository
metadata:
name: prometheus-community
namespace: flux-system
spec:
interval: 60m0s # Charts don't change *that* often
url: https://prometheus-community.github.io/helm-charts
---
apiVersion: helm.toolkit.fluxcd.io/v1beta2
kind: HelmRelease
metadata:
name: prometheus-stack
namespace: monitoring
spec:
interval: 5m0s
chart:
spec:
chart: kube-prometheus-stack
version: "48.1.1" # Pin your versions, people. Seriously.
sourceRef:
kind: HelmRepository
name: prometheus-community
values: # Your custom values file, right here in CRD-land
alertmanager:
enabled: false
grafana:
sidecar:
dashboards:
enabled: true
Best Practice: Always pin your chart versions. Using version: ">" is a fantastic way to have your production cluster unexpectedly explode on a Friday night. The interval on the HelmRepository can be longer because you’re just updating the index of what charts/versions are available. The HelmRelease’s interval is what checks for new artifacts (like if you updated the values in your Git repo).
The key insight is that these controllers work in concert. The Source Controller is the foundation. You then use either a Kustomize Controller or a Helm Controller to actually deploy things based on what the Source provides. Understanding this separation of concerns is what turns you from someone who runs Flux commands into someone who actually understands how the machinery works.