Bootstrap and reconciliation
How Flux gets installed, what it takes over, and where the handoff from Ansible sits. Read this when a bootstrap fails part way, or when you need to know why something is applied rather than reconciled.
flux/ is the GitOps entrypoint. flux/cluster.yaml, the FluxInstance CR, is applied once by
ansible/roles/flux_bootstrap, and Flux does not reconcile the flux/ directory itself, to avoid
watching its own bootstrap.
Everything else under flux/ is reconciled. flux/infra/ks.yaml and flux/nodes/ks.yaml
are the two Flux Kustomization CRs that point Flux at the matching repo-root directories,
./infra and ./nodes. Those two keep their full spec rather than being patched like every
other ks.yaml, because flux/ has no kustomization.yaml of its own. Flux auto-generates one
from cluster.yaml’s sync.path: flux, and adding a real one would pull cluster.yaml itself
into reconciliation. The rest of the naming and layout rules are in
Layout and naming.
Bootstrap sequence
Run by just ans k8s (ansible/playbooks/k8s.yml):
k8s_cluster: render/etc/rancher/k3s/config.yamlfrom inventory, install k3s on the controller and then the workers, and fetch the kubeconfig intoansible/.generated/. Thelocal-pathStorageClass comes up with it, from k3s’s own bundled provisioner:monitoring,authandnodes/kenaz.k8s/actualall bind PVCs on their first reconcile, and nothing in the Flux-managed tree can provision a StorageClass for itself. See Startup ordering.flux_bootstrap:- Install the Flux Operator via Helm.
- Apply the
flux-system/git-deploy-keySecret, withknown_hostsbuilt from GitHub’s published host keys rather than a blindssh-keyscan. - Create the namespaces the next step writes into, since Flux does not exist yet to declare them. Flux takes ownership of all of them on its first reconcile.
- Apply the two Secrets that exist to seed what Flux resolves for itself, and so cannot
come from Flux:
flux-system/sops-ageandinfisical-universal-authin each tier namespace. See Secrets. - Wait for the Flux Operator to be ready.
- Apply
flux/cluster.yaml. Flux takes over from here.
There is no follow-up step. Everything past flux/cluster.yaml is Flux reconciling git.
Verify the handoff:
just fx sources # the GitRepository is Ready at the pushed revision
just fx failing # empty
The handoff, and the one line it never crosses back over. Green is the handoff point itself, the
moment Ansible stops and Flux owns the cluster. The two Kustomization labels are paths under
flux/.
Every Kustomization carries a decryption block naming flux-system/sops-age, patched in once
via infra/kustomization.yaml and nodes/kustomization.yaml. flux/infra/ks.yaml and
flux/nodes/ks.yaml state it in full for the same reason they state everything else in full.
Binary assets and .sourceignore
source-controller applies a built-in exclude list when it packs the artifact, and that list drops
*.png along with the other image and archive extensions. A PNG committed to the repository is
therefore absent from the tree kustomize-controller builds, even though kustomize build and the
pre-commit hooks pass locally. The failure surfaces as a build error naming a file that plainly
exists:
kustomize build failed: ... open .../web-app-manifest-192x192.png: no such file or directory
.sourceignore in the repository root is read after those defaults, so a ! line re-includes what
they excluded. It currently re-includes the Glance app icon under config/branding/. Add a line
there when a component starts mounting any other binary, and confirm with just fx get that the
consuming Kustomization reconciles.
Day-to-day
just fx get # every Kustomization and its sync status
just fx failing # only what isn't Ready
just fx reconcile # force-reconcile everything, or `<name>` for one
just fx logs # tail kustomize-controller
The full list is in Recipe reference.