Discovering GitOps with FluxCD

In this article, I'll discover the GitOps approach and how to manage environment lifecycles in a Kubernetes context. To help with this, I'll try in the meantime FluxCD, a tool to easily handle "as-code" your environments' topology in a Kubernetes cluster. (It could have been ArgoCD also, but Flux is the tool picked by GitLab by default to manage GitOps from it. I'll write articles on both subjects, Argo & GitLab integration, soon 😉).

A quick definition of GitOps

As this article is not dedicated to GitOps, here is a simple definition of GitOps:

GitOps is an operational framework that takes DevOps best practices used for application development such as version control, collaboration, compliance, and CI/CD, and applies them to infrastructure automation.

The main objective of such an approach is to easily manage your environments with an "as-code" approach. Everything is versioned, reproducible. Git is the source of truth and gives access to a complete changelog of the infrastructure. It's some kind of evolution of Infrastructure-as-Code, aka IaC, where git becomes the controller of the changes lifecycle.

FluxCD

In the landscape of GitOps tooling, FluxCD is one of the most used ones. It has been picked by GitLab as the default tool for GitOps enablement into his platform.

Installation & Configuration

Let's start by initializing Flux into our cluster, to do so, we need to install the CLI on our laptop, as described here. If you don't want to install some stuff locally, you can also use the docker image available. I'm going to use the CLI in the article.

Flux respects GitOps approach, so to initialize it in a cluster, we need to boostrap it. Here I'll use GitLab to host the sources. To bootstrap flux installation, we need to provide at least the following elements:

  • owner: either your login or a group (with potential subgroups) path

  • repository: the repository in which flux will commit information regarding flux installation

  • branch: default branch to commit to

  • path: directory path where to commit flux information

Here I specified more option token-auth to specify an authentication with a personal access token and private to make the repository public, so that you can have access to the sources of this article (by default, everything is created in private)

$ flux bootstrap gitlab \
  --owner=fun_with/fun-with-k8s \
  --repository=fun-with-fluxcd \
  --branch=main \
  --path=clusters/ovh-fluxcd \
  --token-auth \
  --private=false
[...]
Please enter your GitLab personal access token (PAT): <GITLAB_PERSONAL_ACCESS_TOKEN>
[...] # If everything goes well, you should see this kind of log
► connecting to https://gitlab.com
► cloning branch "main" from Git repository "https://gitlab.com/fun_with/fun-with-k8s/fun-with-fluxcd.git"
✔ cloned repository
► generating component manifests
✔ generated component manifests
✔ component manifests are up to date
► installing components in "flux-system" namespace
✔ installed components
✔ reconciled components
► determining if source secret "flux-system/flux-system" exists
► generating source secret
► applying source secret "flux-system/flux-system"
✔ reconciled source secret
► generating sync manifests
✔ generated sync manifests
✔ committed sync manifests to "main" ("b34d6c8587efe288f541eb34eb046b9083cd743a")
► pushing sync manifests to "https://gitlab.com/fun_with/fun-with-k8s/fun-with-fluxcd.git"
► applying sync manifests
✔ reconciled sync configuration
◎ waiting for Kustomization "flux-system/flux-system" to be reconciled
✔ Kustomization reconciled successfully
► confirming components are healthy
✔ helm-controller: deployment ready
✔ kustomize-controller: deployment ready
✔ notification-controller: deployment ready
✔ source-controller: deployment ready
✔ all components are healthy # All good !

Now we can see that FluxCD has instantiated some elements in the cluster.

$ kubectl get ns
NAME              STATUS   AGE
default           Active   11m
flux-system       Active   2m35s # New namespace created
kube-node-lease   Active   11m
kube-public       Active   11m
kube-system       Active   11m

# Check resources in this namespace
$ kubectl get po -n flux-system
NAME                                       READY   STATUS    RESTARTS   AGE
helm-controller-7f8449fd58-skd26           1/1     Running   0          5m7s
kustomize-controller-6f666f899b-mp6cg      1/1     Running   0          5m7s
notification-controller-55bcdc9fcf-cv9xl   1/1     Running   0          5m7s
source-controller-b5f58d88d-h56wf          1/1     Running   0          5m7s

And also some files in the newly created repository

  • gotk-components contains all resources for FluxCD to work correctly (namespace, policies, custom resources, ...)

  • gotk-sync contains information to get Flux deployed resources synchronized with the definitions in the repository (GitOps approach)

  • kustomization lists the elements to apply

We are now ready to start managing some projects. Let's go!

First deployment based on git

In this first example, I'll configure FluxCD to synchronize a git repository.

Let's create a simple repository containing a single file deployment.yaml in deployment directory describing a simple sample app

apiVersion: v1
kind: Namespace
metadata:
  labels:
    app.kubernetes.io/instance: flux-demo
    app.kubernetes.io/name: flux-demo
  name: flux-demo

---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: flux-demo
  namespace: flux-demo
  annotations:
    kubernetes.io/ingress.class: nginx
    cert-manager.io/cluster-issuer: letsencrypt-production
spec:
  rules:
  - host: flux.ovh.yodamad.fr
    http:
      paths:
      - backend:
          service:
            name: flux-demo
            port:
              number: 80
        pathType: Prefix
        path: /
  tls:
    - hosts:
      - flux.ovh.yodamad.fr
      secretName: nginx-demo-tls
