Aller au contenu
Conteneurs & Orchestration medium

CI/CD Helm : automatiser le packaging, lint et promotion des charts

16 min de lecture

logo helm

Automatiser le cycle de vie des charts Helm en CI/CD garantit la qualité (lint, tests), la reproductibilité (versioning automatique), et la traçabilité (qui a publié quoi, quand). Ce module présente une pipeline complète avec GitLab CI et GitHub Actions, ainsi que les patterns de promotion dev → staging → prod.


Workflow CI/CD Helm : Lint, Test, Package, Publish, puis Deploy optionnel vers dev, staging et prod

ÉtapeOutilsObjectif
Linthelm lint, ct lintSyntaxe YAML, bonnes pratiques
Testhelm template, kubeval, kubeconformManifests K8s valides
Packagehelm packageCréer le .tgz versionné
Publishhelm pushPousser sur registry OCI
Deployhelm upgrade --install ou GitOpsDéployer sur cluster

Fenêtre de terminal
# Lint un chart
helm lint ./charts/my-app
# Lint strict (warnings = erreurs)
helm lint ./charts/my-app --strict

Chart Testing (ct) est l’outil officiel pour tester des charts en CI :

Fenêtre de terminal
# Installer ct
pip install chart-testing
# Lint tous les charts modifiés
ct lint --config .ct.yaml
# Lint tous les charts
ct lint --all --config .ct.yaml

Configuration .ct.yaml :

.ct.yaml
chart-dirs:
- charts
validate-maintainers: false
validate-chart-schema: true
lint-conf:
- --strict
Fenêtre de terminal
# Si values.schema.json existe
helm lint ./charts/my-app # Valide automatiquement les values

Fenêtre de terminal
# Générer les manifests sans cluster
helm template my-app ./charts/my-app -f values-test.yaml > rendered.yaml
# Vérifier que ça génère du YAML valide
helm template my-app ./charts/my-app > /dev/null
echo $? # 0 = OK
Fenêtre de terminal
# Installer kubeconform (rapide, maintenu)
# https://github.com/yannh/kubeconform
helm template my-app ./charts/my-app | kubeconform -strict -summary

Si votre chart définit des tests dans templates/tests/ :

Fenêtre de terminal
# Déployer puis tester
helm install my-app ./charts/my-app -n test --wait
helm test my-app -n test
helm uninstall my-app -n test

Option 1 : Version depuis Chart.yaml (manuelle)

Chart.yaml
version: 1.2.3 # Incrémenter manuellement
appVersion: "2.0.0"

Option 2 : Version depuis Git tag

Fenêtre de terminal
# Récupérer la version depuis le tag Git
VERSION=$(git describe --tags --abbrev=0 2>/dev/null || echo "0.0.0")
# Remplacer dans Chart.yaml
sed -i "s/^version:.*/version: $VERSION/" charts/my-app/Chart.yaml
# Packager
helm package ./charts/my-app

Option 3 : Semantic Release

# .releaserc.yaml pour semantic-release
plugins:
- "@semantic-release/commit-analyzer"
- "@semantic-release/release-notes-generator"
- ["@semantic-release/exec", {
"prepareCmd": "sed -i 's/^version:.*/version: ${nextRelease.version}/' charts/my-app/Chart.yaml"
}]
Fenêtre de terminal
# Packager
helm package ./charts/my-app
# Résultat : my-app-1.2.3.tgz

Fenêtre de terminal
# Login (une fois par job)
echo "$REGISTRY_PASSWORD" | helm registry login $REGISTRY_URL -u $REGISTRY_USER --password-stdin
# Push
helm push my-app-1.2.3.tgz oci://$REGISTRY_URL/charts
Fenêtre de terminal
# Vérifier que le chart est accessible
helm show chart oci://$REGISTRY_URL/charts/my-app --version 1.2.3

