Velero: sauvegardez et restaurez (proprement) vos applications Kubernetes

Rémi Verchère @ Accenture

Qui suis-je ?

👋 Rémi Verchère

💻 Cloud Native Infrastructure Consultant

DevOps, Cloud, Kubernetes, Backup ?

center

La hype du backup

center

Backup & Kubernetes

Pourquoi ?

  • Conteneurs Stateless
  • Manifest YAML facilement redéployables
  • CI/CD
  • GitOps
  • Boucle de réconciliation
  • kubectl apply || crictl run et c'est réglé ^^
  • Plein d'autres raisons approximatives...

Backup & Kubernetes

Parce que, dans la vraie vie...

  • Conteneurs Statefull
  • Migration & Protection des données
  • Reprise après sinistre
  • Ressources en prod pas "exactement" les mêmes
  • L'erreur est humaine 👾
$ kubectl delete namespace la-prod

Backup & Kubernetes

Velero - Généralités

Un peu d'histoire

  • Heptio Ark, "open-sourcé" mi 2017
  • Racheté par VMware fin 2018
  • Intégré à l'offre Tanzu (TMC)
  • Actuellement en version 1.9.3
$ git log --reverse
commit 2fe501f527a88ea292ca3dde80992ec60b388dda
Author: Andy Goldstein <andy.goldstein@gmail.com>
Date:   Wed Aug 2 13:27:17 2017 -0400

Velero - Généralités

Fonctionnalités principales

  • Sauvegarde des ressources d'un cluster et les restaure en cas de perte.
  • Migration des ressources d'un cluster à un autre.
  • Réplication d'un cluster de production vers des clusters de dev et test.
  • Custom Ressources qui définissent quoi sauvegarder, comment et quand (selector, schedule)

Composants

  • Serveur qui tourne sur votre cluster
  • Client en ligne de commande qui s'exécute localement

Velero - Architecture

Velero - Installation

Cluster: Chart Helm

$ helm repo add vmware-tanzu https://vmware-tanzu.github.io/helm-charts && helm repo update
$ helm install velero vmware-tanzu/velero --create-namespace -n velero
Release "velero" has been installed. Happy Helming!

Poste client: CLI

$ asdf plugin add velero && asdf install velero 1.9.1 && asdf global velero 1.9.1
$ velero version
Client:
        Version: v1.9.1
        Git commit: e4c84b7b3d603ba646364d5571c69a6443719bf2
Server:
        Version: v1.9.1
Aurélie Vache - Understanding Kubernetes in a visual way
Aurélie Vache - Understanding Kubernetes in a visual way

Mise en situation : la prod

Il était une fois la prod "de confiance"

Contexte 🇫🇷

  • Plusieurs clusters Kubernetes managés OVHcloud & Scaleway
    • OVHcloud: Storage object Openstack Swift & block Cinder
    • Scaleway: Storage object S3 🏠 & block 🏠 🤷
  • Outils majoritairement Open Source
  • Applications hétérogènes
  • Backup sur stockage S3, OVHcloud ou Scaleway, régions ❗🟰
  • Besoin de répliquer les applications sur cluster de test
  • Possibilité de PRA sur un autre cloud provider

Exemple d'application

Nextcloud

  • Chart Helm
  • 1 déploiement de l'application
  • 1 base Mariadb
  • 1 cache Redis (master / replicas)
  • Ingress, Cronjobs, etc.

https://github.com/nextcloud/helm

Exemple d'application

Nextcloud

$ kubectl get deployment,statefulset,cronjob,service,ingress
NAME                        READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/nextcloud   1/1     1            1           28d

NAME                                        READY   AGE
statefulset.apps/nextcloud-mariadb          1/1     28d
statefulset.apps/nextcloud-redis-master     1/1     28d
statefulset.apps/nextcloud-redis-replicas   1/1     28d

NAME                           SCHEDULE       SUSPEND   ACTIVE   LAST SCHEDULE   AGE
cronjob.batch/nextcloud-cron   */15 * * * *   False     0        62s             28d

NAME                               TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
service/nextcloud                  ClusterIP   10.3.124.75    <none>        8080/TCP   28d
service/nextcloud-mariadb          ClusterIP   10.3.145.33    <none>        3306/TCP   28d
service/nextcloud-redis-headless   ClusterIP   None           <none>        6379/TCP   28d
service/nextcloud-redis-master     ClusterIP   10.3.207.110   <none>        6379/TCP   28d
service/nextcloud-redis-replicas   ClusterIP   10.3.249.214   <none>        6379/TCP   28d

