Mon premier chart Helm¶
Mes premières commandes Helm¶
Helm est le gestionnaire de paquet de Kubernetes. Les paquets helm peuvent se trouver soit dans un dépot de charts, soit en local.
Pour ajouter un dépot. On peut les trouver sur Artifact Hub -- un catalogue des charts.
Listez les charts que vous pouvez installer :
NAME CHART VERSION APP VERSION DESCRIPTION
stable/acs-engine-autoscaler 2.2.2 2.1.1 DEPRECATED Scales worker nodes within agent pools
stable/aerospike 0.3.5 v4.5.0.5 DEPRECATED A Helm chart for Aerospike in Kubern...
stable/airflow 7.13.3 1.10.12 DEPRECATED - please use: https://github.com/air...
stable/ambassador 5.3.2 0.86.1 DEPRECATED A Helm chart for Datawire Ambassador
stable/anchore-engine 1.7.0 0.7.3 Anchore container analysis and policy evaluatio...
...
Installez un chart d'exemple :
Verifiez que l'installation a créée les ressources dans le namespace tuto
:
k get all
NAME READY STATUS RESTARTS AGE
pod/jupyter-74bfb8cc64-g78rs 1/1 Running 1 (65m ago) 66m
pod/my-example-mysql-6cb4f55c5-hmcqn 1/1 Running 0 83s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/jupyter ClusterIP 10.43.202.0 <none> 8888/TCP 7d1h
service/my-example-mysql ClusterIP 10.43.197.116 <none> 3306/TCP 83s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/jupyter 1/1 1 1 7d1h
deployment.apps/my-example-mysql 1/1 1 1 83s
NAME DESIRED CURRENT READY AGE
replicaset.apps/jupyter-74bfb8cc64 1 1 1 66m
replicaset.apps/my-example-mysql-6cb4f55c5 1 1 1 83s
Helm permet de configurer les options de déploiement d'application. Ici nous avons configuré la limite de la mémoire de conteneur. Pour voir les valeurs par défaut :
Et les valeurs appliquées :
Pour voir ce qui a été déployé :
helm ls
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
my-example-mysql tuto 1 2025-03-12 18:09:16.987606589 +0100 CET deployed mysql-1.6.9 5.7.30
Et désinstaller une release :
Création de chart d'exemple¶
Maintenant, nous allons créer le chart Helm à partir des manifests que nous avions déjà créés.
Créer un projet de chart avec :
jupyter-chart
├── charts
├── Chart.yaml
├── templates
│ ├── deployment.yaml
│ ├── _helpers.tpl
│ ├── hpa.yaml
│ ├── ingress.yaml
│ ├── NOTES.txt
│ ├── serviceaccount.yaml
│ ├── service.yaml
│ └── tests
│ └── test-connection.yaml
└── values.yaml
4 directories, 10 files
- un fichier
values.yaml
contient les paramètres ajustables - un fichier
Chart.yaml
contient metadata (nom, version, description ...) - un dossier
charts
peut contenir les charts dont ce chart dépendre - un dossier
templates
contient des modèles des manifests (écrits avec langage des templates de Go) _helpers.tpl
fournit des fonctions utilesNOTES.txt
est une modèle de ce que helm affiche lors d'installation de chart- un dossier
tests
contient des définitions de Pod/Job qui peuvent être utilisés pour tester l'installation
Creation du chart Jupyter¶
Nous allons remplaces les manifests de base pour créer notre propre charte d'application Jupyter.
cp configmap.yaml jupyter-chart/templates/.
cp ingress.yaml jupyter-chart/templates/.
cp jupyter-claim0-persistentvolumeclaim.yaml jupyter-chart/templates/pvc.yaml
cp jupyter-deployment.yaml jupyter-chart/templates/deployment.yaml
cp jupyter-service.yaml jupyter-chart/templates/service.yaml
cp secret.yaml jupyter-chart/templates/.
rm jupyter-chart/templates/serviceaccount.yaml
rm jupyter-chart/templates/hpa.yaml
rm -r jupyter-chart/tests
echo '' > jupyter-chart/templates/NOTES.txt
tree jupyter-chart
tree jupyter-chart/
jupyter-chart/
├── Chart.yaml
├── templates
│ ├── _helpers.tpl
│ ├── configmap.yaml
│ ├── insgress.yaml
│ ├── pvc.yaml
│ ├── deployment.yaml
│ ├── service.yaml
│ └── secret.yaml
└── values.yaml
2 directories, 9 files
Modifions le metadata du chart :
apiVersion: v2
name: jupyter
description: My first helm chart for Jupyter
# A chart can be either an 'application' or a 'library' chart.
#
# Application charts are a collection of templates that can be packaged into versioned archives
# to be deployed.
#
# Library charts provide useful utilities or functions for the chart developer. They're included as
# a dependency of application charts to inject those utilities and functions into the rendering
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.1.0
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
appVersion: "4.0.7" # <-- on a mis la version de jupyterlab ici
Même qu'il ne soit pas configurable, c'est déjà un chart Helm valide et installable.
Effacons tous de Namespace tuto :
et installons le chart :
NAME: my-jupyter
LAST DEPLOYED: Wed Mar 12 19:38:02 2025
NAMESPACE: tuto
STATUS: deployed
REVISION: 1
TEST SUITE: None
helm ls
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
my-jupyter tuto 1 2025-03-12 19:38:02.264727617 +0100 CET deployed jupyter-0.1.0 4.0.7
Le chart a ce stade est disponible ici.
Variables de chart Helm¶
Nous allons ajouter des variables aux modèles des manifests.
...
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
io.kompose.service: jupyter
strategy:
type: {{ .Values.strategy }}
template:
metadata:
labels:
io.kompose.service: jupyter
spec:
containers:
- image: {{ .Values.image.repository }}:{{ .Values.image.tag }}
name: jupyter
resources:
requests:
cpu: {{ .Values.resources.requests.cpu }}
memory: {{ .Values.resources.requests.memory }}
limits:
cpu: {{ .Values.resources.limits.cpu }}
memory: {{ .Values.resources.limits.memory }}
...
apiVersion: v1
kind: Service
metadata:
labels:
io.kompose.service: jupyter
name: jupyter
spec:
type: {{ .Values.service.type }}
ports:
- name: jupyterlab
port: {{ .Values.service.externalPort }}
targetPort: 8888
selector:
io.kompose.service: jupyter
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: jupyter-claim0
spec:
storageClassName: {{ .Values.storage.storageClass }}
accessModes:
- {{ .Values.storage.accessMode }}
resources:
requests:
storage: {{ .Values.storage.size }}
Et aussi ajoutons les valeurs par défaut au fichier values.yaml
:
replicaCount: 1
strategy: Recreate
image:
repository: jupyter/base-notebook
tag: lab-4.0.7
resources:
requests:
cpu: 500m
memory: 500Mi
limits:
cpu: "1"
memory: 1024Mi
service:
type: ClusterIP
externalPort: 8888
storage:
storageClass: local-path
accessMode: ReadWriteOnce
size: 100Mi
Creez l'autre fichier values.yaml
dehors du dossier de chart. Il sera utilisé pour remplacer des valeurs par défaut :
Le chart modifié et le fichier de valeurs sont fourni au repo
Appliquez la nouvelle version du chart avec des valeurs remplacées :
Voir l'histoire des installations :
REVISION UPDATED STATUS CHART APP VERSION DESCRIPTION
1 Wed Mar 12 23:16:04 2025 superseded jupyter-0.1.0 4.0.7 Install complete
2 Wed Mar 12 23:16:11 2025 deployed jupyter-0.1.0 4.0.7 Upgrade complete
Instructions de contrôle de flux et fonctions¶
Le langage des modèles Helm est une combinaison du Go template language, des fonctions additionnelles et des wrappers pour exposer les objets spécifiques de Helm. Les docs complets sont accessibles sur https://helm.sh/docs/chart_template_guide/getting_started/.
Fonctions¶
La syntaxe des fonctions est {{ functionName arg1 arg2 }}
. Il est possible d'utiliser l'opérateur pipe |
pour passer l'argument de la fonction, {{ functionName arg }}
est {{ arg | functionName }}
sont équivalents. Si la fonction accepte plusieurs arguments, le dernier est passé par |
.
Pour illustrer, faisons le mot de passe configurable par valeurs
apiVersion: v1
data:
password: {{ .Values.auth.password | b64enc }}
kind: Secret
metadata:
creationTimestamp: null
name: jupyter-pwd
et ajoutons dans values.yaml
dans le chart:
Appliquons le chart :
Le mot de passe n'est pas changé après installation de la nouvelle version. C'est parce que le manifest de déploiement n'été pas changé et il n'a pas redémarré. Alors, comme le mot de passe est injecté par conteneur d'init, il garde sa valeur. Il existe une astuce pour déclencher le redémarrage de pod lors du changement de Configmap/Secret. Ajoutons l'annotation dans déploiement:
kind: Deployment
spec:
template:
metadata:
annotations:
checksum/config: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }}
...
En appliquons le chart modifié nous allons voir que le pod redémarre.
Blocs conditionnels¶
Le langage des modèles Helm support les blocs conditionnels if/else
et les boucles range
.
Nous allons rendre des composants de l'installation être optionnels (-
sert à supprimer les espaces (y compris retour à la ligne) avant/après le bloc "moustache") :
{{ if .Values.ingress.enabled | default true -}}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: jupyter
annotations:
ingress.kubernetes.io/ssl-redirect: "false"
spec:
rules:
- http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: jupyter
port:
number: {{ .Values.service.externalPort }}
{{ end }}
et ajoute le défaut aussi dans values.yaml
Question
Modifiez le chart pour que la création du volume persistante soit configurable. Notez que ça exige plusieurs modifications, dans le fichier pvc.yaml
ainsi que dans deployment.yaml
.
Vous pouvez vérifier que les modèles sont lisibles et produisent des yaml
valides :
et le chart peut être installé :
Success
{{ if .Values.storage.enabled | default false -}}
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: jupyter-claim0
spec:
storageClassName: {{ .Values.storage.storageClass }}
accessModes:
- {{ .Values.storage.accessMode }}
resources:
requests:
storage: {{ .Values.storage.size }}
{{ end }}
apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
containers:
- image: {{ .Values.image.repository }}:{{ .Values.image.tag }}
name: jupyter
volumeMounts:
{{- if .Values.storage.enabled | default false }}
- mountPath: /home/jovyan/work/local
name: jupyter-claim0
{{- end }}
- name: jupyter-confdir
mountPath: /home/jovyan/.jupyter
...
volumes:
{{- if .Values.storage.enabled | default false }}
- name: jupyter-claim0
persistentVolumeClaim:
claimName: jupyter-claim0
{{- end }}
- name: initscript
configMap:
name: initscript
- name: jupyter-confdir
emptyDir: {}
...
Nous appliquons le chart :
et vérifions que le volume n'est plus là : kubectl get pvc
.
Le chart de référence à ce stade est disponible
Boucles¶
Instruction range
permet de créer les boucles type for each
. La syntaxe de base est comme :
À l'intérieur de la boucle le contexte change, alors {{ . }}
fait référence à l'élément actuel.
Nous allons profiter d'une syntaxe plus verbeuse qui nous permet d'utiliser les indices des éléments. Cette syntaxe utilise des variables.
Pour essayer les boucles, nous allons ajouter la possibilité de créer plusieurs volumes persistants.
D'abord, modifions le fichier values.yaml
(celui dans le directoire de chart) :
replicaCount: 1
strategy: Recreate
image:
repository: jupyter/base-notebook
tag: lab-4.0.7
resources:
requests:
cpu: 500m
memory: 500Mi
limits:
cpu: "1"
memory: 1024Mi
service:
type: ClusterIP
externalPort: 8888
storage: [] # <--changé
auth:
password: password
ingress:
enabled: true
Contrairement à la configuration que nous utilisions avant, .Values.storage
sera une liste des dictionnaires, pas un dictionnaire.
Dans deployment.yaml
:
apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
containers:
- image: {{ .Values.image.repository }}:{{ .Values.image.tag }}
name: jupyter
volumeMounts:
{{- if .Values.storage | default false -}}
{{- range $index, $volume := .Values.storage }}
- mountPath: {{ $volume.mountPath }}
name: jupyter-claim{{ $index }}
{{- end -}}
{{- end }}
- name: jupyter-confdir
mountPath: /home/jovyan/.jupyter
...
volumes:
{{- if .Values.storage | default false }}
{{- range $index, $volume := .Values.storage }}
- name: jupyter-claim{{ $index }}
persistentVolumeClaim:
claimName: jupyter-claim{{ $index }}
{{- end -}}
{{- end }}
- name: initscript
configMap:
name: initscript
- name: jupyter-confdir
emptyDir: {}
Question
Changez le fichier pvc.yaml
pour créer les PVC
s correspondants. Notez que plusieurs ressources peuvent être places dans le même fichier en utilisant le séparateur ---
. Le séparateur se répétera en boucle.
Vérifiez qu'aucun PVC n'est créé avec la valeur de storage
par défaut (une liste vide). Ajoutez deux volumes au fichier values.yaml
(celui dehors directoire de chart) :
storage:
- storageClass: local-path
accessMode: ReadWriteOnce
size: 100Mi
mountPath: /home/jovyan/work/volume100
- storageClass: local-path
accessMode: ReadWriteOnce
size: 50Mi
mountPath: /home/jovyan/work/volume50
Verifiez que deux volumes sont crééz et montés lors d'installation helm upgrade my-jupyter jupyter-chart/ -f values.yaml
.
Success
{{- if .Values.storage | default false -}}
{{ range $index, $volume := .Values.storage -}}
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: jupyter-claim{{ $index }}
spec:
storageClassName: {{ $volume.storageClass }}
accessModes:
- {{ $volume.accessMode }}
resources:
requests:
storage: {{ $volume.size }}
{{ end }}
{{ end }}
Le chart de référence à ce stade est disponible
Helpers¶
Le fichier _helpers.yaml
contient des définitions utilitaires. Nous allons les utiliser pour que les noms des ressources correspondent au nom du release Helm (il est fixé par instant) et pour remplacer des étiquettes de kompose
.
Nous avons besoin d'ajouter quelques valeurs par défaut qui sont utilisées dans _helpers.tpl
:
En déploiement (diff) :
--- step3/jupyter-chart/templates/deployment.yaml 2025-03-25 18:13:11.522701853 +0100
+++ step4/jupyter-chart/templates/deployment.yaml 2025-03-25 18:31:24.339726299 +0100
@@ -1,14 +1,14 @@
apiVersion: apps/v1
kind: Deployment
metadata:
+ name: {{ include "jupyter-chart.fullname" . }}
labels:
- io.kompose.service: jupyter
- name: jupyter
+ {{- include "jupyter-chart.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
- io.kompose.service: jupyter
+ {{- include "jupyter-chart.selectorLabels" . | nindent 6 }}
strategy:
type: {{ .Values.strategy }}
template:
@@ -16,7 +16,7 @@
annotations:
checksum/config: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }}
labels:
- io.kompose.service: jupyter
+ {{- include "jupyter-chart.labels" . | nindent 8 }}
spec:
containers:
- image: {{ .Values.image.repository }}:{{ .Values.image.tag }}
@@ -67,20 +67,21 @@
- name: JUPYTER_PWD
valueFrom:
secretKeyRef:
- name: jupyter-pwd
+ name: {{ include "jupyter-chart.fullname" . }}-pwd
key: password
restartPolicy: Always
volumes:
{{- if .Values.storage | default false }}
+ {{ $basename := include "jupyter-chart.fullname" . }}
{{- range $index, $volume := .Values.storage }}
- name: jupyter-claim{{ $index }}
persistentVolumeClaim:
- claimName: jupyter-claim{{ $index }}
+ claimName: {{ $basename }}-claim{{ $index }}
{{- end -}}
{{- end }}
- name: initscript
configMap:
- name: initscript
+ name: {{ include "jupyter-chart.fullname" . }}-initscript
- name: jupyter-confdir
emptyDir: {}
Info
Notez l'utilisation de variable $basename
. Dans la boucle, le contexte change alors la variable .Values
n'est plus disponible (et cela est utilisé par modèle jupyter-chart.fullname
).
Question
Appliquez les mêmes changements aux autres manifests. Vous pouvez suivre ceux créés par la commande helm create <name>
ou consulter le chart dans le repositoire.