.gitlab-ci.yml
stages:
- lint
- test
- package
- publish
variables:
HELM_VERSION: "3.14.0"
CHART_PATH: "charts/my-app"
REGISTRY_URL: "harbor.example.com/charts"
.helm-base:
image: alpine/helm:${HELM_VERSION}
before_script:
- helm version
lint:
extends: .helm-base
stage: lint
script:
- helm lint ${CHART_PATH} --strict
- helm template test ${CHART_PATH} > /dev/null
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
test:
extends: .helm-base
stage: test
image: alpine:latest
before_script:
- apk add --no-cache helm curl
- curl -sL https://github.com/yannh/kubeconform/releases/latest/download/kubeconform-linux-amd64.tar.gz | tar xz
- mv kubeconform /usr/local/bin/
script:
- helm template test ${CHART_PATH} | kubeconform -strict -summary
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
package:
extends: .helm-base
stage: package
script:
# Version depuis le tag Git ou CI_COMMIT_TAG
- |
if [ -n "$CI_COMMIT_TAG" ]; then
VERSION=$CI_COMMIT_TAG
else
VERSION="0.0.0-${CI_COMMIT_SHORT_SHA}"
fi
- sed -i "s/^version:.*/version: $VERSION/" ${CHART_PATH}/Chart.yaml
- helm package ${CHART_PATH}
artifacts:
paths:
- "*.tgz"
expire_in: 1 week
rules:
- if: $CI_COMMIT_TAG
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
publish:
extends: .helm-base
stage: publish
script:
- echo "${REGISTRY_PASSWORD}" | helm registry login ${REGISTRY_URL} -u ${REGISTRY_USER} --password-stdin
- helm push *.tgz oci://${REGISTRY_URL}
dependencies:
- package
rules:
- if: $CI_COMMIT_TAG

.github/workflows/helm.yml
name: Helm CI/CD
on:
push:
branches: [main]
tags: ['v*']
pull_request:
branches: [main]
env:
CHART_PATH: charts/my-app
REGISTRY: ghcr.io
REGISTRY_PATH: ${{ github.repository_owner }}/charts
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Helm
uses: azure/setup-helm@v3
with:
version: '3.14.0'
- name: Lint chart
run: |
helm lint ${{ env.CHART_PATH }} --strict
helm template test ${{ env.CHART_PATH }} > /dev/null
test:
runs-on: ubuntu-latest
needs: lint
steps:
- uses: actions/checkout@v4
- name: Set up Helm
uses: azure/setup-helm@v3
- name: Install kubeconform
run: |
curl -sL https://github.com/yannh/kubeconform/releases/latest/download/kubeconform-linux-amd64.tar.gz | tar xz
sudo mv kubeconform /usr/local/bin/
- name: Validate manifests
run: helm template test ${{ env.CHART_PATH }} | kubeconform -strict -summary
publish:
runs-on: ubuntu-latest
needs: test
if: startsWith(github.ref, 'refs/tags/v')
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Set up Helm
uses: azure/setup-helm@v3
- name: Get version from tag
id: version
run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
- name: Update Chart.yaml version
run: |
sed -i "s/^version:.*/version: ${{ steps.version.outputs.VERSION }}/" ${{ env.CHART_PATH }}/Chart.yaml
- name: Package chart
run: helm package ${{ env.CHART_PATH }}
- name: Login to GHCR
run: echo "${{ secrets.GITHUB_TOKEN }}" | helm registry login ${{ env.REGISTRY }} -u ${{ github.actor }} --password-stdin
- name: Push chart
run: helm push *.tgz oci://${{ env.REGISTRY }}/${{ env.REGISTRY_PATH }}

  • Répertoirecharts/
    • Répertoiremy-app/
      • Chart.yaml
      • values.yaml Defaults
      • Répertoiretemplates/
    • Répertoirevalues/
      • values-dev.yaml Dev overrides
      • values-staging.yaml
      • values-prod.yaml