NAME                                  CLASS    HOSTS                     ADDRESS                            PORTS     AGE
ingress.networking.k8s.io/nextcloud   <none>   nextcloud.demo.vrchr.fr   ip-135-125-84-204.gra.lb.ovh.net   80, 443   28d

Backup & Restore, l'aventure...

Velero - Première fois

1er backup

$ velero backup create nextcloud-backup-1 --include-namespaces nextcloud

$ velero backup describe nextcloud-backup-1
Name:         nextcloud-backup-1
Namespace:    velero
[...]
Phase:  Completed

Errors:    0
Warnings:  0
[...]
Total items to be backed up:  200
Items backed up:              200
[...]

Velero - Première fois

1er restore

$ velero restore create nextcloud-restore-1 --from-backup nextcloud-backup-1

$ velero restore nextcloud-restore-1
Name:         nextcloud-restore-1
Namespace:    velero
Labels:       <none>
Annotations:  <none>

Phase:                       Completed
Total items to be restored:  200
Items restored:              200

Velero - Première fois

1er fail

Objets kubernetes sauvés, données sacrifiées !

center

Velero Snapshots, Backups, Restore

Volumes Persistants

Velero - Sauvegarde des données

Objects Kubernetes

Les objets kubernetes sont exportés sur bucket S3

3 types de storage pour les données persistantes

  1. Snapshots des Persistent Volumes (PV) via API du Cloud Provider

  2. Snapshots des Persistent Volumes (PV) via CSI (>= version 1.4)

  1. Export volumes montés des pods sur Bucket S3 via Restic

Velero - Snapshot PV via API

 center

Velero - Snapshot PV via CSI

 center

Velero - Snapshot PV via CSI

Activation avec chart Helm

Sous réserve que votre CSI le supporte : --features=EnableCSI

# $ helm get values velero
snapshotsEnabled: true
[...]
configuration:
  [...]
  features: EnableCSI
[...]
initContainers:
  - name: velero-plugin-for-csi
    image: velero/velero-plugin-for-csi:v0.3.1
    volumeMounts:
      - mountPath: /target
        name: plugins

Velero - Snapshot PV via CSI

J'ai testé pour vous 😭

$ velero backup get                                       
NAME                            STATUS            ERRORS   WARNINGS   CREATED                          EXPIRES   STORAGE LOCATION   SELECTOR
daily-s3-20220920000000         Failed            0        0          2022-09-20 02:00:00 +0200 CEST   1d        velero-gra9        <none>
daily-s3-20220916000004         Completed         0        0          2022-09-16 02:00:04 +0200 CEST   1d        velero-gra9        <none>
daily-s3-20220915000003         Completed         0        0          2022-09-15 02:00:03 +0200 CEST   5h        velero-gra9        <none>
daily-snapshot-20220920010027   Failed            0        0          2022-09-20 03:00:27 +0200 CEST   1d        velero-gra9        <none>
daily-snapshot-20220915010003   Completed         0        0          2022-09-15 03:00:03 +0200 CEST   6h        velero-gra9        <none>
$ kubectl logs -f velero-5b86bc8db-5p8md
time="2022-10-03T19:32:22Z" level=info msg="Waiting for volumesnapshotcontents
snapcontent-bc5660db-b0a4-4cc5-98cb-a071d5473dcb to have snapshot handle. Retrying in 5s" [...]
time="2022-10-03T19:32:27Z" level=info msg="Waiting for volumesnapshotcontents
snapcontent-bc5660db-b0a4-4cc5-98cb-a071d5473dcb to have snapshot handle. Retrying in 5s" [...]

Velero - Snapshot PV via CSI

J'ai insisté, avec les équipes du CSP 💪

