Faire du GitOps avec Flux v2 (Part 2/4) - Templétisation avec Helm

Introduction

Dans l’article précédent, nous avons vu comment mettre en place FluxCD sur un cluster Kubernetes afin de créer automatiquement des ressources lorsque des manifestes YAML sont poussés sur un répertoire de code.  Dans cette deuxième partie, nous allons voir comment utiliser Helm pour créer des ressources plus rapidement à l’aide de templates variabilisés. Premièrement, nous allons voir son utilisation seule dans un cluster Kubernetes, puis son utilisation dans le cadre d’une synchronisation de répertoire Git avec un cluster lorsqu’on utilise FluxCD.

Pour rappel, voici l’infrastructure que nous avons mise en place sur notre cluster Kubernetes.

Mon installation:

  • Instance AWS Cloud9
  • Instance type: m5.large (8 GiB RAM + 2 vCPU)
  • OS: Ubuntu Server 18.04 LTS
  • Minikube v1.27.1
  • Helm v3.7
  • Flux v2

Helm

Helm, à quoi ça sert ?

Helm est un package manager pour Kubernetes. Il permet de déployer des applications conteneurisées à partir de templates, ce qui permet d’avoir une cohérence au niveau des fichiers de configuration entre différentes applications. Il est également utile lorsque l’on souhaite déployer une même application sous différents environnements (développement, staging, pré-production, production) avec des configurations relatives avec ces derniers.

Depuis sa V3, Helm n’a plus besoin de passer par un agent (Tiller) installé sur le cluster Kubernetes pour créer des ressources, le client est en capacité de communiquer directement avec le Kube Api Server via le port 6443.

Terminologie

  • Un chart: un package Helm qui contient toutes les définitions des ressources nécessaires pour exécuter une application.
  • Une Release: une instance d’un chart tournant sur un cluster Kubernetes. On peut installer plusieurs releases à partir d’un même chart sur un même cluster.
  • HelmRelease : Ressource qui permet de faire le pont entre Helm et Flux.
  • Dossier Template: Le fichier qui contient les manifestes variabilisés des ressources Kubernetes dans le chart
  • Fichier Values: Un fichier qui contient les valeurs attribuées aux variables contenues dans le dossier des templates.

Helm Quickstart

Installer le client Helm sur votre machine :

$ curl https://baltocdn.com/helm/signing.asc | gpg --dearmor | sudo tee /usr/share/keyrings/helm.gpg > /dev/null

$ sudo apt-get install apt-transport-https --yes

$ echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/helm.gpg] https://baltocdn.com/helm/stable/debian/ all main" | sudo tee /etc/apt/sources.list.d/helm-stable-debian.list

$ sudo apt-get update

$ sudo apt-get install helm

Voir la version de helm installée :

$ helm version

Helm repose sur le concept de chart, c'est-à-dire, des templates que nous pouvons utiliser pour créer un ensemble de ressources.

Lister les charts à disposition localement :

$ helm list

Utiliser des releases officielles

Pour démarrer et comprendre le principe de Helm, il peut être utile d’utiliser des charts officiels

Ajouter un répertoire publique (ici bitnami):

$ helm repo add bitnami https://charts.bitnami.com/bitnami

Afficher toutes les releases disponibles chez bitnami:

$ helm search repo bitnami

Installer le chart mysql développé par bitnami:

$ helm install my-release bitnami/mysql

Cette commande déploie une release du chart mysql sur le cluster kubernetes.

On peut observer les releases présentes sur notre cluster en utilisant la commande suivante :

$ helm ls
NAME        NAMESPACE     REVISION    STATUS        CHART           APP VERSION
my-release  default       1           deployed      nginx-13.2.10   1.23.1 

Si on regarde l'état des pods :

$ kubectl get pods
NAME                                READY   STATUS    RESTARTS   AGE
my-release-nginx-6ff47c9598-2php6   1/1     Running   0          36s

Désinstaller sa release du cluster :

$ helm uninstall my-release

Tous les répertoires sont centralisés sur artifacthub.io .

Créer sa release custom

On peut utiliser des charts officiels mais il peut être utile de créer les siens dans certains cas. Pour ce faire, il existe la commande suivante :

$ helm create mychart