Fenêtre de terminal
# Dev (automatique sur main)
helm upgrade --install my-app oci://registry/charts/my-app \
--version 1.2.3 \
-f values/values-dev.yaml \
-n dev
# Staging (manuel ou tag)
helm upgrade --install my-app oci://registry/charts/my-app \
--version 1.2.3 \
-f values/values-staging.yaml \
-n staging
# Prod (approval + tag)
helm upgrade --install my-app oci://registry/charts/my-app \
--version 1.2.3 \
-f values/values-prod.yaml \
-n prod
# GitLab CI - promotion avec approval
deploy-staging:
stage: deploy
script:
- helm upgrade --install my-app oci://${REGISTRY}/my-app --version ${VERSION} -f values/values-staging.yaml -n staging
environment:
name: staging
rules:
- if: $CI_COMMIT_TAG
deploy-prod:
stage: deploy
script:
- helm upgrade --install my-app oci://${REGISTRY}/my-app --version ${VERSION} -f values/values-prod.yaml -n prod
environment:
name: production
when: manual # Approval requis
rules:
- if: $CI_COMMIT_TAG

# Application Argo CD
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: my-app
namespace: argocd
spec:
project: default
source:
repoURL: oci://harbor.example.com/charts
chart: my-app
targetRevision: 1.2.3
helm:
valueFiles:
- values-prod.yaml
destination:
server: https://kubernetes.default.svc
namespace: prod
syncPolicy:
automated:
prune: true
selfHeal: true
# HelmRelease Flux
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
name: my-app
namespace: prod
spec:
interval: 5m
chart:
spec:
chart: my-app
version: "1.2.3"
sourceRef:
kind: HelmRepository
name: my-charts
namespace: flux-system
values:
replicaCount: 3
# ... prod values

Objectif : Créer une pipeline qui lint, package et publie un chart sur push vers main ou tag.

  1. Structure du repo

    Fenêtre de terminal
    mkdir helm-cicd-lab && cd helm-cicd-lab
    git init
    # Créer un chart
    mkdir -p charts
    helm create charts/my-app
    # Créer les values par env
    mkdir -p values
    cp charts/my-app/values.yaml values/values-dev.yaml
    cp charts/my-app/values.yaml values/values-prod.yaml
  2. Créer la pipeline (GitLab ou GitHub selon votre environnement)

    Copier le fichier .gitlab-ci.yml ou .github/workflows/helm.yml ci-dessus.

  3. Configurer les secrets

    • GitLab : Settings → CI/CD → Variables

      • REGISTRY_URL
      • REGISTRY_USER
      • REGISTRY_PASSWORD
    • GitHub : Settings → Secrets and variables → Actions

      • GITHUB_TOKEN est automatique pour GHCR
  4. Pousser et déclencher

    Fenêtre de terminal
    git add .
    git commit -m "feat: initial chart"
    git push origin main
    # Créer un tag pour publish
    git tag v0.1.0
    git push origin v0.1.0
  5. Vérifier

    • Pipeline lint/test passe
    • Chart publié sur le registry
    • helm show chart oci://registry/charts/my-app --version 0.1.0

Critères de réussite :

  • Pipeline déclenchée sur push
  • helm lint exécuté sans erreur
  • Manifests validés avec kubeconform
  • Chart packagé et versionné depuis le tag
  • Publication automatique vers registry OCI

  • Lint : helm lint --strict + ct lint pour la qualité
  • Test : helm template + kubeconform pour valider les manifests
  • Package : versioning automatique depuis Git tag
  • Publish : helm push vers registry OCI
  • Promotion : même chart, values différents par environnement
  • GitOps : Argo CD / Flux pour les déploiements prod (recommandé)

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracing. Aujourd'hui, ce site ne couvre même pas mes frais d'hébergement, d'électricité, de matériel, de logiciels, mais surtout de cafés.

Un soutien régulier, même symbolique, m'aide à garder ces ressources gratuites et à continuer de produire des guides de qualité. Merci pour votre appui.