FluxCD
FluxCD is a Continuous Deployment solution that keeps your applications in Rahti in sync with a source of truth that you maintain outside the cluster, typically a Git repository. In Rahti, FluxCD is provided as a managed component, so you do not need to install or maintain the controllers yourself: you describe what should be running in your Rahti project, and Flux applies it for you.
What is GitOps
GitOps is a way of operating applications where the desired state of the system is stored in version control, and a controller running in the cluster continuously makes the cluster match it. Instead of running oc apply from your own machine, you commit a change and the cluster picks it up.
This has a few practical consequences:
- The repository is the source of truth. What is committed is what runs. If you want to know what is deployed, you read the repository rather than querying the cluster.
- Every change has a history. Deployments are commits, so they can be reviewed, approved, and reverted with the same tools you already use for code.
- Nobody needs to deploy by hand. No credentials for the cluster are needed in your continuous integration system, because nothing is pushed to Rahti from outside. Flux pulls from the repository instead.
- Manual changes do not stick. If someone edits a Deployment directly in the cluster, Flux changes it back on the next reconciliation. This is a feature: the cluster cannot drift away from what is written down.
How automatic deployment works
Flux splits the work into two kinds of objects, and you normally create one of each:
- A source object describes where your manifests come from and how often to look for changes: a
GitRepository, anOCIRepository, or aHelmRepository. The source controller fetches the content and stores it inside the cluster as an artifact. - An applier object describes what to do with that content: a
Kustomizationapplies plain YAML or Kustomize overlays, and aHelmReleaseinstalls or upgrades a Helm chart.
Both kinds of object have an interval field, which is how often Flux checks and re-applies. This is the reconciliation loop: on every interval, Flux compares the desired state with what is in your project and corrects any difference. You can also trigger a reconciliation immediately, without waiting for the next interval:
The examples on this page use an interval of one or two hours, which is a sensible default for a production application: changes are picked up automatically without polling the source every minute.
Rahti runs Flux with multi-tenancy enabled. In practice this means two things for you:
- Flux does not apply anything with its own cluster-wide permissions. Every
KustomizationandHelmReleasemust name a service account in your project, and Flux acts as that service account. This is what keeps one project from deploying into another. - Sources and appliers must live in the same Rahti project. Cross-namespace references are disabled, for example, a
Kustomizationcannot use aGitRepositoryfrom another project.
Prerequisites
The oc command line tool needs to be installed, and you need to be logged in to the right Rahti project (oc project <project_name>).
Before creating any Flux object, create the service account that Flux will use and grant it the admin role inside your project:
The same can be done declaratively, which is useful if you want the binding itself under version control. A RoleBinding that references the admin ClusterRole grants those rights only inside the project it is created in:
apiVersion: v1
kind: ServiceAccount
metadata:
name: flux
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: flux-admin
subjects:
- kind: ServiceAccount
name: flux
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: admin
Every applier object in the examples below refers to this service account with spec.serviceAccountName. If you leave it out, or if the service account lacks the rights for something in your manifests, the object stays not ready and reports the permission error in its conditions.
Deploying from a Git repository
This is the most common setup. The GitRepository object below tracks the master branch of the public podinfo repository, which Flux uses in its own documentation and which is convenient for testing, and the Kustomization applies the manifests found under ./kustomize:
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
name: podinfo
spec:
interval: 1h
url: https://github.com/stefanprodan/podinfo
ref:
branch: master
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: podinfo
spec:
interval: 1h
targetNamespace: <NAMESPACE>
serviceAccountName: flux
sourceRef:
kind: GitRepository
name: podinfo
path: ./kustomize
prune: true
timeout: 3m
patches:
- patch: |
- path: /spec/template/spec/containers/0/resources
op: remove
target:
kind: Deployment
prune: true means that when you delete a manifest from the repository, Flux deletes the corresponding object from your project as well. Without it, removed manifests would be left running.
For a private repository, create a Secret with your credentials and reference it from the source:
oc create secret generic my-repo-auth \
--from-literal=username=<GIT_USERNAME> \
--from-literal=password=<GIT_TOKEN_OR_APP_PASSWORD>
Use a token or deploy key with read-only access. An SSH key works as well, in which case the Secret holds identity, identity.pub, and known_hosts entries.
Deploying a Helm chart
To install a chart, combine a HelmRepository source with a HelmRelease. The example uses the public podinfo chart repository:
apiVersion: source.toolkit.fluxcd.io/v1
kind: HelmRepository
metadata:
name: podinfo
spec:
interval: 2h
url: https://stefanprodan.github.io/podinfo
---
apiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
name: podinfo
spec:
interval: 1h
serviceAccountName: flux
chart:
spec:
chart: podinfo
version: '6.*'
sourceRef:
kind: HelmRepository
name: podinfo
interval: 2h
values:
replicaCount: 1
ingress:
enabled: false
resources:
requests:
cpu: 400m
memory: 128Mi
limits:
cpu: "1"
memory: 512Mi
The version field takes a semantic version range, so 6.* picks up patch and minor releases of the 6 series automatically while never jumping to 7. The interval inside chart.spec is how often Flux checks the chart repository for a new version matching that range, and the interval of the HelmRelease is how often the release itself is reconciled. Anything you would normally put in a values.yaml file goes under values.
Charts in an OCI registry
Many projects now publish charts to an OCI registry instead of a chart repository. For those, set type: oci in the HelmRepository and use an oci:// URL, for example oci://ghcr.io/stefanprodan/charts. The HelmRelease stays the same.
Bear in mind the support levels described on the Rahti catalog page when choosing a chart, and prefer charts whose images you can rely on being updated.
Deploying from an OCI registry
Manifests can also be packaged as an OCI artifact and stored in a container registry, next to your images. This removes the dependency on Git being reachable at deployment time and gives you immutable, digest-addressable releases. An OCIRepository replaces the GitRepository, and the Kustomization is unchanged apart from the source reference:
apiVersion: source.toolkit.fluxcd.io/v1
kind: OCIRepository
metadata:
name: podinfo
spec:
interval: 2h
url: oci://ghcr.io/stefanprodan/manifests/podinfo
ref:
semver: '6.*'
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: podinfo
spec:
interval: 1h
targetNamespace: <NAMESPACE>
serviceAccountName: flux
sourceRef:
kind: OCIRepository
name: podinfo
path: ./
prune: true
timeout: 3m
patches:
- patch: |
- path: /spec/template/spec/containers/0/resources
op: remove
target:
kind: Deployment
Instead of semver, the ref can name a tag or pin a digest for a fully immutable deployment. For a private registry, add a secretRef pointing to a pull secret, which can be the same one you use for images in your project.
Encrypting secrets with SOPS
A GitOps repository holds your whole deployment, which means it would also hold your Secret objects. Committing them in plain text is not acceptable, even in a private repository. Flux solves this with SOPS, which encrypts only the values inside a YAML file and leaves the structure readable. The encrypted file is safe to commit, and the kustomize-controller decrypts it inside the cluster when it applies the manifest, so the plaintext exists only in your project.
The default way of doing this is with a GPG key pair. Generate one without a passphrase, since Flux has to use it unattended.
-
Generate the GPG key pair
-
Take note of the key fingerprint, which identifies the key in the commands that follow:
-
Store the private key in your Rahti project so that Flux can decrypt with it. The entry in the
Secrethas to end in.asc: -
Back up the private key somewhere safe, and then remove it from your own machine, so that the only copy that can decrypt is the one in the cluster:
-
The public key is what you and your colleagues use to encrypt, so it can be committed to the repository. The following command will generate the public key file named
.sops.pub.asc.Anyone who needs to add an encrypted file imports it once with
gpg --import .sops.pub.asc. -
Next, tell SOPS which key to use and which fields to encrypt by adding a
.sops.yamlfile to the root of the repository. Encrypting onlydataandstringDatakeeps the rest of the manifest readable, so changes to it can still be reviewed in a diff: -
With that file in place, encrypting a manifest needs no further arguments. You can encrypt the files using the following command. Note that it will encrypt the
dataorstringDatafield of the file. -
Commit the encrypted file, and tell the
Kustomizationto decrypt:
Warning
The private key in the sops-gpg Secret can decrypt every secret in your repository. Keep it out of the repository itself, restrict who has access to the Rahti project that holds it, and rotate it if it is ever exposed. Anyone who can read secrets in your project can also read the decrypted values, so treat project membership as equivalent to access to those credentials.
You can add GPG public key to your repository but never commit the private key.
SOPS supports other kinds of keys as well. age is a simpler alternative to GPG, and a key management service avoids storing a private key in the cluster at all, at the cost of needing credentials for that external service. Both are documented in the upstream Flux guide on Mozilla SOPS.
Checking what Flux is doing
The status of every Flux object is visible with oc:
NAME URL READY STATUS
gitrepository/podinfo https://github.com/stefanprodan/podinfo True stored artifact for revision 'master@sha1:...'
NAME READY STATUS
kustomization/podinfo True Applied revision: master@sha1:...
If READY is False, the reason is in the object's conditions:
To stop Flux from reconciling temporarily, for example while debugging something by hand, suspend the object and resume it afterwards:
oc patch kustomization podinfo --type=merge -p '{"spec":{"suspend":true}}'
oc patch kustomization podinfo --type=merge -p '{"spec":{"suspend":false}}'
The flux command line tool
Flux has its own CLI, which offers shortcuts such as flux get all and flux reconcile. It is convenient, but its flux bootstrap and flux install commands need cluster-admin rights and are not usable in Rahti, since the controllers are already installed and managed for you. The oc commands above work with ordinary project permissions.
More information
- Flux documentation: the full reference for all source and applier kinds.
- Flux Kustomization reference: every field of the applier used above, including SOPS decryption.
- Flux guide on SOPS: encrypting secrets with GPG, age, or a key management service.
- Kustomize: how to structure the manifests that a
Kustomizationapplies. - CI/CD on Rahti: how automatic deployment fits into a wider pipeline.