La structure d’un chart Helm ressemble à la suivante:

  • ./template: Le dossier dans lequel seront stockés les manifestes de création de ressource (ici un fichier deployment.yaml et service.yaml à titre d’exemple).
  • ./template/deployment.yaml: un manifest permettant de créer une ressource deployment
  • ./template/service.yaml: Un manifest permettant de créer une ressource service
  • ./template/_helpers.tpl: Le fichier template dans lequel les valeurs par défaut du chart sont déclarées
  • Chart.yaml: Manifest permettant de créer le chart qui est de type “application” ou “library”
  • values.yaml: Fichier contenant les valeurs à appliquer dans le chart

Ce chart de démonstration étant un peu dense, effaçons le et recréons en un plus simple. Disons que nous souhaitons créer le même déploiement que celui de l’article précédent. A savoir:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  minReadySeconds: 5
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.14.2
        ports:
        - containerPort: 80

Un appel au fichier de valeur se manifeste dans une expression entourée par des accolades “{{“ et “}}”. Ici par exemple, le nom et un label du déploiement sont variabilisés, ainsi que le nombre de replicas.

./templates/pod.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Values.deployment.name }}-deployment
  labels:
    app: {{ .Values.deployment.name }}
spec:
  replicas: {{ .Values.deployment.replicas }}
  selector:
    matchLabels:
      app: {{ .Values.deployment.name }}
  template:
    metadata:
      labels:
        app: {{ .Values.deployment.name }}
    spec:
      containers:
      - name: {{ .Values.deployment.name }}
        image: {{ .Values.deployment.image }}
        ports:
        - containerPort: {{ .Values.deployment.port }}

Des valeurs peuvent ensuite être initiées à ces variables, depuis le fichier de values:

values.yaml

deployment:
  name: nginx
  replicas: 3
  image: nginx:1.14.2
  port: 80

Avant de créer une release à partir de ce dossier template et ce fichier de valeurs, on peut vouloir tester le template au préalable, avec la commande suivante:

$ helm template mychart-release ./ --debug

Avec :

  • mychart-release: Le nom que vous souhaitez donner à votre release (attention que des lettres minuscules, chiffres ainsi que les caractères “-” et “.” ne sont autorisés)
  • ./ : Le chemin de votre chart. Ici nous sommes dedans.
  • --debug: Un des flags

Une fois que celui-ci est validé, le template chart peut être considéré comme utilisable et nous pouvons donc créer la release. Pour ce faire, utilisons la commande suivante:

$ helm install mychart-release ./ 

Nous pouvons vérifier ensuite que les 3 pods ont bien été créés avec kubectl:

$ kubectl get pods
NAME                                READY   STATUS    RESTARTS   AGE
nginx-deployment-7fb96c846b-7djq7   1/1     Running   0          11s
nginx-deployment-7fb96c846b-b69qz   1/1     Running   0          11s
nginx-deployment-7fb96c846b-z47bm   1/1     Running   0          11s

Pour vérifier le comportement de Helm, créons un deuxième fichier de valeur en passant le nombre de replicas à 2 cette fois-ci et en changeant le nom et l'image utilisée pour le déploiement:

values2.yaml

deployment:
  name: redis
  replicas: 2
  image: redis
  port: 8080

Pour créer une nouvelle release à partir de ce nouveau fichier de valeur, utilisons la commande suivante:

$ helm install mychart-release-2 ./ -f values2.yaml

Vérifions le nombre de pods:

$ kubectl get pods
NAME                                READY   STATUS    RESTARTS   AGE
nginx-deployment-7fb96c846b-7djq7   1/1     Running   0          40m
nginx-deployment-7fb96c846b-b69qz   1/1     Running   0          40m
nginx-deployment-7fb96c846b-z47bm   1/1     Running   0          40m
redis-deployment-78fb488fd5-9bcdd   1/1     Running   0          3s
redis-deployment-78fb488fd5-wsks2   1/1     Running   0          3s

Nous avons bien 2 nouveau pods redis qui ont été lancés, et cela à partir du même template mais en passant un fichier de valeurs différent.

L’utilisation du helpers.tpl

