Gérer son cluster Kubernetes avec la méthode GitOps grâce à Flux2 et GitLab

Rédigé par Katia HIMEUR le 22/11/2021
Temps de lecture: 8 minutes

dd
Lors de l’édition 2021 de l’open source experience, j’ai eu l’occasion, dans le cadre d’un workshop, de faire une démonstration pour montrer comment nous pouvons gérer un cluster Kubernetes avec la méthode GitOps, notamment grâce à Flux 2 et GitLab. L’objectif de cet article est de vous permettre de reproduire, étape par étape, les étapes de cette démonstration. Dans cet article, nous allons voir comment, grâce à Flux :

  • Déployer des ressources Kubernetes dans un cluster inaccessible depuis Internet (un cluster local pour cet article)
  • Mettre à jour automatiquement le nombre de réplicas déployés
  • Mettre à jour, automatiquement, le tag de l’image Docker déployée

Mais avant de commencer, je vais rappeler la définition du GitOps.

1. Qu’est-ce que le GitOps ?

Le GitOps est une manière d’implémenter le déploiement continu des applications dites cloud natives. L’état désiré des infrastructures est décrit de manière déclarative dans un répertoire Git. Et un processus automatisé vient vérifier que l’état décrit désiré correspond à l’état actuel. Si cela n’est pas le cas, il fait en sorte d’appliquer les modifications nécessaires. Comme son nom l’indique, dans la méthode GitOps, le répertoire Git est l’élément central.

Parmi les avantages du GitOps, nous pouvons citer :

  • Réduction du nombre d’outils utilisés : Git devient l’élément central
  • Disposer d’un historique des changements
  • Facilité des retours arrières
  • Gérer ses déploiements depuis l’environnement et non plus depuis l’extérieur.
  • Possibilité d’avoir le descriptif complet des éléments déployés en consultant les manifests.

2. Avant de commencer

Avant de commencer, il nous faut répondre à plusieurs pré-requis :

  • Un cluster Kubernetes fonctionnel : pour la suite de cet article, je vais travailler avec un cluster Kubernetes local.
  • La CLI Flux installée
  • Un token GitLab : GitLab Personal access tokens

Je vais également utiliser Kustomize pour écrire et organiser mes ressources Kubernetes. Outil dont j’ai déjà parlé ici : Introduction to Kustomize: how to customize Kubernetes objects

3. Organisation des répertoires Git sur GitLab

J’ai créé deux projets GitLab pour ma démonstration :

  • my-infra : répertoire Git que je laisse volontairement vide au début et où mon code d’infrastructure sera versionné
  • my-app : contient les sources de mon application, les manifestes Kubernetes qui permettent de la déployer et le pipeline de CI/CD qui permet de créer une image Docker à chaque création de tag Git. Cette image est ensuite poussée dans le registre d’images de conteneurs GitLab. Les sources complètes sont ici : Sources

4. Vérifier que le cluster Kubernetes répond aux pré-requis de Flux

Avant d’initialiser Flux dans mon cluster, je vais vérifier que ce dernier répond à la liste des pré-requis attendus par Flux. Pour ce faire, je vais exécuter la commande suivante :

1flux check --pre

Résultats de la vérification des pré-requis Flux

5. Initialiser Flux (Bootstrap)

1flux bootstrap gitlab \
2  --owner=demo-flux \
3  --repository="my-infra" \
4  --branch=main \
5  --path=clusters/local \
6  --token-auth \
7  --personal

Cette commande permet d’installer les composants nécessaires au bon fonctionnement de Flux et de mettre à jour le répertoire GitLab my-infra avec la configuration générée automatiquement. Comme je suis dans un environnement local, je demande à Flux, grâce au paramètre —path de mettre la configuration de mon environnement dans clusters/local.

6. Créer un namespace Kubernetes grâce à Flux

