| Filename | Latest commit message | Latest commit date |
|---|---|---|
| defaults | ||
| meta | ||
| tasks | ||
| .ansible-lint-ignore | ||
| .gitignore | ||
| README.md | ||
| requirements.yml | ||
fluxcd
Bootstraps Flux v2 into a Kubernetes cluster: installs its controllers
(fluxcd_components, defaulting to source-controller, kustomize-controller, helm-controller and
notification-controller - the same set a bare flux install gives you), and applies any GitRepository
(fluxcd_git_repositories, each with its own SSH deploy-key Secret and, optionally, its own SOPS decryption
Secret) and root Kustomization (fluxcd_kustomizations) objects configured - neither set by default. A
Kustomization sourcing from a repository with SOPS enabled gets spec.decryption wired to that repository's
Secret automatically.
The install manifest is not vendored in this role. Instead, the role ensures the pinned flux CLI binary
(fluxcd_version) is present on the target host and renders the manifest locally at execution time via
flux install --export --components={{ fluxcd_components | join(',') }} - a pure local render from data
embedded in the CLI binary, no network call needed once the binary is in place. The CLI binary itself is
downloaded once by the Ansible controller (delegate_to: localhost) and copied to the target host, so only
the controller needs outbound access to GitHub - the target host never has to reach it directly. Before
downloading, the role queries the GitHub Releases API for the archive's published checksum and passes it to
get_url's checksum:, so a corrupted or tampered download is caught rather than silently installed.
For a fluxcd_git_repositories entry with sops_enabled: true, SOPS decryption is configured from
sops_age_private_key if it's set - in which case that's the only place the key lives; the role never
writes it to fluxcd_host_secrets_dir/fluxcd_controller_secrets_dir. Otherwise, the role generates an
age keypair on the target host (installing the age package there first) and persists it to
fluxcd_host_secrets_dir and fluxcd_controller_secrets_dir ({{ inventory_dir }}/secrets by default,
one file per repository) so re-runs reuse the same key instead of generating a new one and rotating the
Secret's contents every time.
The controller manifest is re-rendered and re-applied (kubernetes.core.k8s: state: present) on every run,
not just when the CLI binary changes - so bumping fluxcd_version upgrades the running controllers on the
next run, and manual drift (e.g. someone deleting or hand-editing a controller Deployment) is healed too.
This role is idempotent: re-running it against an already-bootstrapped cluster with nothing to change makes
no changes, since kubernetes.core.k8s only patches resources whose live state differs from the rendered
manifest.
Requirements
- A working kubeconfig on the target host, with access to the cluster to bootstrap (this role does not
create one) - at
fluxcd_kubeconfig,~/.kube/configby default. kubernetes.core(applies/waits on the Flux manifest, Secrets,GitRepositoryandKustomizationobjects) andcommunity.general(extracts the flux CLI archive's published checksum from the GitHub Releases API response) collections - both pinned inrequirements.yml; install withansible-galaxy collection install -r requirements.yml.python3-kuberneteson the target host (Debian/Ubuntu-based, installed viaaptunder thefluxcd_install/fluxcd_configtags) - the Python clientkubernetes.core.k8s/k8s_infoneed to reach the cluster from there.- Outbound internet access from the Ansible controller (not the target host) to GitHub, to query the flux release metadata and fetch the CLI release archive.
- If any
fluxcd_git_repositoriesentry hassops_enabled: trueand nosops_age_private_keyset: the target host must be Debian/Ubuntu-based (the role installsageviaapt), since it generates that repository's SOPS age key there withage-keygen.
The role's identity - galaxy_info.role_name/namespace in meta/main.yml, and the prefix on every
variable and tag - is fluxcd, independent of whatever directory or repo name it's checked out under. If
installing it from a requirements.yml that clones it by repo URL, set name: fluxcd (or
isbg.fluxcd if installed as a namespaced Galaxy role) to consume it under its real name.
Role Variables
Generic, with sane defaults (see defaults/main.yml): fluxcd_namespace, fluxcd_kubeconfig,
fluxcd_version, fluxcd_components,
fluxcd_cli_arch_map/_cli_arch/_cli_archive_name/_cli_url/_cli_install_dir,
fluxcd_git_repositories, fluxcd_kustomizations, fluxcd_host_secrets_dir, fluxcd_controller_secrets_dir.
fluxcd_kubeconfig defaults to ~/.kube/config (resolved on the target host) and is passed to every
kubernetes.core.k8s/k8s_info task in this role - override it if the target host's kubeconfig lives
elsewhere.
fluxcd_components defaults to [source-controller, kustomize-controller, helm-controller, notification-controller] - the same set a bare flux install gives you. Add image-reflector-controller
and/or image-automation-controller to enable image-update automation; neither is installed by default.
fluxcd_git_repositories defaults to [] - the role applies no GitRepository objects (and no deploy-key
or SOPS Secrets) unless you set it. Each entry is a dict:
| Key | Required | Purpose |
|---|---|---|
name |
yes | Name of the GitRepository object; referenced by fluxcd_kustomizations[].source |
url |
yes | ssh:// URL of the git repository |
secret_ref |
yes | Name of the Secret this repository's deploy key is stored in and authenticates with |
deploy_key |
yes | SSH private key granting read access to this repository |
known_hosts |
yes | SSH host key(s) for this repository's git host |
branch |
no (main) |
Branch this GitRepository tracks |
interval |
no (5m0s) |
Reconciliation interval |
sops_enabled |
no (false) |
Whether Kustomizations sourcing from this repo get SOPS decryption configured |
sops_secret_ref |
only if sops_enabled |
Name of the Secret this repository's SOPS age key is stored in |
sops_age_private_key |
no | Pins the SOPS age key instead of letting the role generate one |
fluxcd_kustomizations defaults to [] - the role applies no root Kustomizations unless you set it. Each
entry is a dict: name, source (required - the name of a fluxcd_git_repositories entry) and path
(required), depends_on (list of other entries' names, default []), interval (default 10m0s),
prune (default true). A Kustomization's source repository having sops_enabled: true is enough to
wire up its decryption - no per-Kustomization SOPS field needed. Most consumers set both lists to the
single-repository, two-Kustomization shape (infra, apps, the latter depending on the former) shown in
the example below.
fluxcd_git_repositories' deploy_key/known_hosts/sops_age_private_key are per-repository, since each
repository may live on a different git host, need a different deploy key, or need a different SOPS key -
set the whole fluxcd_git_repositories list in an ansible-vault-encrypted file (it carries secrets end to
end), and the rest of the plain vars in a regular group_vars file, e.g. group_vars/<group>/fluxcd.yml
and group_vars/<group>/vault.yml respectively.
Tags
| Tag | Purpose |
|---|---|
fluxcd_cli |
Only ensure the pinned flux CLI binary is present on the target host. |
fluxcd_install |
Install/upgrade the Flux controllers (implies fluxcd_cli, since it needs the CLI to render the manifest). |
fluxcd_config |
Only (re)apply configured GitRepository/deploy-key/SOPS-secret/root Kustomization objects - skips CLI and controller installation. |
fluxcd_sops |
Only (re)apply SOPS secrets - a subset of fluxcd_config, no-op unless a fluxcd_git_repositories entry has sops_enabled: true. |
Each tag produces a meaningful result on its own; none are destructive.
Example Playbook
---
- name: "Bootstrap Flux onto the cluster hosts"
hosts: "controlplane"
roles:
- name: "isbg.fluxcd"
With group_vars/controlplane/fluxcd.yml:
---
fluxcd_kustomizations:
- name: "infra"
source: "infrastructure"
path: "./clusters/production/infra"
- name: "apps"
source: "infrastructure"
path: "./clusters/production/apps"
depends_on: ["infra"]
and group_vars/controlplane/vault.yml (vaulted):
---
fluxcd_git_repositories:
- name: "infrastructure"
url: "ssh://[email protected]/org/infra.git"
secret_ref: "infra-repo-deploy-key"
known_hosts: "git.example.com ssh-ed25519 AAAA..."
deploy_key: |
-----BEGIN OPENSSH PRIVATE KEY-----
...
-----END OPENSSH PRIVATE KEY-----
sops_enabled: true
sops_secret_ref: "infrastructure-sops-age"
sops_age_private_key: "AGE-SECRET-KEY-1..."
With vaulted credentials
---
fluxcd_git_repositories:
- name: "infrastructure"
url: "ssh://[email protected]/org/infra.git"
secret_ref: "infra-repo-deploy-key"
known_hosts: "{{ vault_fluxcd_infrastructure_known_hosts }}"
deploy_key: "{{ vault_fluxcd_infrastructure_deploy_key }}"
sops_enabled: true
sops_secret_ref: "infrastructure-sops-age"
sops_age_private_key: "{{ vault_fluxcd_infrastructure_sops_age_private_key }}"
and group_vars/controlplane/vault.yml (vaulted):
---
vault_fluxcd_infrastructure_known_hosts: "git.example.com ssh-ed25519 AAAA..."
vault_fluxcd_infrastructure_deploy_key: |
-----BEGIN OPENSSH PRIVATE KEY-----
...
-----END OPENSSH PRIVATE KEY-----
vault_fluxcd_infrastructure_sops_age_private_key: "AGE-SECRET-KEY-1..."