Jusqu’à présent, nous avons pu récupérer des valeurs depuis un fichier afin de personnaliser des ressources créées à partir de manifestes communs à d’autres applications. Dans ce cadre d’utilisation, il peut être utile d’inclure des valeurs par défaut dans les manifestes (gain de temps et homogénéisation dans la structure des ressources), valeurs qui seraient adaptées à chaque application mais non renseignées dans le fichier de valeur. Ces valeurs pourraient par exemple être le numéro de version de la Release, le namespace dans lequel la Release est déployée, la concaténation du nom de l’application et de sa version pour des impératifs de labellisation (pour du monitoring par exemple). Certaines de ces valeurs sont récupérables grâce à des fonctions pré-intégrées dans Helm (par exemple “Release.Name”, “Release.Namespace”, “Release.Revision”) et sont injectables dans un manifeste en passant par des blocs balisés par des accolades “{{“ et “}}”. Par exemple:

{{ .Release.Name }}

L’avantage de passer par ces variables, est qu’elles sont réutilisables dans tout le chart. Vous pouvez retrouver la liste des fonctions et objets présents dans Helm sur le lien suivant:

Helm Built In Objects

Pour les variables qui ne sont pas prévues ou qui sont spécifiques à la politique informatique d’une entreprise, il existe le fichier  “_helper.tpl” (tpl pour template) qui permet de créer ses propres fonctions  et variables.  Par exemple, dans le cadre d’un déploiement, imaginons qu'il est impératif que tous les pods contiennent un label contenant le nom de la personne qui déploie l’application. Il est possible de le faire en définissant une variable dans le fichier helper et en injectant la variable dans le manifeste comme suit:

_helpers.tpl

{{- define "mychart.labels" -}}
      deployedby: paul
{{- end }}

deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Values.deployment.name }}-deployment
  labels:
    app: {{ .Values.deployment.name }}
spec:
  replicas: {{ .Values.deployment.replicas }}
  selector:
    matchLabels:
      app: {{ .Values.deployment.name }}
  template:
    metadata:
      labels:
        app: {{ .Values.deployment.name }}
        {{- include "mychart.labels" . | nindent 8 }}
    spec:
      containers:
      - name: {{ .Values.deployment.name }}
        image: {{ .Values.deployment.image }}
        ports:
        - containerPort: {{ .Values.deployment.port }}

En regardant de plus prêt notre pod, nous pouvons voir que le label lui a bien été ajouté :

$ kubectl describe pod redis-deployment-64d969dc4-hjskh
Name:             redis-deployment-64d969dc4-hjskh
Namespace:        default
Priority:         0
Service Account:  default
Node:             minikube/192.168.49.2
Start Time:       Thu, 13 Oct 2022 09:12:13 +0000
Labels:           app=redis
                  deployedby=paul
                  pod-template-hash=64d969dc4

Dans ce cas là,  l’intérêt d’utiliser ce helper n'est pas bien clair, il aurait été plus simple d'écrire la mention “deployedby=paul” directement dans le template. Mais le helper permet de créer des conventions de nommage.

Par exemple, si au sein d'une entreprise, chaque pod doit être nommé par le nom et la version de l’application (qui eux sont indiqués dans le fichier de valeur), la variable podname peut être construite comme suit:

_helpers.tpl

{{- define "mychart.podname" -}}
{{- printf "%s-%s" .Values.deployment.name .Values.deployment.version }}
{{- end }}

Chaque équipe utilisant ce template se verra imposer cette même convention de nommage.

Il est possible de faire de nombreuses choses avec ce fichier de template, de nombreuses fonctions existent pour créer de nouvelles variables. L’objectif derrière cette création de valeur par défaut est encore une fois d’homogénéiser les ressources Kubernetes au sein d’un groupe ou d’une entreprise.

Et si on combine FluxCD et Helm ?

Nous avons vu le fonctionnement de FluxCD dans le premier article de cette série, puis le fonctionnement de Helm, le meilleur des mondes voudrait que l’on puisse utiliser les deux en même temps, c’est à dire qu’à chaque fois qu’un fichier de valeur serait modifié (le nombre de replicas dans un déploiement par exemple), les ressources présentes sur un cluster serait mises à jour avec ces valeurs. Il s’avère que Helm vient nativement avec FluxCD, via le Helm-Controller, lorsque celui-ci est installé sur un cluster.