$ velero backup get                                       
NAME                            STATUS      ERRORS   WARNINGS   CREATED                          EXPIRES   STORAGE LOCATION   SELECTOR
daily-s3-20221004182135         Completed   0        0          2022-10-04 20:21:35 +0200 CEST   1d        velero-gra9        <none>
daily-s3-20221004181435         Completed   0        0          2022-10-04 20:14:35 +0200 CEST   1d        velero-gra9        <none>
[...]
daily-snapshot-20221004190535   Completed   0        0          2022-10-04 21:05:35 +0200 CEST   1d        velero-gra9        <none>
daily-snapshot-20221004190035   Completed   0        0          2022-10-04 21:00:35 +0200 CEST   1d        velero-gra9        <none>
daily-snapshot-20221004185535   Completed   0        0          2022-10-04 20:55:35 +0200 CEST   1d        velero-gra9        <none>
daily-snapshot-20221004185035   Completed   0        0          2022-10-04 20:50:35 +0200 CEST   1d        velero-gra9        <none>
[...]

⚠️ Aux limites configurées côté storage provider

⚠️ N'est pas considéré comme stockage pérenne

Velero - Backup via Restic

 center

Velero - Backup via Restic

Activation avec chart Helm

# $ helm get values velero
deployRestic: true # Deploiement des Daemonsets sur les nodes
[...]
configuration:
  [...]
  defaultVolumesToRestic: true
[...]
initContainers:
  # Deja défini pour l'export des objets k8s
  - name: velero-plugin-for-aws
    image: velero/velero-plugin-for-aws:v1.5.1
    volumeMounts:
      - mountPath: /target
        name: plugins

Velero - Backup via Restic

2 modes de backup

  1. Opt-in: rien par défaut, on sélectionne ce qu'on veut sauvegarder
podAnnotations:
  ## Velero annotations
  backup.velero.io/backup-volumes-includes: nextcloud-main
  1. Opt-out: tout par défaut, on sélectionne ce qu'on veut exclure
    Par défaut depuis Velero 1.5
podAnnotations:
  ## Velero annotations
  backup.velero.io/backup-volumes-excludes: redis-data

Velero - Restore

  • Process de restore "presque" l'inverse du backup, mais en plus :

    • Validation des ressources seront compatibles

    • Ordre de restauration des ressources

    • Possibilité de changer de namespace --namespace-mappings

    • Possibilité de changer de storage classvelero.io/change-storage-class

    • Mode "incrémental" --existing-resource-policy

Données (PV/PVC)

  • Si Snapshot API ou CSI: Restore du volume, puis remapping / reference

  • Si Restic: Création d'un nouveau volume, réhydratation des données

Velero - Restore avec Restic

center

Snapshots ou Restic ?

Velero - Snapshots ou Restic ?

Snapshot Restic
Non atomique Non atomique
Même média* Média différent
Plus rapide Plus lent
Proche Distant (⚠️ coût transferts)
Non chiffré (⚠️ Storage Class) Chiffré
Même Cloud Provider (⚠️ Zone) Cloud Provider Agnostic

Velero - Snapshots ou Restic ?

Rappels de bonnes pratiques

center

voir même 3-2-1-1

Velero - Snapshots ou Restic ?

Les 2 ! Mais...

  • Bien configurer l'opt-in / opt-out côté Restic (defaultVolumesToRestic)

  • Bien configurer includes / excludes des volumes

  • Bien configurer l'utilisation de snapshots (--snapshot-volumes)

⚠️ Certaines combinaisons ne fonctionnent pas ensemble

  • Si opt-in && includes : pas de snapshots des volumes
time="2022-10-02T00:01:04Z" level=info msg="Skipping snapshot of persistent volume \
because volume is being backed up with restic."

Velero - Snapshots ou Restic ?

Mes recommendations

  • opt-out par défaut

  • 1 schedule classique: backup S3, avec excludes pour certains volumes, sans snapshots (snapshotVolumes: false)

  • 1 schedule snapshots: defaultVolumesToRestic: false (opt-in)

  • Dans le doute : Restic

Focus sur Restic

Restic - Généralités

Restic is a backup program that is fast, efficient and secure. It supports the three major operating systems (Linux, macOS, Windows) and a few smaller ones (FreeBSD, OpenBSD).

  • Simple

  • Multi backend (Local, SFTP, S3, ...)

  • Rapide (Backup Incrémental & Déduplication)

  • Secure

$ git log --reverse
commit c54facf66be1c4e137121f36b300543f6673ea7c
Author: Alexander Neumann <alexander@bumpern.de>
Date:   Sun Apr 6 12:22:58 2014 +0200

Restic - Incrémental

Backup 👍

center

Restore 👎

Velero won’t restore a resource if a that resource is scaled to 0 and already exists in the cluster.

Restic - Restauration partielle

Exemple de besoin: récupérer juste 1 fichier effacé par un humain 👾

Comment le récupérer depuis le repository restic ?

$ export AWS_SECRET_KEY_ID="********"
$ export AWS_SECRET_ACCESS_KEY="********"

$ restic snapshots -r s3:https://s3.fr-par-.scw.cloud/backup-velero-nextcloud/restic/nextcloud
enter password for repository:

Restic - Securité

Secure: Restic uses cryptography to guarantee confidentiality and integrity of your data. The location the backup data is stored is assumed not to be a trusted environment (e.g. a shared space where others like system administrators are able to access your backups). Restic is built to secure your data against such attackers.

Those of you familiar with restic may know that it encrypts all of its data. Velero uses a static, common encryption key for all Restic repositories it creates

$ kubectl get secret velero-restic-credentials -o jsonpath="{.data.repository-password}" \
| base64 -d 

https://github.com/vmware-tanzu/velero/pull/4961

Restic - Atomicité

Note that cluster backups are not strictly atomic. If Kubernetes objects are being created or edited at the time of backup, they might not be included in the backup. The odds of capturing inconsistent information are low, but it is possible

Idée (à la ***)

  • Scale à 0 juste avant le backup, au moins pas d'écritures !

Velero’s Restic integration backs up data from volumes by accessing the node’s filesystem, on which the pod is running. For this reason, Velero’s Restic integration can only backup volumes that are mounted by a pod and not directly from the PVC

Restic -> Kopia ?

1 point sur les hooks...

Velero - Hooks

  • Pre & Post Hooks

  • Pod Annotation ou Backup Spec

    • pre.hook.backup.velero.io/container

    • pre.hook.backup.velero.io/command

    • pre.hook.backup.velero.io/on-error

    • pre.hook.backup.velero.io/timeout

⚠️ commande doit être disponible, sinon utilisation de sidecar container

Et 1 autre sur les Bases de Données !

Velero - BDD & Atomicité

  1. Utilisation des hooks avec fsfreeze, sqldump
    ⚠️ blocage de la BDD le temps du backup

  2. Utilisation de BDD réplica en read-only
    Hook de backup sur le réplica, à l'ancienne

  1. En dehors du cluster Kubernetes

  2. Utilisation de BDD managée 🙆

  • Blueprints de backups ? Voir Kanister

🌩️ Cas de migration cloud 🌩️

Velero - Cas d'une migration cloud

center

Velero - Cas d'une migration cloud

  • Quelques points de vigilance

    • Bascule DNS (CQFD)

    • Version de Kubernetes & API différentes : "API Group Versions Feature"

    • Certaines ressources non nécessaires : "--exclude-resources"

    $ velero restore --exclude-resources CustomResourceDefinition,CertificateRequest,Order
    
    • Storage différent : "Changing PV/PVC Storage Class"
    kind: ConfigMap
    metadata:
      labels:
        velero.io/change-storage-class: RestoreItemAction
    data:
      <old-storage-class>: <new-storage-class>
    

Velero - Cas d'une migration cloud

  • Temps de restauration assez long (si plusieurs centaines de Go)
    Comment optimiser ce temps de "bascule" ?
    • Flag --existing-resource-policy pour restore "incrémental" ?

center

Velero - Cas d'une migration cloud

  • Temps de restauration assez long (si plusieurs centaines de Go)
    Comment optimiser ce temps de "bascule" ?
    • Réponse: rsync !

center

OpenShift MTC

center

OpenShift MTC

The Migration Toolkit for Containers (MTC) enables you to migrate stateful application workloads between OpenShift Container Platform 4 clusters at the granularity of a namespace.

The file system copy method uses Restic for indirect migration or Rsync for direct volume migration.

https://docs.openshift.com/container-platform/4.11/migration_toolkit_for_containers/about-mtc.html

✨ Quelques fails ✨

Velero - Backup qui s'est mal passé

  1. Storage Location
    Oubli de préciser, Velero ne sait pas où sauvegarder
spec:
    storageLocation: ovh-velero-storage-location
  1. Timeout
    Sur gros volumes, Restic part en timeout