Avant de déployer mon application, je veux que Flux crée le namespace my-app. Je mets les manifestes dans le dossier infrastructure à la racine du répertoire my-infra. Maintenant, il faut que je prévienne Flux de prendre en compte ces manifestes. Pour cela je crée le fichier infrastructure.yaml dans clusters/local.

 1---
 2apiVersion: kustomize.toolkit.fluxcd.io/v1beta1
 3kind: Kustomization
 4metadata:
 5  name: infrastructure
 6  namespace: flux-system
 7spec:
 8  interval: 30s
 9  sourceRef:
10    kind: GitRepository
11    name: flux-system
12  path: ./infrastructure/local
13  prune: true
14  validation: client

Je commit et je push ce fichier. J’attends quelques secondes pour que Flux voit mes modifications et hop, le namespace est créé.

7. Déployer mon application

Mon application est une simple page Index.html que j’utilise pour remplacer l’image par défaut de Nginx. Je versionne dans un autre répertoire Git mes sources. Pour déployer dans mon cluster, je crée deux ressources Kubernetes : un déploiement et un service. Mes manifestes sont dans le dossier manifests/.

La première étape pour déployer est de dire à Flux quelle source Git utiliser (Ressource GitRepository). Mais avant, pour générer le bon manifeste, j’exécute la commande suivante :

1flux create source git my-app \
2  --url=https://gitlab.com/demo-flux/my-app.git \
3  --branch=main \
4  --interval=30s \
5  --export > ./clusters/local/my-app-source.yaml

Cette commande va générer le fichier my-app-source.yaml

 1---
 2apiVersion: source.toolkit.fluxcd.io/v1beta1
 3kind: GitRepository
 4metadata:
 5  name: my-app
 6  namespace: flux-system
 7spec:
 8  interval: 30s
 9  ref:
10    branch: main
11  url: https://gitlab.com/demo-flux/my-app.git

Pour que Flux puisse voir cette ressource, je pousse ma modification sur le répertoire distant. Pour vérifier la prise en compte de ma modification, j’exécute la commande suivante :

1flux get sources git --watch

La seconde étape est d’indiquer à Flux où se situent les manifestes qui concernent mon environnement. Pour cela, je génère la ressource Kustomization grâce à la commande suivante :

1flux create kustomization my-app \
2  --target-namespace=my-app \
3  --source=my-app \
4  --path="./manifests/local" \
5  --prune=true \
6  --interval=30s \
7  --export > ./clusters/local/my-app-kustomization.yaml

Le fichier my-app-kustomization.yaml est le suivant :

 1---
 2apiVersion: kustomize.toolkit.fluxcd.io/v1beta2
 3kind: Kustomization
 4metadata:
 5  name: my-app
 6  namespace: flux-system
 7spec:
 8  interval: 30s
 9  path: ./manifests/local
10  prune: true
11  sourceRef:
12    kind: GitRepository
13    name: my-app
14  targetNamespace: my-app

Une fois la modification poussée sur le répertoire Git distant, je vérifie la prise en compte de ma modification grâce à la commande

1flux get kustomizations --watch

J’attends quelques secondes et Flux va déployer mon application.

Maintenant, je souhaite modifier le nombre de réplicas de mon déploiement. Je crée une branche où j’apporte les modifications nécessaires aux manifestes

1# manifests/local/deployment.yml
2aapiVersion: apps/v1
3kind: Deployment
4metadata:
5  name: my-app
6  namespace: my-app
7spec:
8  replicas: 2
1# manifests/local/kustomization.yml
2apiVersion: kustomize.config.k8s.io/v1beta1
3kind: Kustomization
4
5resources:
6  - ../base
7
8patchesStrategicMerge:
9  - deployment.yml

Je merge et j’attends quelques secondes, le temps que Flux voit la modification. J’ai maintenant deux pods au lieu d’un seul.

8. Mettre à jour l’image Docker déployée automatiquement grâce à Flux

A ce stade de l’article, le tag de l’image Docker déployée est écrit en dur dans les manifestes. Si je souhaite modifier le tag déployé, je dois modifier le manifeste. Je souhaite déléguer cette mission à Flux pour ne pas avoir à le faire manuellement.