---
apiVersion: v1
kind: Service
metadata:
  name: flux-demo
  namespace: flux-demo  
spec:
  ports:
  - port: 80
    targetPort: 80
  selector:
    app: flux-demo
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: flux-demo
  namespace: flux-demo  
spec:
  replicas: 2
  selector:
    matchLabels:
      app: flux-demo
  template:
    metadata:
      labels:
        app: flux-demo
    spec:
      containers:
      - image: nginxdemos/hello
        name: flux-demo
        ports:
        - containerPort: 80

Now I'll configure Flux to monitor this repository and apply it to the cluster. Therefore I need to configure 2 components:

  • GitRepository which represents the repository to synchronize by defining

    • the url of the repository

    • the ref/branch to synchronize with cluster state

    • the interval to check if some new commits have been made

apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
  name: flux-simple-app
  namespace: flux-system
spec:
  interval: 1m0s
  ref:
    branch: main
  url: https://gitlab.com/fun_with/fun-with-k8s/fluxcd-samples/sample-git-repository.git
  • Kustomization which specifies in GitRepository, the path to synchronize and some options like prune. This one specifies to Flux to apply garbage collector on resources, which means that resources that are no longer present in the yaml descriptions files are removed. The interval defined in Kustomization, is the interval to reconcile resources state and resources definition.
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: flux-simple-app
  namespace: flux-system
spec:
  interval: 1m0s
  path: ./deployment
  prune: true
  sourceRef:
    kind: GitRepository
    name: flux-simple-app

We can commit these 2 elements in a single file under clusters/ovh-flucd/simple-app in the repository created from the bootstrap command.

Once committed, Flux will do the job. A few seconds later, I can see a newly created namespace flux-demo and pods created inside of it

$ kubectl get ns
NAME              STATUS   AGE
[...]
flux-demo         Active   4m38s # My namespace defined in my git repo
flux-system       Active   5m14s # FluxCD dedicated namespace
[...]
kube-node-lease   Active   22m
kube-public       Active   22m
kube-system       Active   22m

$ kubectl get po -n flux-demo 
NAME                         READY   STATUS    RESTARTS   AGE
flux-demo-688dbc9f65-kdkhl   1/1     Running   0          5m46s
flux-demo-688dbc9f65-qs7fh   1/1     Running   0          5m46s

We can see in the logs of the source-controller pod of Flux that the repository has been detected

$ kubectl logs source-controller-b5f58d88d-4hzz7 -n flux-system
[...]
{"level":"info","ts":"2023-08-17T13:16:50.324Z","msg":"stored artifact for commit '🥳 Init deployment'","controller":"gitrepository","controllerGroup":"source.toolkit.fluxcd.io","controllerKind":"GitRepository","GitRepository":{"name":"flux-simple-app","namespace":"flux-system"},"namespace":"flux-system","name":"flux-simple-app","reconcileID":"6dd85fb4-b6d5-43b0-ae9b-1ed67aaf50c4"}
[...]

My repository is now synchronized with Flux, if I make any change in my deployment configuration, it will be automatically applied. Let's say I want to increase the number of nginx replicas to 3. I commit in the sample-git-repository : replicas: 3, and a few seconds after, I can see that it has been applied to the cluster

$ kubectl get po -n flux-demo
NAME                         READY   STATUS    RESTARTS   AGE
flux-demo-688dbc9f65-kdkhl   1/1     Running   0          13m
flux-demo-688dbc9f65-kpskm   1/1     Running   0          78s
flux-demo-688dbc9f65-qs7fh   1/1     Running   0          13m

Again, in source-controller pod, I can see that the changes had been detected & applied

$ kubectl logs source-controller-b5f58d88d-4hzz7 -n flux-system
[...]
{"level":"info","ts":"2023-08-17T13:28:53.889Z","msg":"stored artifact for commit '💪 Increase replicas'","controller":"gitrepository","controllerGroup":"source.toolkit.fluxcd.io","controllerKind":"GitRepository","GitRepository":{"name":"flux-simple-app","namespace":"flux-system"},"namespace":"flux-system","name":"flux-simple-app","reconcileID":"e54ef49e-30d8-4302-9d37-a8ff53cfd4b7"}

Other possible inputs

As it would be too long in this article to cover them all, FluxCD does support another kind of sources:

  • Helm repository

  • Kustomization repository

  • Cloud providers vendor locking ones

For the 2 first ones, I'd probably write some dedicated articles on them to introduce Helm and Kustomize in the meantime.

Conclusion

In this article, I introduce GitOps approach based on a git repository and managed with FluxCD. At the end of your reading, you should be able to set it up and start your journey to GitOps. This is the first basic approach and I will complete this with further articles to bring more real-life use cases and integration with a classic DevOps platform such as #gitlab

At the time of this article, flux was in version 2.0.1. Sources are available in my GitLab space :

Thanks again to OVHcloud for their support in providing me with resources to try all these nice tools

2
Subscribe to my newsletter

Read articles from Matthieu Vincent directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Matthieu Vincent
Matthieu Vincent

TechAdvocate DevSecOps / Cloud platform Lead Architect @ Sopra Steria Speaker @ Devoxx, Snowcamp, Breizhcamp, GitLab Connect and internally Co-Founder of Volcamp IT Conference @ Clermont-Fd (https://volcamp.io) GitLab Hero