How Flux works: GitRepository, Kustomization and Receiver
Flux is a set of Kubernetes controllers that keeps your cluster in sync, based on configuration changes you do in a Git repository instead of running kubectl by hand.
A simple Flux setup can be explained with three resources, each one answering a different question:
- where the configuration comes from,
- what gets applied,
- and when to look again.
Let us start with the where.
GitRepository: where
The GitRepository resource tells Flux which repository to watch and how often to check it. It does not apply anything. It only fetches and makes the content available to other resources as a source.
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
name: my-config
namespace: flux-system # A K8s namespace
spec:
interval: 1m
url: https://github.com/my-org/my-config
ref:
branch: main
spec.intervalis the polling period, with one minute being a reasonable default.spec.ref, if omitted entirely, it defaults to amasterbranch reference.
Kustomization: what
The Kustomization resource points at a source and says which part of it to apply. Plain manifests in a folder work fine.
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: my-app
namespace: flux-system
spec:
interval: 10m
sourceRef:
kind: GitRepository
name: my-config
path: "./clusters/production"
prune: true
spec.pathis relative to the repository root. Point it at a folder per cluster or per environment rather than at the root. If you mix that up one cluster can end up applying manifests meant for another.spec.pruneis required and has no default. Withprune: trueFlux garbage-collects the objects it previously applied that are no longer in the source. WithfalseFlux leaves them on the cluster when you remove them from Git, and you have to clean them up yourself.intervalis a different thing from the one onGitRepository. That one controls how often Flux checks Git for changes; this one controls how often it reconciles the manifests in the cluster, even when Git has not changed. The second is your drift correction, which means that if somebody edits a live object withkubectl edit, this is the timer that puts it back. A new commit does not wait for this timer: as soon as theGitRepositoryfetches a new revision, theKustomizationapplies it.
Receiver: when
The two resources above are enough for a working GitOps setup. Without a webhook, Flux only notices a new commit at the next GitRepository poll. A Receiver exposes a webhook, so GitHub or GitLab can tell Flux about a push the moment it happens, instead of waiting for that poll.
The Receiver needs a Secret holding a random token, which you also give to the webhook on the provider side:
TOKEN="$(openssl rand -hex 32)"
kubectl -n flux-system create secret generic receiver-token \
--from-literal=token="$TOKEN"
echo "$TOKEN"
apiVersion: notification.toolkit.fluxcd.io/v1
kind: Receiver
metadata:
name: my-config-receiver
namespace: flux-system
spec:
type: github
events:
- push
secretRef:
name: receiver-token
resources:
- kind: GitRepository
name: my-config
spec.typematches your provider (github,gitlab, etc.).spec.eventslimits which webhook events Flux acts on. Without it, any valid event triggers a reconciliation.spec.secretRefpoints at the Secret created above. It must contain atokenkey.spec.resourcesis a list, so one Receiver can trigger several resources from a single push.
To get the webhook path run the following, when the Receiver is ready:
kubectl -n flux-system get receiver my-config-receiver -o jsonpath='{.status.webhookPath}'
Combine that path with the hostname of an ingress that exposes the notification controller’s webhook-receiver Service, and that full URL is what goes into the webhook settings in GitHub or GitLab.
Putting it together
Three resources, three questions:
| Resource | Question | Key field |
|---|---|---|
GitRepository |
Where does configuration come from? | spec.url |
Kustomization |
What gets applied? | spec.path |
Receiver |
When should we check immediately? | spec.resources |
Start with the first two and let it poll. Add the Receiver as an optimisation, not a requirement. A cluster that only polls is still a correct GitOps setup.
GitRepository is one kind of source, and HelmRepository and OCIRepository are others.
Kustomization is one kind of applier, and HelmRelease is another.
Not every source fits every applier, though: a Kustomization cannot read from a HelmRepository, because Helm charts are handled by HelmRelease.
Feel free to drop me a comment, if you found this article helpful.