configuration:
  resticTimeout: 2h

Velero - Backup qui s'est mal passé

  1. Objets S3
    Backup trop gros (beaucoup de namespaces)

center

Velero - Backup qui s'est mal passé

  1. Restic OOMKilled !
    Augmenter les requests & limits
    GOGC ! (https://tip.golang.org/doc/gc-guide)
configuration:
  extraEnvVars:
    GOGC: 10
resources:
  limits:
    cpu: null
    memory: 2Gi
restic:
  resources:
    limits:
      cpu: "2"
      memory: 4Gi

Velero - Backup qui s'est mal passé

  1. Restic Locked backend
    Gestion multi clusters : attention aux storage location RW
stderr=unable to create lock in backend: repository is already locked exclusively by PID 11108
on velero-76cfbd7858-5fr8t by nonroot (UID 65532, GID 65532)
lock was created at 2022-09-30 04:00:05 (1m5.593684965s ago)
storage ID 44a72b6f the `unlock` command can be used to remove stale locks
exit status 1" error.file="/go/src/github.com/vmware-tanzu/velero/pkg/restic/backupper.go:184"
error.function="github.com/vmware-tanzu/velero/pkg/restic.(*backupper).BackupPodVolumes"
logSource="pkg/backup/backup.go:417"
name=nextcloud-mariadb-0
$ kubectl patch backupstoragelocation <STORAGE LOCATION NAME> \
    --namespace velero \
    --type merge \
    --patch '{"spec":{"accessMode":"ReadOnly"}}'

Velero - Backup qui s'est mal passé

  1. Restic, NFS & ReadWriteMany

5.1. Backup Serveur NFS

5.2. Backup PV Pod 1

5.3. Backup PV Pod 2

5.4. Backup PV Pod N

 

msg="Pod volume uses a persistent volume claim which has already been backed up \
with restic from another pod, skipping."

Velero - Restore qui s'est mal passé

  1. Restic Annotations
    Oubli d'annoter 1 volume lors du backup 🤭

  2. Selector
    Tout n'est pas pris en compte

$ kubectl get all -l app=mariadb-database
NAME                      READY   STATUS    RESTARTS   AGE
pod/mariadb-database-0   1/1     Running   0          93d

Velero - Restore qui s'est mal passé

  1. Base de données corrompue !
    Pas d'atomicité : Utilisation des pre-hooks & post-hooks
    Sur Backup, mais aussi sur Restore
metadata:
      annotations:
        backup.velero.io/backup-volumes: data
        pre.hook.backup.velero.io/command: '["/bin/bash", "-c",
          "mkdir -p /bitnami/mariadb/backups \
          && mysqldump -u $MARIADB_USER -p$MARIADB_PASSWORD \
          $MARIADB_DATABASE > /bitnami/mariadb/backups/nextcloud.dump"]'

Faut-il aussi pouvoir récupérer les dumps (tar)...

$ kubectl cp nextcloud-mariadb-0:~/nextcloud.dump ./nextcloud.dump                                                         
command terminated with exit code 126

Velero - Restore qui s'est mal passé

  1. Statefulset disparu ?!
    Bug avec velero.io/change-storage-class
$ velero restore create nextcloud-migration-22091501 --from-backup nextcloud-22091501
$ velero restore describe nextcloud-migration-22091501
[...]
Phase: PartiallyFailed (run 'velero restore logs nextcloud-migration-22091501' for more information)
Total items to be restored:  105
Items restored:              105

Started:    2022-09-15 21:02:46 +0200 CEST
Completed:  2022-09-15 22:13:04 +0200 CEST

[...]

Errors:
  Namespaces:
    nextcloud:  error preparing statefulsets.apps/nextcloud/nextcloud-mariadb: rpc error: code = Aborted desc = plugin panicked: \
                runtime error: invalid memory address or nil pointer dereference
                error preparing statefulsets.apps/nextcloud/nextcloud-redis-master: rpc error: code = Aborted desc = plugin panicked: \
                runtime error: invalid memory address or nil pointer dereference
[...]

Velero, encore plus !

Monitoring

metrics:
  serviceMonitor:
    enabled: true
