Loading search data...

Kubernetes - Ecrire son premier chart Helm

Publié le : 19 janvier 2022 | Mis à jour le : 19 septembre 2022

logo helm

Pour ceux qui ne connaissant pas Helm je vous renvoie à mon premier billet décrivant les concepts et l’utilisation de Helm en tant que gestionnaire de package.

Le langage de Helm est aussi complet et complexe de par sa syntaxe. La première lecture peut être rebutante. Donc accrochez-vous!

Petite précision, je débute comme vous et je risque donc de compléter cette documentation en fonction de mes découvertes. (Si vous êtes compétent sur le sujet je prends vos remarques !)

Création d’un chart

La commande helm create <nom-du-chart> permet de créer la structure complète d’un chart avec des templates pour instancier un deployment, un service, un ingress, un serviceaccount et un autoscaler.

Structure d’un chart

Créons le chart myfirst-chart :

helm create myfirst-chart
Creating myfirst-chart

tree
└── myfirst-chart
    ├── charts
    ├── Chart.yaml
    ├── .helmignore
    ├── templates
    │   ├── deployment.yaml
    │   ├── _helpers.tpl
    │   ├── hpa.yaml
    │   ├── ingress.yaml
    │   ├── NOTES.txt
    │   ├── serviceaccount.yaml
    │   ├── service.yaml
    │   └── tests
    │       └── test-connection.yaml
    └── values.yaml

Nous retrouvons :

  • un dossier charts qui contient d’éventuels charts dépendants
  • un dossier templates qui contient les manifests Kubernetes qui seront générés
  • un fichier Chart.yaml qui contient les metada du chart : nom, description,type, version du chart
  • un fichier values.yaml qui contient les valeurs qui seront utilisé pour construire les manifest kubernetes finaux à partir des templates.
  • fichier .helmignore qui indiquera quels fichiers ou répertoires à ignore lors de la construction de l’artefact. Le pacakge qui sera envoyé dans votre repository helm.

Première installation du chart

Le chart créé est opérationnel et peut être instancié dans votre cluster kubernetes :

kubectl create ns test
kubectl config set-context --current --namespace=test
cd myfirst-chart
helm install myfirst-chart ./
NAME: myfirst-chart

LAST DEPLOYED: Wed Jan 19 13:48:29 2022
NAMESPACE: test
STATUS: deployed
REVISION: 1
NOTES:
1. Get the application URL by running these commands:
  export POD_NAME=$(kubectl get pods --namespace test -l "app.kubernetes.io/name=myfirst-chart,app.kubernetes.io/instance=myfirst-chart" -o jsonpath="{.items[0].metadata.name}")
  export CONTAINER_PORT=$(kubectl get pod --namespace test $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
  echo "Visit http://127.0.0.1:8080 to use your application"
  kubectl --namespace test port-forward $POD_NAME 8080:$CONTAINER_PORT

Vérifions ce qu’il a créé :

kubectl get all
NAME                                 READY   STATUS    RESTARTS   AGE
pod/myfirst-chart-7cb78598b9-jjzcm   1/1     Running   0          30s

NAME                    TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)   AGE
service/myfirst-chart   ClusterIP   10.104.106.188   <none>        80/TCP    30s

NAME                            READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/myfirst-chart   1/1     1            1           30s

NAME                                       DESIRED   CURRENT   READY   AGE
replicaset.apps/myfirst-chart-7cb78598b9   1         1         1       30s

On a donc un déployment utilisant un pod exposé avec un service de type ClusterIP. Et oui vous avez déja fait tout ça en quelques commandes. Magique non ?

Prise en main du langage de templating Go

Helm étant écrit en Go, il utilise donc le système de templating Go. Pour ceux qui connaissent Jinja vous ne serez pas trop perdu, car il s’en inspire.

Afficher le contenu du fichier templates/service.yaml :

