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
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.