Skip to content

GitOps tu connais ?

Estimated time to read: 9 minutes

Ok, on a réussi notre petite recette. Tout est écrit mais ça serait encore mieux si on pouvait automatiser tout ça en utilisant Flux par exemple.

Pré-requis

Pour boostraper Flux (en gros l'installer dans votre cluster), il faut que la personne qui lance la commande ait les droits cluster admin sur le cluster Kubernetes cible. Il est aussi nécessaire que la personne qui lance la commande soit le propriétaire du projet GitLab, ou ait les droits admin d'un groupe GitLab.

N'oubliez pas de forker le projet pour pouvoir le modifier ET de basculer sur votre nouveau repo 😁

Info

Si vous avez fait la partie environnements gitlab avant celle-ci et que vous avez déjà forké le projet, pas besoin de faire un nouveau fork. Vous pouvez réutiliser le 1er.

Info

Si vous utilisez Gitpod n'oubliez pas de télécharger votre kubeconfig alias cluster-ovh-${TF_VAR_OVH_CLOUD_PROJECT_KUBE_NAME}.yml. Avec le fork vous allez démarrer avec un nouveau pod et par conséquence vos fichiers locaux ne seront plus accessibles.

Ensuite, il va nous falloir un token GitLab pour que Flux puisse se connecter à notre repo GitLab.

GitLab PAT (Personal Access Token)

On va récupérer le token et le stocker dans une variable d'environnement.

Comment récupérer un token GitLab

Pour créer un token GitLab, il faut aller dans votre profil GitLab, puis dans Preferences > Access Tokens Profile

Create PAT

Et créer un token avec les bons scopes: PAT Scopes

export GITLAB_TOKEN=<THE TOKEN>

Installation de Flux

La première étape est d'installer Flux CLI.

curl -s https://fluxcd.io/install.sh | sudo bash

Cette fois c'est la bonne : on configure Flux

On va demander à la CLI d'initialiser Flux sur notre cluster et de se connecter à notre repo GitLab.

Tout est décrit dans le tuto Flux.

Info

Si la variable d'environement GITLAB_TOKEN n'est pas renseignée, le boostrap va demander de saisir le token.

Il est possible de fournir le token avec une commande du type: echo "<gl-token>" | flux bootstrap gitlab.

On lance le bootstrap sur le projet avec notre compte personnel:

flux bootstrap gitlab \
  --deploy-token-auth \
  --owner=<NAMESPACE_NAME> \
  --repository=<PROJECT_STUG> \
  --branch=main \
  --path=flux/devoxx-cluster/ \
  --personal

Il y a trois paramètres à remplacer :

  • NAMESPACE_NAME : le groupe ou sous-groupe dans lequel vous voulez initialiser Flux
  • PROJECT_STUG : le project dans lequel Flux va stocker les informations dont il a besoin
  • path : le chemin où sont stocké les fichiers de configuration de Flux dans le repo

NAMESPACE_NAME + PROJECT_STUG doivent correspondre au chemin du repository dans lequel vous avez forké ce workshop.

Lorsque l'on utilise --deploy-token-auth, la CLI génère un token GL et le stock dans le cluster sous la forme d'une Secret qui s'appelle flux-system dans le Namespace flux-system.

Flux bootstrap output
► connecting to https://gitlab.com
► cloning branch "main" from Git repository "https://gitlab.com/yodamad-workshops/kub-workshop.git"
✔ cloned repository
► generating component manifests
✔ generated component manifests
✔ committed sync manifests to "main" ("4271d8d7adef5572f1031f0f21767d449d0ccbb4")
► pushing component manifests to "https://gitlab.com/yodamad-workshops/kub-workshop.git"
► installing components in "flux-system" namespace
✔ installed components
✔ reconciled components
► checking to reconcile deploy token for source secret
✔ configured deploy token "flux-system-main-flux-system-./flux/devoxx-cluster" for "https://gitlab.com/yodamad-workshops/kub-workshop"
► 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" ("05dfd597d5959fda3a783f2336b65d1f1d7b121d")
► pushing sync manifests to "https://gitlab.com/yodamad-workshops/kub-workshop.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

Bien joué !

L'agent Flux va maintenant surveiller notre repo GitLab et appliquer les changements automatiquement.

Petite visite de notre nouvelle cuisine

Le cellier

Si on pull le repo GitLab, on peut voir que Flux a crée un nouveau repertoire flux/devoxx-cluster/flux-system qui contient la configuration de Flux :

  • kustomization.yaml est un index, on va lister ici les manifests qui doivent être pris en compte dans ce répertoire.
  • gotk-components.yaml contient la définition des RBAC et des CRDs (Custom Resource Definition) utilisées par Flux.
  • gotk-sync.yaml définit la manières dont l'opérateur se connecte au repo au travers du Kind GitRepository et le type Kustomization permet de configurer quels sont les manifest / configuration à scruter. Pour nous, tout ce qui se trouve dans ./flux/devoxx-cluster sera utilisé comme configuration.

Ici le fichier ./flux/devoxx-cluster/flux-system/gotk-sync.yaml contient la configuration de Flux pour se connecter à notre repo GitLab.

./flux/devoxx-cluster/flux-system/gotk-sync.yaml
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
# ICI on trouve le nom de notre Objet GitRepository
  name: flux-system
  namespace: flux-system
spec:
  interval: 1m0s
  ref:
# ICI la branche à utiliser
    branch: main
  secretRef:
    name: flux-system
# ICI vous retrouverez l'adresse de votre repo GitLab    
  url: https://gitlab.com/jouve.thomas/kub-workshop-snowcamp-2024.git

Notre première recette

On va pouvoir lui dire d'appliquer la configuration grace aux objets Kustomization.

Si on regarde par example le fichier flux/repo.yaml

./flux/repo.yaml
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: repos
  namespace: flux-system
spec:
  interval: 1m0s
  path: ./flux/repository
  prune: true
  sourceRef:
    kind: GitRepository
    name: flux-system

On peut voir que l'on décrit en language Flux un nouveau répertoire à surveiller ./flux/repository dans notre GitRepository:flux-system.

Définition des HelmRepository

Dans ce répertoire ./flux/repository on retrouve par example le fichier nginx.yaml :

./flux/repository/nginx.yaml
1
2
3
4
5
6
7
8
9
---
apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: HelmRepository
metadata:
  name: nginx-ingress-controller
  namespace: flux-system
spec:
  interval: 1h0m0s
  url: https://kubernetes.github.io/ingress-nginx

Cette fois-ci on configure un HelmRepository qui va permettre à Flux de récupérer les informations sur les charts disponibles dans le repo Helm.

On pourra faire référence à ce chart sous le nom nginx-ingress-controller.

Nouveaux commis

On va maintenant deplacer ce fichier repo.yaml dans le répertoire flux/devoxx-cluster/ qui est le seul, pour le moment, que connait Flux.

Effectivement le fichier gotk-sync.yaml indique que seul le répertoire ./flux/devoxx-cluster est scrupté par Flux :

./flux/devoxx-cluster/flux-system/gotk-sync.yaml
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: flux-system
  namespace: flux-system
spec:
  interval: 10m0s
  path: ./flux/devoxx-cluster
  prune: true
  sourceRef:
    kind: GitRepository
    name: flux-system

On envoie les commandes en cuisine

Et on push commit, car maintenant c'est Flux qui se charge de faire la synchronisation sur le cluster depuis notre repo.

cp flux/repo.yaml flux/devoxx-cluster/repo.yaml
git add flux/devoxx-cluster/repo.yaml
git commit -am ":satellite_orbital: Setup Helm repos" && git push

On peut observer la réconciliation avec la commande suivante :

flux get kustomizations --watch

On a quelque chose comme ça :

  • il y a 2 répertoires à surveiller (2 Kustomization)
  • la synchronisation est active (SUSPENDED : False)
  • et à jour (READY : True)

Il indique aussi quelle est la révision utilisée pour la synchronisation (ici :main@sha1:c80d7d4c).

NAME            REVISION                SUSPENDED       READY   MESSAGE                              
flux-system     main@sha1:c80d7d4c      False           True    Applied revision: main@sha1:c80d7d4c
repos           main@sha1:c80d7d4c      False           True    Applied revision: main@sha1:c80d7d4c

On peux vérifier en regardant si il a bien créé nos resources HelmRepository :

kubectl get HelmRepository -A
NAMESPACE     NAME                       URL                                          AGE    READY   STATUS
flux-system   cert-manager               https://charts.jetstack.io                   112s   True    stored artifact: revision 'sha256:c930db5052b76d7be3026686612fa09f89a23f8547a8ecad7496d788e34964e5'
flux-system   external-secrets           https://charts.external-secrets.io           112s   True    stored artifact: revision 'sha256:35fa1d6332232e3c6d032627547ffc74c7e61c4729ed1daa680b2202c61a78da'
flux-system   nginx-ingress-controller   https://kubernetes.github.io/ingress-nginx   112s   True    stored artifact: revision 'sha256:e6a6c9e8f3682deea82b3bc22506d4fdabd667ce37cb1d0f7509459ca92c3426'

Ingress Controller

Tout est prêt dans le répertoire ./nginx-ingress-controller/flux pour déployer notre Ingress Controller.

Le descriptif de notre recette est une Kustomization

Ici ce n'est pas une Kustomization mais une Kustomization 🧌

Il faut lire :

  • Kustomization@kustomize.toolkit.fluxcd.io/v1 est la CRD de FLux pour les objects Flux (le liens vers un repo / repertoire / interval de scrapping)
  • Kustomization@kustomize.config.k8s.io/v1beta1 est l'objet Kustomize de Kubernetes

Ici on déclare une recette avec le nom flux-nginx-ingress-controller et qu'il est nécessaire d'utiliser les fichiers nginx-ingress-controller.yaml et ns.yaml pour déployer notre Ingress Controller.

./nginx-ingress-controller/flux/ks.yaml
1
2
3
4
5
6
7
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
metadata:
  name: flux-nginx-ingress-controller
resources:
- ns.yaml
- nginx-ingress-controller.yaml

La definition de notre Namespace

Avec un simple fichier manifest vanilla :

./nginx-ingress-controller/flux/ns.yaml
1
2
3
4
5
---
apiVersion: v1
kind: Namespace
metadata:
  name: nginx-ingress-controller

Et la definition de notre HelmRelease

./nginx-ingress-controller/flux/nginx-ingress-controller.yml
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
  name: nginx-ingress-controller
  namespace: nginx-ingress-controller
spec:
  chart:
    spec:
    #Le nom du chart que l'on souhaite installer
      chart: ingress-nginx
      #La référence vers le repository Helm (déclaré dans le fichier /flux/repository/helm/nginx.yaml)
      sourceRef:
        kind: HelmRepository
        name: nginx-ingress-controller
        namespace: flux-system
      #La version du chart que l'on souhaite installer
      version: 4.9.0
  interval: 1h0m0s
  #Les valeurs que l'on souhaite passer au chart
  values: 
    controller:
      publishService: 
        enabled: true

Chaud devant !

Avant de lancer la commande en cuisine, soyons fou et supprimons notre Ingress Controller précédemment installé pour laisser faire Flux.

Ce n'est pas obligatoire, mais c'est pour voir la magie de Flux.

En fait Flux va juste réappliquer la configuration, donc si vous ne supprimez pas l'Ingress Controller, il va juste le mettre à jour. Il utilisera install ou upgrade en fonction de l'état de l'objet HelmRelease.

 helm list -A
 helm uninstall ingress-nginx -n nginx-ingress-controller
 helm list -A
 kubectl get all -n nginx-ingress-controller
 kubectl delete ns nginx-ingress-controller
 kubectl get ns

On envoie la sauce

On va maintenant déplacer le fichier flux/nginx-ingress-controller.yaml dans le répertoire flux/devoxx-cluster/ comme pour les repos Helm.

./flux/nginx-ingress-controller.yaml
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: nginx-ingress-controller
  namespace: flux-system
spec:
  interval: 1m0s
  path: ./nginx-ingress-controller/flux
  prune: true
  sourceRef:
    kind: GitRepository
    name: flux-system

cp flux/nginx-ingress-controller.yaml flux/devoxx-cluster/nginx-ingress-controller.yaml
git add flux/devoxx-cluster/nginx-ingress-controller.yaml
git commit -am ":satellite_orbital: Setup Ingress Controller" && git push
On observe la synchro avec la commande suivante :

flux get kustomizations --watch
 helm list -A
 echo "Helm list ne nous retourne rien car c'est Flux qui gére maintenant"
 sleep 20
 echo "On utilise : "
 kubectl get HelmRepository -A
 kubectl get HelmChart -A
 kubectl get HelmRelease -A
 kubectl get ns
 kubectl get all -n nginx-ingress-controller

External Secrets

On va maintenant déployer notre External Secrets. Comme ça pas besoin de stocker les secrets à la main.

Setup des secrets

Commençons par créer les variables d'environnement dans notre repo GitLab. (Ils ne sont pas repris lors du fork et heureusement ...).

  • Soit on passe par l'UI de Gitlab

Variables CI CD GL

  • Soit on utilise l'API de Gitlab

Il vous faut l'ID du projet, vous pouvez le trouver ici :

Project ID

PROJECT_ID= <YOUR_PROJECT_ID>
curl --request POST --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
     "https://gitlab.com/api/v4/projects/${PROJECT_ID}/variables" \
     --form "key=API_MAIL" --form "value=${API_MAIL}"

curl --request POST --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
     "https://gitlab.com/api/v4/projects/${PROJECT_ID}/variables" \
     --form "key=API_KEY" --form "value=${API_KEY}"

Il ne nous reste plus qu'à :

  • créer notre Secret dans notre cluster Kubernetes comme vu précédement, mais cette fois avec votre compte.
  • modifier le fichier ./external-secrets/flux/external-secrets-secret-store.yml pour y mettre votre ID project.

./external-secrets/flux/external-secrets-secret-store.yml
apiVersion: external-secrets.io/v1beta1
kind: ClusterSecretStore
metadata:
  name: gitlab-cluster-secret-store
  namespace: external-secrets
spec:
  provider:
    # provider type: gitlab
    gitlab:
      url: https://gitlab.com/
      auth:
        SecretRef:
          accessToken:
            name: gitlab-token
            key: token
            namespace: external-secrets
      projectID: <PROJECT_ID>
Par exemple avec :
sed -i "s/<PROJECT_ID>/$PROJECT_ID/" ./external-secrets/flux/external-secrets-secret-store.yml

Présentation de notre kustomization

./external-secrets/flux/ks.yml
#https://external-secrets.io/latest/examples/gitops-using-fluxcd/
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
metadata:
  name: flux-external-secrets
resources:
- ns.yml
- deployment-cdrs.yml
- deployment.yml
- deployment-crs.yml
- external-secrets-secret-store.yml
  • Création du NS
  • Déployement des CRDS (Custom Resource Definition)
  • Déployement de l'opérateur
  • Déployement des CRS (Custom Resource) les différents providers (ici GitLab)
  • La définition de notre secret store

Chaud devant !

Fin prêt pour lancer la commande :

cp flux/external-secrets.yaml flux/devoxx-cluster/external-secrets.yaml
git add flux/devoxx-cluster/external-secrets.yaml
git commit -am ":satellite_orbital: Setup External Secret" && git push

External DNS && Cert-manager

Un dernier petit tips pour la route :

./flux/cert-manager.yaml
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: cert-manager
  namespace: flux-system
spec:
  interval: 1m0s
  path: ./cert-manager/flux
  dependsOn:
    - name: external-dns
    - name: nginx-ingress-controller
  prune: true
  sourceRef:
    kind: GitRepository
    name: flux-system
La commande dependsOn permet de définir une dépendance entre les différents objets Flux.
flowchart TD
  A[Cert Manager] -->|dependsOn| B[external-dns];
  A[Cert Manager] -->|dependsOn| C[nginx-ingress-controller];
  B -->|dependsOn| D[external-secrets];

On s'assure ainsi que les différentes recettes sont appliquées dans l'ordre.

Retournons à la recette pour encore plus de découvertes ➡️