# HELP velero_backup_attempt_total Total number of attempted backups
# HELP velero_backup_deletion_success_total Total number of successful backup deletions
# HELP velero_backup_duration_seconds Time taken to complete backup, in seconds
# HELP velero_backup_failure_total Total number of failed backups
# HELP velero_backup_items_errors Total number of errors encountered during backup
# HELP velero_backup_validation_failure_total Total number of validation failed backups
# HELP velero_csi_snapshot_attempt_total Total number of CSI attempted volume snapshots
successful volume snapshots
# HELP velero_restore_attempt_total Total number of attempted restores
# HELP velero_restore_total Current number of existent restores
# HELP velero_restore_validation_failed_total Total number of failed restores failing validations
# HELP velero_volume_snapshot_attempt_total Total number of attempted volume snapshots

Monitoring

center

Alerting !

center

Backup Policies

Kyverno

center

https://kyverno.io/policies/?policytypes=Velero

Derniers conseils

  • Lisez la doc Velero !
  • Vérifiez l'état de vos backups
  • Validez vos restaurations régulièrement
  • On n'est pas obligé de tout mettre dans Kubernetes, cf services managés 🙃

Ils utilisent Velero

  • Openshift "OADP" & "MTC"
  • VMware "TMC"
  • Accenture 😬
  • Vous ? Parlons-en !

Merci !

Rémi Verchère @ Accenture

Mutation Wehbook

Notes: Je hais les backups, mais je hais encore plus perdre des données. Depuis quelques temps j'administre des cluster k8s & applications qui tournent dessus. On m'a présenté Velero comme l'outil magique, mais j'y crois pas trop ;) J'ai pas trouvé bcp de ressources FR sur le sujet, d'où ce talk pour partager avec vous mes galères !

Pas de sauvegarde d'ETCD / Control planes, on reste sur l'applicatif

1. The Velero client makes a call to the Kubernetes API server to create a Backup object. 2. The BackupController notices the new Backup object and performs validation. 3. The BackupController begins the backup process. It collects the data to back up by querying the API server for resources. 4. The BackupController makes a call to the object storage service – for example, AWS S3 – to upload the backup file.

CSI: Container Storage Interface

⚠️ `fsfreeze` & montages NFS

# Velero - Cas d'une migration cloud - Installation de Velero sur les 2 clusters. - Configuration des mêmes Storage Location (Read Only) - Backup Velero Cluster A, sur Bucket S3 avec Restic - Restore sur Cluster B, depuis même Bucket S3 avec Restic - Facile ! Ou pas...

Utilisation de nfs-ganesha-server-and-external-provisioner

--- # Une config de chart helm # qui marche *à peu près* --- # Velero - Config qui marche *à peu près* ```yaml # *** helm get values velero initContainers: - name: velero-plugin-for-aws image: velero/velero-plugin-for-aws:v1.5.1 volumeMounts: - mountPath: /target name: plugins - name: velero-plugin-for-csi image: velero/velero-plugin-for-csi:v0.3.1 volumeMounts: - mountPath: /target name: plugins ``` --- # Velero - Config qui marche *à peu près* ```yaml configuration: provider: aws backupStorageLocation: name: ovh-velero-storage-location bucket: ovh-velero-storage-location default: true config: region: sbg s3ForcePathStyle: true s3Url: https://s3.sbg.cloud.ovh.net volumeSnapshotLocation: config: enableSharedConfig: true region: gra ``` --- # Velero - Config qui marche *à peu près* ```yaml configuration: extraEnvVars: GOGC: 10 features: EnableCSI defaultVolumesToRestic: true resticTimeout: 2h ``` --- # Velero - Config qui marche *à peu près* ```yaml resources: limits: cpu: memory: "2Gi" snapshotsEnabled: true deployRestic: true restic: resources: limits: cpu: "2" memory: "4Gi" ``` --- # Velero - Config qui marche *à peu près* ```yaml credentials: secretContents: cloud: | [default] aws_access_key_id="Utilisez plutôt des secrets, bon sang !" aws_secret_access_key="Si votre RSSI voit ça vous êtes mal..." metrics: serviceMonitor: enabled: true additionalLabels: release: prom ``` --- # Velero - Sauvegarde Nexcloud ## Schedule S3 Rendez-vous sur mon compte GitLab ;) ## Schedule Snapshot Rendez-vous sur mon compte GitLab ;) ## Annotations chart Helm Rendez-vous sur mon compte GitLab ;)