Premièrement, je réxécute la commande bootstrap pour installer les composants nécessaires :

1flux bootstrap gitlab \
2  --owner=demo-flux \
3  --components-extra=image-reflector-controller,image-automation-controller \
4  --repository="my-infra" \
5  --branch=main \
6  --path=clusters/local \
7  --token-auth \
8  --personal

J’ai ajouté le paramètre —composants-extra qui va me déployer deux nouveaux pods. À chaque fois que je crée ou modifie un manifeste existant, je commit et pousse mes modifications systématiquement sur le répertoire Git distant pour que Flux prenne en compte ces changements.

Ensuite, je crée un token GitLab qui doit avoir les droits de lecture et d’écriture sur le répertoire Git de mon application. Je stocke ce token et l’identifiant dans un secret Kubernetes

1kubectl create secret generic -n flux-system https-credentials \
2  --from-literal=username="${GITLAB_USERNAME}" --from-literal=password="${GITLAB_TOKEN}"

Je mets à jour la ressource GitRepository créée au début, afin de lui spécifier ce secret à utiliser

1flux create source git my-app \
2  --url=https://gitlab.com/demo-flux/my-app.git \
3  --branch=main \
4  --interval=30s \
5  --secret-ref=https-credentials \
6  --export > ./clusters/local/my-app-source.yaml

J’ai ajouté le paramètre —secret.

Maintenant, je vais créer une ressource Image repository pour dire à Flux quel registre d’images de conteneurs scanné :

1flux create image repository my-app \
2--image=registry.gitlab.com/demo-flux/my-app \
3--interval=30s \
4--export > ./clusters/local/my-app-registry.yaml

Le manifeste créé est le suivant :

1---
2apiVersion: image.toolkit.fluxcd.io/v1beta1
3kind: ImageRepository
4metadata:
5  name: my-app
6  namespace: flux-system
7spec:
8  image: registry.gitlab.com/demo-flux/my-app
9  interval: 30s

Une fois la ressource créée, je crée une image policy. Je génère le manifeste qui décrit cette ressource avec la commande suivante :

1flux create image policy my-app \
2--image-ref=my-app \
3--select-semver=1.0.x \
4--export > ./clusters/local/my-app-policy.yaml

Le fichier généré est le suivant :

 1---
 2apiVersion: image.toolkit.fluxcd.io/v1beta1
 3kind: ImagePolicy
 4metadata:
 5  name: my-app
 6  namespace: flux-system
 7spec:
 8  imageRepositoryRef:
 9    name: my-app
10  policy:
11    semver:
12      range: 1.0.x

Je choisis le semantic versioning comme police. Je vais créer un tag Git 1.0.0 pour créer une image Docker avec le même tag.

Maintenant, la dernière étape est de créer une ressource de type Image Update Automation

1flux create image update my-app \
2--git-repo-ref=my-app \
3--git-repo-path="./manifests/base" \
4--checkout-branch=main \
5--push-branch=main \
6--author-name=fluxcdbot \
7--author-email=fluxcdbot@users.noreply.github.com \
8--commit-template="{{range .Updated.Images}}{{println .}}{{end}}" \
9--export > ./clusters/local/my-app-automation.yaml

Avec cette commande, je spécifie à Flux où se trouve l’image déployée avec le paramètre —git-repo-path. Et dans le manifeste Kubernetes de mon déploiement, je vais ajouter une annotation au niveau de la ligne de l’image # {"$imagepolicy": "flux-system:my-app"}

Dès que je la pousse dans le répertoire distant et que Flux détecte le nouveau tag créé, il va se charger de mettre à jour le manifeste dans le répertoire my-app. Ensuite Flux va détecter que le manifeste a changé et il va donc appliquer la modification. Et c’est ainsi, que l’image déployée est mise à jour automatiquement, sans intervention humaine.

9.Conclusion

Flux est un outil très puissant et simple à utiliser qui permet d’implémenter le déploiement continu avec la méthode GitOps et ainsi de bénéficier de ces nombreux avantages.