Reprenons une nouvelle infrastructure, je vais détailler à nouveau toutes les étapes qui permettent d’avoir une telle synchronisation.

Lancement d’un cluster Kubernetes en local

Je recréée d’abord une instance Cloud9 sur laquelle j’installe Minikube.

$ curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64

$ sudo install minikube-linux-amd64 /usr/local/bin/minikube

Une fois installée, j’ai un cluster Kubernetes accessible sur mon instance. Je peux le lancer avec la commande suivante:

$ minikube start

N’oublions pas de créer un alias pour simuler l’utilisation de la commande kubectl

$ alias kubectl="minikube kubectl --"

Installation de la CLI Flux

Absolument rien ne change ici par rapport au premier article:

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

Installation de Flux et Helm dans le cluster

La seconde étape consiste en la génération d’un token d’accès depuis Gitlab et de l’utiliser comme variable d’environnement dans l’instance. Ce token permettra à flux de s’authentifier auprès de Gitlab pour créer le projet d’installation ou alors de le récupérer s’il existe déjà.

$ export GITLAB_TOKEN=<your-token>

Bootstraper le projet qui va permettre d’installer Helm et FluxCD v2 sur notre cluster, dans un namespace “flux-system”

$ flux bootstrap gitlab \
 --owner=paulboisson \
 --repository=article-gitops-fluxcd-setup \
 --branch=main \
 --path=clusters \
 --token-auth \
 --personal

Cette commande est la même que celle utilisée dans le premier article.

Nous pouvons observer les ressources qui ont été créées en affichant les pods dans le namespace “flux-system”:

$ kubectl get pods -n flux-system
NAME                                    READY   STATUS    RESTARTS   AGE
helm-controller-68b799b589-8hz6f        1/1     Running   0          59s
kustomize-controller-7ddb8d8f7-cp5w9    1/1     Running   0          59s
notification-controller-56bd788f9-sdrtn 1/1     Running   0          59s
source-controller-7d98d6688c-j4c6j      1/1     Running   0          59s

Mise en place de la livraison continue

Regardons de plus près ce que nous voulons mettre en place, nous souhaitons faire de la livraison continue, c’est à dire qu’à chaque modification du code, les nouvelles configurations des ressources vont être poussées sur le cluster.

Nous avons 2 répertoires impliqués (ils peuvent être confondus et être seulement des dossiers dans un même répertoire), l’un contient le chart avec le template de notre application, l’autre contient ce qu’on appelle le helmrelease (différent de la release helm qui est la ressource créée à partir d’un chart helm).

Récupération du projet

Tout d’abord, clonons le projet que j’ai créé à l’avance

$ git clone https://gitlab.com/paulboisson/article-gitops-fluxcd-helmrelease

Dans ce projet, nous avons 4 dossiers:

  • chart: contient le chart et le dossier de template (même que précédemment)
  • config: contient le fichier de configuration de Kustomize
  • releases: contient les fichiers helmrelease qui seront les fichiers dont la détection devra être détectée
  • setup: contient les fichiers à appliquer manuellement dans le cluster (pour la création d’une source Git et la préparation de Kustomize)

Nous verrons ces différents fichiers plus en détails par la suite.

Création d’une source

FluxCD est constitué  de différents  outils GitOps qui permettent notamment de faire de la livraison continue sur Kubernetes. L’un de ces outils, ou composant, est le Source Controller. Il permet de construire des artefacts utilisables depuis Kubernetes à partir de différentes sources:

  • Un répertoire Git
  • Un répertoire Helm
  • Un bucket S3

Dans le cadre de cet article, nous souhaitons pouvoir récupérer un projet depuis Gitlab et devons donc passer par l’API “GitRepository” via l’utilisation d’une ressource de type “GitRepository” comme décrit ci-dessous:

gitRepo.yaml

apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: GitRepository
metadata:
  name: myappgitsource
  namespace: default
spec:
  interval: 1m0s
  url: https://gitlab.com/paulboisson/article-gitops-helmrelease.git
  ref:
    branch: main

Cette ressource nommée “myappgitssource” va sonder le répertoire Gitlab indiqué, toutes les minutes et créer un artefact composé du code contenu sur la branche “main”.

Pour créer cette ressource, on peut soit

  • créer ce fichier YAML et l’appliquer avec la commande:
$ kubectl apply -f gitRepo.yaml
  • ou alors utiliser la commande flux dédiée:
$ flux create source git myappsource \
--url=https://gitlab.com/paulboisson/article-gitops-fluxcd-helmreleaset \
--branch=main

On peut observer la ressource correspondante et le nom de l’artefact:

$ kubectl get gitrepository
NAME             URL                                       AGE   READY 
myappgitsource  https://gitlab.com/paulboisson/article….   7s    True  

STATUS
stored artifact for revision 'main/2f41a6b7d5c69bd27e3ad7caa85c7c8a875b3fae'

Création d’une ressource Kustomization

Le kustomize-controller est un opérateur Kubernetes, qui permet de faire de la livraison continue, il permet notamment de surveiller un répertoire et de déclencher une action lorsque des modifications y sont apportées. C’est le controller que nous avons utilisé dans le premier article. Ici nous devons créer une ressource de type “Kustomization” pour faire appel au Kustomize-Controller:

Kustomize.yaml

apiVersion: kustomize.toolkit.fluxcd.io/v1beta2
kind: Kustomization
metadata:
  name: app
spec:
  sourceRef:
    kind: GitRepository
    name: myappsource
  path: ../config
  prune: true
  interval: 1m

Cette ressource va se baser sur les artefacts générés par la ressource “myappsource” pour déterminer si une modification a été apportée dans le code. Il est possible de créer cette ressource à la main en utilisant la commande :

$ kubectl apply -f Kustomize.yaml

Vérifions que cette ressource a bien été créée et qu’elle est prête à être utilisée:

$ kubectl get kustomization
NAME   AGE   READY   STATUS
app    3s    True    Applied revision: main/2f41a6b7d5c69bd27e3ad7caa85c7c8a875b3fae

Cependant il faut qu’une autre ressource Kustomization soit présente dans le répertoire pour indiquer les ressources à surveiller. Ici il s’agit du fichier “app.yaml” présent dans le dossier “/releases”. Cette ressource sera automatiquement créé à partir du manifeste suivant par la Kustomize-Controller

/config/kustomization.yaml

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
metadata:
  name: myappkustomize
commonLabels:
  app: hello
resources:
- ../releases/app.yaml

La HelmRelease

Le Helm-Controller permet de faire du templating à partir de charts et de ressources générées par Flux. La ressource de type HelmRelease est une ressource que le Helm Controller est capable de manipuler dans son utilisation avec FluxCD.

Dans notre cas, c’est ce fichier que nous allons modifier pour faire varier notre configuration. Cette ressource va être automatiquement créée par le Kustomize-Controller.

/releases/app.yaml

apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
  name: my-app-release
  namespace: default
spec:
  interval: 1m
  chart:
    spec:
      sourceRef:
        kind: GitRepository
        name: myappgitsource
        namespace: default
      chart: chart
      interval: 1m
  values:
    deployment:
      name: redis
      version: 3630
      replicas: 3
      image: redis
      port: 8080

On y renseigne la source du code (ici la ressource “myappsource” de type “GitRepository” qui est présente dans le namespace par défaut). Le chart est contenu dans le dossier “chart” de la source et la mise à jour de la helmrelease devra se faire toutes les minutes. Il est à noter que comme vu précédemment, la source pourrait être de type “HelmRepository” ou “Bucket”

Le champ spec.values est l’équivalent du fichier values.yaml dans un chart Helm. Ces valeurs viendront écraser les valeurs renseignées dans le fichier de valeur du chart.

Afin de souligner cet écrasement de valeur, regardons les valeurs dans le chart:

/chart/values.yaml

deployment:
  name: ToBeOverride
  version: ToBeOverride
  replicas: ToBeOverride
  image: ToBeOverride
  port: ToBeOverride

Elles seront écrasées par les valeurs de la HelmRelease.

Bilan

Dans cet article nous avons vu comment faire du templating avec Helm qui vient nativement lorsque l’on installe Flux v2. Il semblerait que cette nouvelle version permette de faire plus de choses que la première et notamment la génération de notification que nous aborderons dans un prochain article.

Voici un schéma qui résume ce que nous avons réussi à mettre en place:

Source

Les répertoires Gitlab