Introduction à l'utilisation de HelmFile


Découverte Helmfile
Qu'est ce que Helmfile ?
Helmfile est un wrapper pour les déploiements de charts Helm.
Helmfile ajoute des fonctionnalités supplémentaires à Helm et permet, entre autres, de déployer un ensemble de charts Helm pour créer un artefact de déploiement complet.
Il prend en charge la customisation des déploiements selon l'environnement, la gestion des secrets mais aussi l'utilisation de Kustomize.
HelmFile utilise une approche déclarative, nous allons décrire l'état désiré de notre stack applicative et HelmFile fera le reste.
Le projet est relativement bien suivi par la communauté et les mises à jour sont régulières.
Installation
L'installation peut se faire de plusieurs manières.
Ici, nous allons installer le binaire directement sur notre distrribution Linux.
A la date de l'écriture de cet article, la dernière version stable est la v0.171.0.
$ curl -L https://github.com/helmfile/helmfile/releases/download/v0.171.0/helmfile_0.171.0_linux_amd64.tar.gz | tar -xz helmfile
$ sudo mv helmfile /usr/local/bin/
Vous pouvez vérifier que HelmFile est bien installé sur votre OS.
$ helmfile --version
helmfile version 0.171.0
Je vous renvoie au projet GitHub officiel pour les autres types d'installation:
Package Manager
Docker
Préparation
Pour cette démo, j'utilise minikube pour pouvoir profiter rapidement d'un cluster Kubernetes fonctionnel.
Le binaire Helm est un prérequis à l'utilisation de HelmFile.
Installons donc helm
avant de rentrer dans le vif du sujet.
curl -L https://get.helm.sh/helm-v3.17.1-linux-amd64.tar.gz | tar -xz linux-amd64/helm --strip-components 1
$ sudo mv helm /usr/local/bin/
HelmFile utilise par défaut certains plugins Helm.
Avant la première utilisation, il est donc nécessaire d'initialiser HelmFile pour que ce dernier aille récupérer les plugins Helm nécessaires à son fonctionnement.
$ helmfile init
The helm plugin "diff" is not installed, do you want to install it? [y/n]: y
Install helm plugin diff
Downloading https://github.com/databus23/helm-diff/releases/download/v3.9.14/helm-diff-linux-amd64.tgz
Preparing to install into /home/bruno/.local/share/helm/plugins/helm-diff
Installed plugin: diff
The helm plugin "secrets" is not installed, do you want to install it? [y/n]: y
Install helm plugin secrets
Installed plugin: secrets
The helm plugin "s3" is not installed, do you want to install it? [y/n]: y
Install helm plugin s3
Downloading and installing helm-s3 v0.16.0 ...
Checksum is valid.
Installed plugin: s3
The helm plugin "helm-git" is not installed, do you want to install it? [y/n]: y
Install helm plugin helm-git
Installed plugin: helm-git
helmfile initialization completed!
Utilisation
Démarrons l'utilisation de HelmFile par un projet simple pour poser les bases du sujet.
Mon premier projet HelmFile
Je souhaite installer NGinx.
Supposons une première version du fichier helmfile.yaml
qui va représenter l'état désiré de notre premiére release Helm ⬇
repositories:
- name: bitnami
url: https://charts.bitnami.com/bitnami
releases:
- name: nginx
namespace: default
chart: bitnami/nginx
Pour synchroniser l'état du cluster K8S avec la déclaration du fichier helmfile.yaml
, il suffit de taper la commande helmfile apply
en étant dans le dossier où se trouve le fichier déclaratif helmfile.yaml
.
$ helmfile apply
Adding repo bitnami https://charts.bitnami.com/bitnami
"bitnami" has been added to your repositories
Comparing release=nginx, chart=bitnami/nginx, namespace=default
********************
Release was not present in Helm. Diff will show entire contents as new.
********************
default, nginx, Deployment (apps) has been added:
-
+ # Source: nginx/templates/deployment.yaml
+ apiVersion: apps/v1
+ kind: Deployment
+ metadata:
+ name: nginx
+ namespace: "default"
[...]
Upgrading release=nginx, chart=bitnami/nginx, namespace=default
Release "nginx" does not exist. Installing it now.
NAME: nginx
LAST DEPLOYED: Thu Mar 6 20:44:24 2025
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
CHART NAME: nginx
CHART VERSION: 19.0.1
APP VERSION: 1.27.4
[...]
Listing releases matching ^nginx$
nginx default 1 2025-03-06 20:44:24.42934593 +0100 CET deployed nginx-19.0.1 1.27.4
UPDATED RELEASES:
NAME NAMESPACE CHART VERSION DURATION
nginx default bitnami/nginx 19.0.1 4s
L'output de la commande est tronquée ici pour des raisons de lisibilité mais, par défaut, HelmFile affiche la différence entre l'état du cluster avant l'installation et après l'éxécution de l'installation de la release du chart Helm NGinx grâce au plugin helm-diff.
Bon, c'est bien beau tout ça mais par défaut, NGinx installe un service en mode LoadBalancer et je n'ai pas de service de type LoadBalancer sur Minikube pour cette démo donc ce dernier reste désespérément sur un status pending
.
nginx LoadBalancer 10.107.63.122 <pending> 80:30309/TCP,443:31536/TCP 24h
Nous allons donc surcharger les values pour demander à ce que notre service se transforme en NodePort.
Jetons un oeil aux values du chart Helm NGinx proposé par Bitnami : https://artifacthub.io/packages/helm/bitnami/nginx?modal=values
Nous allons changer la value service.type ⬇
repositories:
- name: bitnami
url: https://charts.bitnami.com/bitnami
releases:
- name: nginx
namespace: default
chart: bitnami/nginx
values:
- service:
type: NodePort
Une commande particulièrement utile, helmfile diff
permet de visualiser les changements qui vont être opérés sur le cluster K8S avant son éventuel déploiement.
$ helmfile diff
Adding repo bitnami https://charts.bitnami.com/bitnami
"bitnami" has been added to your repositories
Comparing release=nginx, chart=bitnami/nginx, namespace=default
default, nginx, Service (v1) has changed:
# Source: nginx/templates/svc.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx
namespace: "default"
labels:
app.kubernetes.io/instance: nginx
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/name: nginx
app.kubernetes.io/version: 1.27.4
helm.sh/chart: nginx-19.0.1
annotations:
spec:
- type: LoadBalancer
+ type: NodePort
sessionAffinity: None
externalTrafficPolicy: "Cluster"
ports:
- name: http
port: 80
targetPort: http
- name: https
port: 443
targetPort: https
selector:
app.kubernetes.io/instance: nginx
app.kubernetes.io/name: nginx
Parfait, c'est le résultat attendu.
Passons à l'éxecution.
$ helmfile apply
Adding repo bitnami https://charts.bitnami.com/bitnami
"bitnami" has been added to your repositories
Comparing release=nginx, chart=bitnami/nginx, namespace=default
default, nginx, Service (v1) has changed:
[...]
Upgrading release=nginx, chart=bitnami/nginx, namespace=default
Release "nginx" has been upgraded. Happy Helming!
NAME: nginx
LAST DEPLOYED: Fri Mar 7 21:04:41 2025
NAMESPACE: default
STATUS: deployed
REVISION: 2
TEST SUITE: None
NOTES:
CHART NAME: nginx
CHART VERSION: 19.0.1
APP VERSION: 1.27.4
[...]
Notre v2 est maintenant installée avec un service NodePort fonctionnel.
$ helm ls
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
nginx default 2 2025-03-07 21:04:41.076721801 +0100 CET deployed nginx-19.0.1 1.27.4
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx NodePort 10.107.63.122 <none> 80:30309/TCP,443:31536/TCP 24h
Environnements
Jusque là, l'application de modifications reste basique.
HelmFile est capable de gérer plusieurs configurations selon les environnements que l'on souhaite installer.
Les environnements peuvent être définis dans différents fichiers déclaratifs YAML qui vont représenter les configurations spécifiques qui seront installées pour l'environnement choisi.
Continuons sur notre installation NGinx pour y appliquer une configuration particulière selon sur quel environnement nous souhaitons installer notre release.
Par défaut, HelmFile utilise un environnement qu'il nomme default.
Supposons que, pour notre environnement de production, les métriques soient mises à disposition, là où en hors-production, nous n’en voulons pas.
Créons notre fichier d'environnement que nous nommerons environments.yaml
.
environments:
default:
prod:
values:
- prod-environment-values.yaml
hprod:
values:
- hprod-environment-values.yaml
Laissons de côté l'environnement default pour nous concentrer sur nos 2 autres environnements.
Nous allons donc créer les fichiers de values spécifiques à nos environnements PROD et HPROD.
Le premier, prod-environment-values.yaml
sera construit ainsi ⬇
metrics:
enabled: true
Le second, hprod-environment-values.yaml
, vous l'aurez facilement compris sera construit ainsi ⬇
metrics:
enabled: false
La dernière étape consiste à informer le fichier helmfile.yaml
de prendre en compte ces configurations d'environnements spécifiques grâce à la directive suivante:
bases:
- environments.yaml
Notre fichier complet devient donc ⬇
repositories:
- name: bitnami
url: https://charts.bitnami.com/bitnami
bases:
- environments.yaml
---
releases:
- name: nginx
namespace: default
chart: bitnami/nginx
values:
- service:
type: NodePort
- metrics:
enabled: {{ .Values.metrics }}
Maintenant, si nous voulons installer une version NGinx avec les métriques activées, il nous suffit de passer la commande suivante ⬇
$ helmfile apply -e prod
Je me retrouve bien avec la version de NGinx qui possède en sidecar le container pour les métriques.
$ kubectl get po
NAME READY STATUS RESTARTS AGE
nginx-6df97d7679-7qvdb 2/2 Running 0 24m
$ kubectl get pods -o=custom-columns='CONTAINERS:spec.containers[*].name'
CONTAINERS
nginx,metrics
A l'inverse, pour déployer la version hors-production sans l'exposition des métriques Prometheus ⬇
$ helmfile apply -e hprod
$ k get po
NAME READY STATUS RESTARTS AGE
nginx-bd777c6bc-fkv45 1/1 Running 0 13s
$ kubectl get pods -o=custom-columns='CONTAINERS:spec.containers[*].name'
CONTAINERS
nginx
La différence se trouve sous la colonne Ready indiquant le nombre de containers que contient le pod.
Il est possible également de pouvoir définir l'environnment à installer au travers de la variable d'environnement
HELMFILE_ENVIRONMENT
.
Gestion des secrets
HelmFile est capable de pouvoir gérer les secrets en utilisant le plugim helm-secrets ou d'utiliser des coffres-forts distants au travers du packages vals.
Dans cette démo, nous allons stocker notre secret dans un coffre-fort openBao pour le récupérer lors du déploiement de notre release NGinx.
Profitons-en pour ajouter l'installation d'OpenBao au travers de notre fichier helmfile.yaml
repositories:
- name: openbao
url: https://openbao.github.io/openbao-helm
- name: bitnami
url: https://charts.bitnami.com/bitnami
bases:
- environments.yaml
---
releases:
- name: openbao
namespace: default
chart: openbao/openbao
- name: nginx
namespace: default
chart: bitnami/nginx
values:
- values.yaml
- service:
type: NodePort
- metrics:
enabled: {{ .Values.metrics }}
Appliquons notre modification en envoyant la commande helmfile apply
.
Le résultat: Les releases des charts Helm NGinx
et OpenBao
seront déployés sur notre namespace.
$ helmfile apply -e=prod
[...]
$ kubectl get po
NAME READY STATUS RESTARTS AGE
nginx-6df97d7679-lbcnc 2/2 Running 0 6m27s
openbao-0 1/1 Running 0 6m28s
openbao-agent-injector-7c7589ff5b-k78zb 1/1 Running 0 6m29s
J'ai généré 3 secrets sur OpenBao en amont et je vais m'en servir pour personnaliser le comportement de NGinx et utiliser mes propres certificats et clés TLS.
Nous allons récupérer nos secrets depuis OpenBao dans nos déploiements pour éviter de laisser trainer des secrets (comme la clé privée pour le TLS dans le cas présent) sur les fichiers HelmFile ou Helm.
Créons un fichier default-environment-values.yaml
.
service:
tlscrt: ref+vault://kv/secretsPasswords/#tlscrt
tlskey: ref+vault://kv/secretsPasswords/#tlskey
Nous devons maintenant informer notre fichier d'environnements que nous souhaitons utiliser ce nouveau fichier default-environment-values.yaml
dans nos déploiements PROD et HPROD.
Le fichier environemnts.yaml
devient ⬇
environments:
default:
prod:
values:
- default-environment-values.yaml
- prod-environment-values.yaml
hprod:
values:
- default-environment-values.yaml
- hprod-environment-values.yaml
Nous allons ensuite modifier le fichier helmfile.yaml
pour remplacer le certificat, la clé privée et le CA de notre release NGinx.
Attention, le fichier commence à être conséquent.
Il s'agit là d'ajouter un manifeste K8S Secret de type kubernetes.io/tls qui va contenir notre certificat, sa clé privée et l'autorité de certification que nous avons ajouté à OpenBao et récupéré sur les values suivantes :
service.tlscrt
service.tlskey
NGinx demande à ce que le certificat et la chaine de certification soit concaténé dans un seul et unique fichier. Le fichier tls.crt contient donc le certificat + l’autorité de certification.
Cette étape est possible grâce à la value extraDeploy offerte par le chart Helm NGinx de Bitnami.
values:
- extraDeploy:
- apiVersion: v1
kind: Secret
metadata:
name: custom-tls
type: kubernetes.io/tls
data:
tls.key: {{ .Values.service.tlskey | fetchSecretValue | b64enc }}
tls.crt: {{ .Values.service.tlscrt | fetchSecretValue | b64enc }}
La seconde modification du fichier helmfile.yaml
va créer un bloc de configuration NGinx pour lui informer de l’emplacement de nos certificats et clés et lui demander de faire du TLS.
- serverBlock: |-
# HTTPS Server
server {
# Port to listen on, can also be set in IP:PORT format
listen 8443 ssl;
ssl_certificate /certs/tls.crt;
ssl_certificate_key /certs/tls.key;
include "/opt/bitnami/nginx/conf/bitnami/*.conf";
location /status {
stub_status on;
access_log off;
allow 127.0.0.1;
deny all;
}
}
La dernière modification du fichier helmfile.yaml
va surcharger les values relatives au TLS pour y prendre en compte nos nouveaux fichiers déployés sur le secret K8S custom-tls
.
- tls:
enabled: true
existingSecret: custom-tls
certFilename: tls.crt
certKeyFilename: tls.key
Le fichier au complet ⬇
repositories:
- name: openbao
url: https://openbao.github.io/openbao-helm
- name: bitnami
url: https://charts.bitnami.com/bitnami
bases:
- environments.yaml
---
releases:
- name: openbao
namespace: default
chart: openbao/openbao
- name: nginx
namespace: default
chart: bitnami/nginx
values:
- service:
type: NodePort
- metrics:
enabled: {{ .Values.metrics }}
- tls:
enabled: true
existingSecret: custom-tls
certFilename: tls.crt
certKeyFilename: tls.key
- extraDeploy:
- apiVersion: v1
kind: Secret
metadata:
name: custom-tls
type: kubernetes.io/tls
data:
tls.key: {{ .Values.service.tlskey | fetchSecretValue | b64enc }}
tls.crt: {{ .Values.service.tlscrt | fetchSecretValue | b64enc }}
- serverBlock: |-
# HTTPS Server
server {
# Port to listen on, can also be set in IP:PORT format
listen 8443 ssl;
ssl_certificate /certs/tls.crt;
ssl_certificate_key /certs/tls.key;
include "/opt/bitnami/nginx/conf/bitnami/*.conf";
location /status {
stub_status on;
access_log off;
allow 127.0.0.1;
deny all;
}
}
Dernière étape, créer les variables d'environnement suivantes pour que HelmFile puisse se connecter à notre coffre-fort:
$ export VAULT_ADDR=<FQDN de votre instance OpenBao>
$ export VQULT_TOKEN=<Votre token OpenBao>
Appliquons la modification et observons l'état de notre déploiement.
$ helmfile apply -e=prod
[...]
$ kubectl port-forward --address 0.0.0.0 svc/nginx 8000:443
$ curl -v https://127.0.0.1:8000
* Trying 127.0.0.1:8000...
* Connected to 127.0.0.1 (127.0.0.1) port 8000 (#0)
* ALPN: offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
* ALPN: server accepted http/1.1
* Server certificate:
* subject: CN=n0l1n3ry; C=FR; ST=xxx; L=xxx; O=n0l1n3ry
* start date: Mar 8 20:03:46 2025 GMT
* expire date: Mar 6 20:03:46 2035 GMT
[...]
Notre nouveau certificat est bien pris en compte et aucun secret n'est visible sur la configuration HelmFile.
Kustomize
HelmFile gère également certains transformers Kustomize, le strategicMergePatches et le jsonPatches pour jouer et opérer des tranformations aux manifestes K8S d'un projet.
Commençons par y introduire une annotation sur le déploiement NGinx à l'aide du builtin AnnotationTransformer de Kustomize.
Créons un fichier kustomization.yaml
. (Le nom ici n'a pas d'importance)
commonAnnotations:
producedBy: n0l1n3ry
Informons notre fichier principal helmfile.yaml
de la présence de notre nouveau fichier en y ajouter une ligne sous la directive values
de notre release NGinx.
[...]
releases:
[...]
chart: bitnami/nginx
values:
- kustomization.yaml
[...]
Appliquons la modification et observons l'état de notre déploiement.
$ helmfile apply -e=prod
$ kubectl get deploy nginx -o jsonpath='{.metadata.annotations}' | jq .
{
"deployment.kubernetes.io/revision": "1",
"meta.helm.sh/release-name": "nginx",
"meta.helm.sh/release-namespace": "default",
"producedBy": "n0l1n3ry"
}
Et comme je n'ai aucune imagination, tentons de tranformer cette annotation commune par une autre sur le déploiement NGinx uniquement.
Pour cela, utilisons jsonPatches
.
Ajoutons à la fin du fichier helmfile.yaml
la configuration suivante:
jsonPatches:
- target:
version: v1
kind: Deployment
name: nginx
patch:
- op: replace
path: /metadata/annotations/producedBy
value: "n0l1n3ry Corporation"
Le fichier helmfile.yaml
au complet ⬇
repositories:
- name: openbao
url: https://openbao.github.io/openbao-helm
- name: bitnami
url: https://charts.bitnami.com/bitnami
bases:
- environments.yaml
---
releases:
- name: openbao
namespace: default
chart: openbao/openbao
- name: nginx
namespace: default
chart: bitnami/nginx
values:
- kustomization.yaml
- service:
type: NodePort
- metrics:
enabled: {{ .Values.metrics }}
- tls:
enabled: true
existingSecret: custom-tls
certFilename: tls.crt
certKeyFilename: tls.key
- extraDeploy:
- apiVersion: v1
kind: Secret
metadata:
name: custom-tls
type: kubernetes.io/tls
data:
tls.key: {{ .Values.service.tlskey | fetchSecretValue | b64enc }}
tls.crt: {{ .Values.service.tlscrt | fetchSecretValue | b64enc }}
- serverBlock: |-
# HTTPS Server
server {
# Port to listen on, can also be set in IP:PORT format
listen 8443 ssl;
ssl_certificate /certs/tls.crt;
ssl_certificate_key /certs/tls.key;
include "/opt/bitnami/nginx/conf/bitnami/*.conf";
location /status {
stub_status on;
access_log off;
allow 127.0.0.1;
deny all;
}
}
jsonPatches:
- target:
version: v1
kind: Deployment
name: nginx
patch:
- op: replace
path: /metadata/annotations/producedBy
value: "n0l1n3ry Corporation"
Le résultat sur notre déploiement NGinx:
$ kubectl get deploy nginx -o jsonpath='{.metadata.annotations}' | jq .
{
"deployment.kubernetes.io/revision": "1",
"meta.helm.sh/release-name": "nginx",
"meta.helm.sh/release-namespace": "default",
"producedBy": "n0l1n3ry Corporation"
}
Suppression
Pour supprimer nos releases NGinx et OpenBao, une seule commande suffit ⬇
$ helmfile destroy
Plus aucune ressource ou manifest K8S ne sera présent sur notre namespace.
Conclusion
Il ne s'agit-là que d'un aperçu des possibilités qu'offre HelmFile.
Je ne peux que vous conseillez d'aller voir la documentation officielle du projet pour y découvrir toutes les possibilités de cet outil et la valeur ajouté qu'il peut apporter aux déploiements de vos releases Helm.
Subscribe to my newsletter
Read articles from Bruno directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Bruno
Bruno
Depuis août 2024, j'accompagne divers projets sur l'optimisation des processus DevOps. Ces compétences, acquises par plusieurs années d'expérience dans le domaine de l'IT, me permettent de contribuer de manière significative à la réussite et l'évolution des infrastructures de mes clients. Mon but est d'apporter une expertise technique pour soutenir la mission et les valeurs de mes clients, en garantissant la scalabilité et l'efficacité de leurs services IT.