From 0eacfcb86c4322e20f77dea7162955525037bf2e Mon Sep 17 00:00:00 2001 From: Denys SAVCHENKO <savchenko@apc.in2p3.fr> Date: Tue, 25 Mar 2025 15:51:59 +0100 Subject: [PATCH] helm step2 --- docs/kubernetes/06_my_first_helm.md | 159 +++++++++++++++++- files/charts/step2/jupyter-chart/Chart.yaml | 24 +++ .../jupyter-chart/templates/configmap.yaml | 25 +++ .../jupyter-chart/templates/deployment.yaml | 82 +++++++++ .../jupyter-chart/templates/ingress.yaml | 19 +++ .../step2/jupyter-chart/templates/pvc.yaml | 13 ++ .../step2/jupyter-chart/templates/secret.yaml | 7 + .../jupyter-chart/templates/service.yaml | 15 ++ files/charts/step2/jupyter-chart/values.yaml | 23 +++ files/charts/step2/values.yaml | 3 + 10 files changed, 368 insertions(+), 2 deletions(-) create mode 100644 files/charts/step2/jupyter-chart/Chart.yaml create mode 100644 files/charts/step2/jupyter-chart/templates/configmap.yaml create mode 100644 files/charts/step2/jupyter-chart/templates/deployment.yaml create mode 100644 files/charts/step2/jupyter-chart/templates/ingress.yaml create mode 100644 files/charts/step2/jupyter-chart/templates/pvc.yaml create mode 100644 files/charts/step2/jupyter-chart/templates/secret.yaml create mode 100644 files/charts/step2/jupyter-chart/templates/service.yaml create mode 100644 files/charts/step2/jupyter-chart/values.yaml create mode 100644 files/charts/step2/values.yaml diff --git a/docs/kubernetes/06_my_first_helm.md b/docs/kubernetes/06_my_first_helm.md index dc0248f..af97a74 100644 --- a/docs/kubernetes/06_my_first_helm.md +++ b/docs/kubernetes/06_my_first_helm.md @@ -286,8 +286,6 @@ spec: storage: {{ .Values.storage.size }} ``` -Les charts modifiés sont fourni [au repo](https://gitlab.in2p3.fr/si-apc/tp-kubernetes-si/-/tree/main/files/charts/step1) - Et aussi ajoutons les valeurs par défaut au fichier `values.yaml` : ```yaml title="values.yaml" @@ -320,6 +318,9 @@ resources: cpu: "2" ``` +Le chart modifié et le fichier de valeurs sont fourni [au repo](https://gitlab.in2p3.fr/si-apc/tp-kubernetes-si/-/tree/main/files/charts/step1) + + Appliquez la nouvelle version du chart avec des valeurs remplacées : ```bash @@ -338,5 +339,159 @@ REVISION UPDATED STATUS CHART 2 Wed Mar 12 23:16:11 2025 deployed jupyter-chart-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 + +```yaml title="secret.yaml" +apiVersion: v1 +data: + password: {{ .Values.auth.password | b64enc }} +kind: Secret +metadata: + creationTimestamp: null + name: jupyter-pwd +``` + +et ajoutons dans `values.yaml` dans le chart: + +```yaml +auth: + password: password +``` + + +Appliquons le chart : + +```bash +helm upgrade my-jupyter jupyter-chart --values values.yaml +``` + +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](https://helm.sh/docs/howto/charts_tips_and_tricks/#automatically-roll-deployments) pour déclencher le redémarrage de pod lors du changement de Configmap/Secret. +Ajoutons l'annotation dans déploiement: + +```yaml title="deployment.yaml" +kind: Deployment +spec: + template: + metadata: + annotations: + checksum/config: {{ include (print $.Template.BasePath "/secret.yaml") .Values | 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") : + +```yaml title="ingress.yaml" +{{ 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` + +```yaml +... +ingress: + enabled: true +``` + +Volume persistant requise plusieurs modifications : + +```yaml title="pvc.yaml" +{{ 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 }} +``` + +```yaml title="deployment.yaml" +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 pouvons vérifier que les modèles sont lisibles et produisent des `yaml` valides + +```bash +helm lint jupyter-chart/ -f values.yaml +``` + +et peut être installé : + +```bash +helm upgrade my-jupyter jupyter-chart/ -f values.yaml --dry-run +``` + +et finalement appliquer : + +```bash +helm upgrade my-jupyter jupyter-chart/ -f values.yaml +``` + +et vérifier que le volume n'est plus là `kubectl get pvc`. + +Le chart à ce stade [est ici](https://gitlab.in2p3.fr/si-apc/tp-kubernetes-si/-/tree/main/files/charts/step2) + diff --git a/files/charts/step2/jupyter-chart/Chart.yaml b/files/charts/step2/jupyter-chart/Chart.yaml new file mode 100644 index 0000000..23f2f66 --- /dev/null +++ b/files/charts/step2/jupyter-chart/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +name: jupyter-chart +description: A Helm chart for Kubernetes + +# 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" diff --git a/files/charts/step2/jupyter-chart/templates/configmap.yaml b/files/charts/step2/jupyter-chart/templates/configmap.yaml new file mode 100644 index 0000000..27c9f73 --- /dev/null +++ b/files/charts/step2/jupyter-chart/templates/configmap.yaml @@ -0,0 +1,25 @@ +apiVersion: v1 +data: + initpwd.py: | + #!/usr/bin/env python + + from jupyter_server.auth import passwd + import os + import json + + if os.getenv('JUPYTER_PWD') is not None: + pwd = passwd(os.getenv('JUPYTER_PWD')) + + + cfg_dir = os.path.join(os.getenv('HOME'), '.jupyter') + os.makedirs(cfg_dir, exist_ok=True) + cfg_json = os.path.join(cfg_dir, 'jupyter_server_config.json') + + with open(cfg_json, 'w') as fd: + json.dump({"IdentityProvider": {"hashed_password": pwd}}, fd) + else: + print("JUPYTER_PWD environment variable is not set.") +kind: ConfigMap +metadata: + creationTimestamp: null + name: initscript diff --git a/files/charts/step2/jupyter-chart/templates/deployment.yaml b/files/charts/step2/jupyter-chart/templates/deployment.yaml new file mode 100644 index 0000000..05ab631 --- /dev/null +++ b/files/charts/step2/jupyter-chart/templates/deployment.yaml @@ -0,0 +1,82 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + io.kompose.service: jupyter + name: jupyter +spec: + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: + io.kompose.service: jupyter + strategy: + type: {{ .Values.strategy }} + template: + metadata: + annotations: + checksum/config: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }} + 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 }} + ports: + - containerPort: 8888 + protocol: TCP + env: + - name: JUPYTER_PORT + value: "8888" + livenessProbe: + exec: + command: + - /etc/jupyter/docker_healthcheck.py + initialDelaySeconds: 5 + timeoutSeconds: 1 + periodSeconds: 10 + failureThreshold: 3 + volumeMounts: + {{- if .Values.storage.enabled | default false }} + - mountPath: /home/jovyan/work/local + name: jupyter-claim0 + {{- end }} + - name: jupyter-confdir + mountPath: /home/jovyan/.jupyter + initContainers: + - image: jupyter/base-notebook + name: pwd-init + command: + - python + - /initpwd.py + volumeMounts: + - name: initscript + mountPath: /initpwd.py + subPath: initpwd.py + - name: jupyter-confdir + mountPath: /home/jovyan/.jupyter + env: + - name: JUPYTER_PWD + valueFrom: + secretKeyRef: + name: jupyter-pwd + key: password + restartPolicy: Always + volumes: + {{- if .Values.storage.enabled | default false }} + - name: jupyter-claim0 + persistentVolumeClaim: + claimName: jupyter-claim0 + {{- end }} + - name: initscript + configMap: + name: initscript + - name: jupyter-confdir + emptyDir: {} + diff --git a/files/charts/step2/jupyter-chart/templates/ingress.yaml b/files/charts/step2/jupyter-chart/templates/ingress.yaml new file mode 100644 index 0000000..41045c7 --- /dev/null +++ b/files/charts/step2/jupyter-chart/templates/ingress.yaml @@ -0,0 +1,19 @@ +{{ if .Values.ingress.enabled | default false -}} +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 }} \ No newline at end of file diff --git a/files/charts/step2/jupyter-chart/templates/pvc.yaml b/files/charts/step2/jupyter-chart/templates/pvc.yaml new file mode 100644 index 0000000..a2aebea --- /dev/null +++ b/files/charts/step2/jupyter-chart/templates/pvc.yaml @@ -0,0 +1,13 @@ +{{ 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 }} \ No newline at end of file diff --git a/files/charts/step2/jupyter-chart/templates/secret.yaml b/files/charts/step2/jupyter-chart/templates/secret.yaml new file mode 100644 index 0000000..500733d --- /dev/null +++ b/files/charts/step2/jupyter-chart/templates/secret.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +data: + password: {{ .Values.auth.password | b64enc }} +kind: Secret +metadata: + creationTimestamp: null + name: jupyter-pwd diff --git a/files/charts/step2/jupyter-chart/templates/service.yaml b/files/charts/step2/jupyter-chart/templates/service.yaml new file mode 100644 index 0000000..f5973ec --- /dev/null +++ b/files/charts/step2/jupyter-chart/templates/service.yaml @@ -0,0 +1,15 @@ +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 + diff --git a/files/charts/step2/jupyter-chart/values.yaml b/files/charts/step2/jupyter-chart/values.yaml new file mode 100644 index 0000000..840e7d8 --- /dev/null +++ b/files/charts/step2/jupyter-chart/values.yaml @@ -0,0 +1,23 @@ +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 +auth: + password: password +ingress: + enabled: true \ No newline at end of file diff --git a/files/charts/step2/values.yaml b/files/charts/step2/values.yaml new file mode 100644 index 0000000..0fa21b2 --- /dev/null +++ b/files/charts/step2/values.yaml @@ -0,0 +1,3 @@ +resources: + limits: + cpu: "2" -- GitLab