apiVersion: v1
kind: Service
metadata:
  name: {{ include "myfirst-chart.fullname" . }}
  labels:
    {{- include "myfirst-chart.labels" . | nindent 4 }}
spec:
  type: {{ .Values.service.type }}
  ports:
    - port: {{ .Values.service.port }}
      targetPort: http
      protocol: TCP
      name: http
  selector:
    {{- include "myfirst-chart.selectorLabels" . | nindent 4 }}

Contrôler l’indentation de la sortie

Dans un fichier yaml la position du texte à toute son importance. Pour éviter ces problèmes on utilise le filtre/fonction nindent.

    {{- include "myfirst-chart.selectorLabels" . | nindent 4 }}

Pour éviter de se retrouver avec plein de ligne vide on utilise le n caractère - (chomp) qui se place après {{ ou avant }} :

  • {{- supprime les espaces de la ligne avant le template (left-trim)
  • -}} supprime les espaces de la ligne après le template (right-trim)

Les pipelines et les fonctions

Les variables peuvent être manipulés via des fonctions qui sont appelés via les pipelines :

    {{- include "myfirst-chart.labels" . | nindent 4 }}

Les fonctions les plus courantes dans les fichiers générés par défaut :

  • replace : remplace une chaine par une autre
  • trunc : coupe une chaine à partir du n caractère
  • trimSuffix : retranche un suffixe d’une chaine
  • quote : ajout des doubles quotes à une “chaine”
  • nindent : La fonction nindent est identique à la fonction d’indentation, mais ajoute un retour à la ligne au début de la chaîne
  • title : Première lettre en majuscule

La documentation liste l’ensemble de ces fonctions ici

Accéder aux valeurs définies dans le fichier values.yaml

Pour rappel vous pouvez créer plusieurs fichiers de valeur avec des noms différents, mais lors de l’instanciation du chart il faudra lui indiquer le nom du fichier avec l’option -f : helm install myfirst-chart ./ -f dev-values.yaml

Une valeur définie dans le fichier de valeur est accessible sous la forme : {{ .Values. }} Donc ouvrons le fichier de valeur :

# Default values for myfirst-chart.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.

replicaCount: 1

...

service:
  type: ClusterIP
  port: 80
...

Si on prend la valeur replicaCount elle est bien appelé sous la forme : {{ .Values.replicaCount }} De même une valeur d’un dictionnaire est accessible sous la forme : {{ .Values.service.type }}

Vous pouvez modifier ses valeurs et afficher le manifest sans le déployer avec la commande helm template :

helm template .\

....

spec:
  type: ClusterIP
  ports:
    - port: 80
      targetPort: http
      protocol: TCP
      name: http

...

Vous pouvez mettre à jour votre Chart déployé avec la commande helm upgrade :

helm list
NAME            NAMESPACE       REVISION        UPDATED                                 STATUS          CHART                   APP VERSION
myfirst-chart   test            1               2022-01-19 13:48:29.437268209 +0000 UTC deployed        myfirst-chart-0.1.0     1.16.0

helm upgrade myfirst-chart ./
helm history
REVISION        UPDATED                         STATUS          CHART                   APP VERSION     DESCRIPTION
1               Wed Jan 19 13:48:29 2022        superseded      myfirst-chart-0.1.0     1.16.0          Install complete
2               Wed Jan 19 14:06:53 2022        deployed        myfirst-chart-0.1.0     1.16.0          Upgrade complete

Une valeur peut être définie par défaut dans le template :

image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"

Lors de l’installation du chart, s’il ne trouve pas de valeur définie dans le fichier de valeur pour la valeur image.tag alors il prend la valeur définie dans le fichier de description du chart AppVersion. Ce qui est le cas ici.

helm template ./ |grep image
          image: "nginx:1.16.0"
          imagePullPolicy: IfNotPresent
      image: busybox

Factorisation des valeurs

