Helm Chart Releaser avec GitLab CI et Parent-Child Pipelines
Sommaire
En tant qu'admin système, je gère quelques clusters Kubernetes, y déploie des applications, notamment avec Helm. J'utilise essentiellement GitLab comme plateforme de développement, avec la fonctionnalité d'Intégration et Déploiement Continue associée (GitLab CI).
Introduction
Pour livrer quelques charts Helm, j'ai cherché quelque chose de similaire à ce que fait GitHub avec les actions et le chart-releaser. J'ai trouvé quelques projets utilisant les GitLab Pages et Chartmuseum, mais rien que ne me satisfasse.
J'ai alors décidé d'écrire mon propre GitLab CI helm-releaser, avec les spécifications suivantes :
- Le dépôt des charts Helm doit avoir la même structure que ceux que l'on trouve sur GitHub (comme ceux de Bitnami, Grafana, etc.)
- Le "releaser" de charts helm doit ne délivrer que les charts qui sont modifiés / mis à jour. La CI, en se déclenchant, ne doit pas tout régénérer.
- Les charts Helm doivent être mis à disposition sur Harbor, dépôt d'images Docker et charts Helm, que j'utilise déjà.
- Pouvoir le déployer dans un environnement de staging Kubernetes.
- Optionnellement, pouvoir générer une release GitLab associée.
Ci-après quelques informations sur comment le chart releaser a été construit, avec les fonctionnalités de la CI GitLab et un peu de bash, comme d'habitude.
La fonctionnalité principale utilisée ici est de pouvoir générer des pipelines dynamiques, avec l'option "parent-child pipelines". Vous trouverez plus d'information ici : https://about.gitlab.com/blog/2020/04/24/parent-child-pipelines/
Structure du dépôt GitLab
La structure du dépôt git ressemble basiquement à cela :
1$ tree -aL 1
2.
3├── charts
4├── gci-templates
5├── .gitlab-ci.yml
6└── scripts
- Le dossier charts contient tous les charts.
- Le dossier gci-templates contient les templates de jobs CI/CD.
- Le dossier scripts contient les scripts permettant la génération automatique des jobs de CI.
- Le fichier .gitlab-ci.yml classique pour utiliser la CI GitLab.
Publication Helm avec la CI/CD
Publier un chart Helm est assez simple :
- On valide le linting (visibilité / syntaxe du code) du chart.
- On vérifie que l'installation peut passer avec un
--dry-run
. - On package le chart et on l'envoie sur le registre de charts Harbor (ou un autre, au choix), avec un tag
dev
(voir la partie "Problèmes & Contournements" à ce propos). - On déploie une version correctement taggée sur Harbor (voir semver).
- On crée une release GitLab.
Les pipelines de CI ne doivent se déclencher seulement si charts/my-chart
est modifié. J'inclus donc un template pour éviter le copier/coller pour chaque chart que je dois générer.
Note : pour ce blog post, uniquement le lint & release sont affichées. Un pipeline plus complet est disponible à la fin de l'article.
Ce qui donne le code suivant dans le job de CI .gitlab-ci.yml
:
1include: '/gci-templates/.gitlab-ci.yml'
2
3stages:
4 - test
5 - install
6 - release
7
8'my-chart:lint':
9 stage: test
10 extends: .lint
11 variables:
12 HELM_NAME: "my-chart"
13 rules:
14 - if: '$CI_COMMIT_TAG =~ "/^$/"'
15 changes:
16 - charts/my-chart/**/*
17
18'my-chart:release':
19 stage: release
20 extends: .release
21 variables:
22 HELM_NAME: "my-chart"
23 rules:
24 - if: '$CI_COMMIT_TAG =~ "/^$/"'
25 changes:
26 - charts/my-chart/Chart.yaml
Le template inclus ressemble à cela :
1---
2image: dtzar/helm-kubectl:3.5.3 # last version using k8s 1.20
3
4.lint:
5 script:
6 - helm lint .
7
8.release:
9 script:
10 - apk add git
11 - helm plugin install https://github.com/chartmuseum/helm-push
12 - helm repo add --username=${DOCKER_REGISTRY_USER} --password=$(cat "$DOCKER_REGISTRY_PASSWORD") ${HELM_PROJECT} https://${DOCKER_REGISTRY}/chartrepo/${HELM_PROJECT}
13 - helm package .
14 - helm cm-push . ${HELM_PROJECT}
OK, maintenant que le job de CI est là, on peut publier le chart... Mais comme je n'ai pas envie de récrire les tâches de CI à chaque fois, on va utiliser les pipelines générés !
Generated Pipelines
Les pipelines générés sont très simples : il s'agit d'une tâche de CI qui génère d'autres tâches. On fait cela en 2 étapes :
- On génère un fichier yalm qui inclut toutes les étapes de la CI qu'on doit effectuer. Ce fichier yaml peut être créé via des scripts (ici bash)
1chart-generator:
2 stage: generate
3 image: alpine:3.15
4 script:
5 - ./scripts/generate-pipeline.sh > generated-pipeline.yml
6 artifacts:
7 expire_in: 1 hour
8 paths:
9 - generated-pipeline.yml
10 rules:
11 - if: '$CI_COMMIT_TAG =~ "/^$/"'
12 changes:
13 - charts/**/*
Le script generate-pipeline.sh
contient le code simple précédemment vu pour générer un seul chart :
1#!/usr/bin/env bash
2
3cat <<EOF
4include: '/gci-templates/.gitlab-ci.yml'
5stages:
6 - test
7 - install
8 - release
9EOF
10
11for f in $(find charts/* -maxdepth 0 -type d)
12do
13 CHART_VERSION=$(grep "^version:" ${f}/Chart.yaml | cut -d' ' -f2-)
14 CHART_RELEASE="${f##*/}-${CHART_VERSION}"
15 cat <<EOF
16'${f##*/}:lint':
17 stage: test
18 extends: .lint
19 variables:
20 HELM_NAME: "${f##*/}"
21 rules:
22 - if: '\$CI_COMMIT_TAG =~ "/^$/"'
23 changes:
24 - ${f}/**/*
25'${f##*/}:release':
26 stage: release
27 extends: .release
28 variables:
29 HELM_NAME: "${f##*/}"
30 rules:
31 - if: '\$CI_COMMIT_TAG =~ "/^$/"'
32 changes:
33 - ${f}/Chart.yaml
34EOF
35
36done
-
Appelons ce pipeline généré dans la prochaine étape de la CI, avec 2 mots-clés :
- trigger : pour appeler un pipeline "enfant".
- include artefact : pour appeler l'artefact précédemment généré.
1chart-jobs:
2 stage: generate
3 needs:
4 - chart-generator
5 trigger:
6 include:
7 - artifact: generated-pipeline.yml
8 job: chart-generator
9 strategy: depend
10 rules:
11 - if: '$CI_COMMIT_TAG =~ "/^$/"'
12 changes:
13 - charts/**/*
Avec toutes ces étapes, un pipeline enfant sera déclenché à chaque fois qu'un chart sera créé ou modifié, sans devoir recopier toutes les tâches de la CI.
Magic
Maintenant que l'on a tout ce qu'il faut pour ne pas réécrire les pipelines à chaque fois, on peut pousser autant de charts que l'on veut, il suffit juste de l'inclure dans le dépôt, et la CI fera le reste !
Remarques et Ressources
On retrouvera le "Helm Chart Releaser" quasiment complet ici, il suffit "juste" de modifier quelques variables. Bien-sûr, à tester avant utilisation ;)
https://gitlab.com/rverchere/helm-chart-release-example
Voir aussi la section release_tag
pour pousser les releases sur directement sur GitLab !
"Last but not the least", il ne reste plus qu'un peu de GitOps pour déployer en prod, mais cela est une autre histoire ;)
Problèmes & Contournements
Avec Helm, il n'y a pas de notion de tag "latest" comme dans Docker. Pour contourner cela, on utilisera avec Harbor le tag O.0.0-dev
. Ainsi, à chaque nouvelle release orienté dev
dans Harbor, on utilisera ce tag.