Pour éviter de répéter du code, il existe un premier moyen qui est d’affecteur une valeur à une variable en utilisant l’affectation $var := val. L’appel de cette variable se fera via le caractère $ placé devant le nom de la variable. Un exemple :

{{- $fullName := include "myfirst-chart.fullname" . -}}
  name: {{ $fullName }}

Un autre moyen est d’utiliser le fichier nommé _helpers.tpl qui contient des valeurs construites à partir d’autres et qui peuvent être utilisées ensuite dans tous les templates.

Elles sont appelées cette fois via un include :

  selector:
    {{- include "myfirst-chart.selectorLabels" . | nindent 4 }}

On retrouve bien sa définition dans le fichier _helpers.tpl

{{- define "myfirst-chart.selectorLabels" -}}
app.kubernetes.io/name: {{ include "myfirst-chart.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}

{{ Release.Name }} est le nom de la release qui sera instanciée.

  selector:
    app.kubernetes.io/instance: myfirst-chart
    app.kubernetes.io/name: myfirst-chart

Les Conditions

Comme dans tous les langages on utilise ici le bon vieux {{ if }} :

{{- if .Values.serviceAccount.create }}
{{- default (include "myfirst-chart.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}

Un test est négatif s’il retourne :

  • un booléen égale à false
  • une valeur égale à zero
  • une chaine vide
  • une valeur de type empty ou null
  • une collection vide

Donc les autres valeurs sont positives.

Les scopes

Pour simplifier l’écriture on peut changer la portée d’une variable avec l’opérateur with. Prenons un exemple pour comprendre son fonctionnement. Par exemple on a la variable favorite définie avec :

favorite:
  drink: coffee
  food: pizza

Pour éviter d’écrire à chaque fois .Values.favorite on utilise with :

  {{- with .Values.favorite }}
  drink: {{ .drink | default "tea" | quote }}
  food: {{ .food | upper | quote }}
  {{- end }}

Sinon nous aurions écrit :

  drink: {{ .Values.favorite.drink | default "tea" | quote }}
  food: {{ .Values.favorite.food | upper | quote }}

Les Boucles

On utilise range et non pas for ou each. Prenons l’exemple suivant. On a un fichier de valeur :

pizzaToppings:
  - mushrooms
  - cheese
  - peppers
  - onions

Si on doit boucler sur les valeurs du tableau pizzaToppings on écrit :

  toppings: |-
    {{- range .Values.pizzaToppings }}
    - {{ . | title | quote }}
    {{- end }}

Vous remarquez qu’ici on ne définit pas de nom de boucle, mais on utilise le . !

Ce qui sera converti en :

  toppings: |-
    - "Mushrooms"
    - "Cheese"
    - "Peppers"
    - "Onions"    

Valider vos templates

Comme beaucoup de langages Helm possède son propre linter. Il s’agit de la commande helm lint :

helm lint
==> Linting .
[INFO] Chart.yaml: icon is recommended

1 chart(s) linted, 0 chart(s) failed

Chercher de l’inspiration

Je vous conseille de lire des charts de la communauté. Pour cela il suffit de télécharger les charts avec la commande Helm pull :

helm search repo sonarqube
NAME                    CHART VERSION   APP VERSION     DESCRIPTION
bitnami/sonarqube       0.2.3           9.2.4           SonarQube is an open source quality management ...

helm pull bitnami/sonarqube --untar
cd sonarqube

A bientôt !


Si vous avez apprécié cet article de blog, vous pouvez m'encourager à produire plus de contenu en m'offrant un café sur   Ko-Fi  . Vous pouvez aussi passer votre prochaine commande sur amazon, sans que cela ne nous coûte plus cher, via   ce lien  . Vous pouvez aussi partager le lien sur twitter ou linkedin via les boutons ci-dessous. Je vous remercie de votre soutien


Mots clés :

devops tutorials kubernetes helm

Autres Articles


Commentaires: