Certaines applications déployées dans un cluster Kubernetes utilisent des volumes persistants (pvc). C’est de celles-là dont nous allons parler dans cet article, et plus précisément du moyen de sauvegarder ces volumes afin d’éviter une perte de données.
Par Maud Laurent, administratrice système @ Objectif Libre
Public visé : Administrateurs Kubernetes
Kubernetes : sauvegarder ses applications Stateful
Pourquoi ce concentrer sur les applications Stateful ?
Parce que les applications Stateless sont uniquement construites avec des fichiers yaml, qui peuvent être aisément archivés, versionnés dans un dépôt comme git, et déployés automatiquement par un mécanisme de CI/CD. Aucune donnée n’est sauvegardée au sein de cette application. Le peu de configurations nécessaires à son fonctionnement tiennent dans une ConfigMap ou des Secrets.
A contrario, les applications Stateful génèrent des données, elles sont souvent rattachées à des volumes et ce sont ces volumes qui contiennent toutes les informations dont elles ont besoin ou dont d’autres applications ont besoin. Et c’est elles que nous devont sauvegarder !
Les outils pour faire des sauvegardes
Pour sauvegarder des volumes depuis Kubernetes, il existe actuellement deux applications : Velero et Stash.
Velero est un outil de sauvegarde qui ne se contente pas de sauvegarder les volumes : il permet de sauvegarder la totalité de son cluster (pods, services, volumes…) avec tout un système de filtres par labels ou objets Kubernetes.
Stash est un outil se concentrant uniquement sur la sauvegarde de volume.
Ces deux applications utilisent le même support pour gérer les sauvegardes : Restic. Restic est un programme de gestion de sauvegarde facilitant l’enregistrement et la restauration des données. Il chiffre les données sauvegardées pour garantir la confidentialité et l’intégrité de ces dernières.
Restic is built to secure your data against such attackers, by encrypting it with AES-256 in counter mode and authenticating it using Poly1305-AES. (source: https://restic.net/)
Notre cas de test
Pour expliciter, prenons un exemple et sauvegardons avec ces deux outils notre application. L’application exécutée est un simple serveur nginx publié au travers d’un service en NodePort. Les logs du serveur sont enregistrés dans un volume persistant.
Le cluster utilisé est un cluster « 1 master 2 workers », avec une solution de stockage Ceph objet (s3) et bloc installée à l’aide de Rook.
L’application Nginx est déployée à l’aide des yaml suivants : un yaml de déploiement, un yaml pour le service et un yaml pour le volume. Ces fichiers sont disponibles ci-dessous.
La commande suivante crée le namespace de test, et lance les yamls de déploiement.
kubectl create ns demo-app && kubectl apply -n demo-app -f pvc-log-1Gi-no-ns.yml -f deployment-no-ns.yml -f service-no-ns.yml
Ci-dessous, le fichier yaml utilisé pour créer le déploiement de l’application sur Kubernetes.
apiVersion: apps/v1 kind: Deployment metadata: labels: app: demo-app name: demo-app spec: replicas: 1 selector: matchLabels: app: demo-app template: metadata: labels: app: demo-app name: web-app spec: containers: - args: name: nginx-app image: nginx imagePullPolicy: IfNotPresent volumeMounts: - mountPath: /var/log/nginx/ name: log-data restartPolicy: Always volumes: - name: log-data persistentVolumeClaim: claimName: demo-app-log
Ci-dessous, le fichier yaml utilisé pour créer le volume persistant (pvc) sur Kubernetes.
apiVersion: v1 kind: PersistentVolumeClaim metadata: name: demo-app-log spec: storageClassName: rook-ceph-block accessModes: - ReadWriteOnce resources: requests: storage: 1Gi
Ci-dessous, le fichier yaml utilisé pour créer le service sur Kubernetes.
apiVersion: v1 kind: Service metadata: name: demo-app labels: app: demo-app spec: selector: app: demo-app ports: - port: 80 protocol: TCP targetPort: 80 type: NodePort
Pour générer un peu de logs Nginx, nous allons utiliser cette commande watch -n 30 curl IP_Master:NodePort
et la laisser tourner dans un coin.
Notre volume stocke les logs de l’application Nginx. Avec la commande kubectl suivante : kubectl exec -it -n demo-app demo-app-5b955c984d-x7pqf -c nginx-app -- wc -l /var/log/nginx/access.log
nous pouvons compter le nombre de lignes dans notre fichier access.log et ainsi voir les accès à notre page web. Nous vérifierons lors des restaurations leur bon fonctionnement avec cette commande.
Velero
Installation
- Télécharger la commande velero (la version utilisée est la 1.10) : https://github.com/heptio/velero/releases/tag/v1.1.0
- Créer un fichier d’accès au stockage s3 :
[default] aws_access_key_id = Your_Access_Key aws_secret_access_key = Your_Secret_Key
- Installer Velero avec la ligne de commande (ajouter
--dry-run -o yaml
pour voir le yaml appliqué) :
velero install --provider aws \ --bucket velero-backup \ --secret-file ./velero-creds \ --use-volume-snapshots=true \ --backup-location-config region=":default-placement",s3ForcePathStyle="true",s3Url=http://rook-ceph-rgw-my-store.rook-ceph.svc.cluster.local \ --snapshot-location-config region=":default-placement" \ --use-restic
Ou avec Helm :
- Créer un secret avec les informations d’accès au stockage
kubectl create secret generic s3-velero-creds -n velero --from-file cloud=velero-creds
- Créer un fichier de paramètres values.yml
configuration: provider: aws backupStorageLocation: name: aws bucket: velero-backup config: region: ":default-placement" s3ForcePathStyle: true s3Url: http://rook-ceph-rgw-my-store.rook-ceph.svc.cluster.local credentials: existingSecret: s3-velero-creds deployRestic: true
- Lancer l’installation avec helm
helm-3 install -f values.yml velero --namespace velero --version 2.1.3 stable/velero
Lancement d’une sauvegarde
Pour sauvegarder nos volumes avec Restic, Velero demande d’annoter les pods utilisant ces volumes kubectl -n demo-app annotate pod/demo-app-57f87559b6-jhdfk backup.velero.io/backup-volumes=log-data
. Cette annotation peut aussi être mise directement, dans le yaml de déploiement de l’application, dans la partie spec.template.metadata.annotations
.
Les sauvegardes peuvent être créées avec la commande velero backup create demo-backup --include-namespaces demo-app
ou en appliquant un fichier yaml. (https://velero.io/docs/v1.1.0/api-types/backup/).
apiVersion: velero.io/v1 kind: Backup metadata: name: demo-app-backup namespace: velero # must be match velero server namespace spec: includedNamespaces: - demo-app ttl: 24h0m0s # default 720h0m0s storageLocation: default # backup storage location volumeSnapshotLocations: - default
Avec ce fichier, une seule sauvegarde va être faite, mais il est possible de les programmer avec la commande velero schedule create demo-app-schedule-backup --schedule="@every 5m" --include-namespace demo-app --ttl 0h30m00s
ou le yaml ci-dessous.
apiVersion: velero.io/v1 kind: Schedule metadata: name: demo-app-schedule-backup namespace: velero spec: schedule: '@every 5m' template: includedNamespaces: - demo-app ttl: 0h30m00s
Il est possible de créer plusieurs localisations pour le stockage des sauvegardes et ainsi de choisir où sauvegarder les données.
Pour supprimer une sauvegarde dans velero, il est préférable d’utiliser la ligne de commande velero
que de faire une suppression avec kubectl
de la resource backup.
Restauration de la sauvegarde
Avant de restaurer un des snapshots enregistrés précédement, nous allons supprimer le namespace de déploiement kubectl delete ns demo-app
.
La liste des snapshots est disponible avec la commande velero backup get
.
On peut créer une restauration avec la commande velero restore create --from-backup demo-app-backup
ou avec un fichier yaml de ce type.
apiVersion: velero.io/v1 kind: Restore metadata: name: demo-app-restore namespace: velero spec: backupName: demo-app-backup excludeResources: - nodes - events - events.events.k8s.io - backups.velero.io - restores.velero.io - resticrepositories.velero.io
Comme pour les sauvegardes, il est possible de voir l’état de la restauration avec la commande velero restore get
et d’avoir plus d’information avec les mots clés describe
ou logs
.
Une fois la restauration terminée, nous avons de nouveau toutes nos données disponibles. Pour le vérifier, on peut exécuter la commande kubectl get all,pvc -n demo-app
.
Dans mon cas, le résultat est le suivant :
NAME READY STATUS RESTARTS AGE pod/demo-app-5b955c984d-x7pqf 2/2 Running 0 109s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/demo-app NodePort 10.233.33.213 80:31010/TCP 105s NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/demo-app 1/1 1 1 108s NAME DESIRED CURRENT READY AGE replicaset.apps/demo-app-5b955c984d 1 1 1 108s NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE persistentvolumeclaim/demo-app-log Bound pvc-7f302738-15ad-4294-8e8e-6407e42f8bf3 1Gi RWO rook-ceph-block 110s
On observe que la restauration d’une sauvegarde avec Velero garde le nom exact du pod, que la restauration se fasse dans un namespace différent ou dans le même namespace que le précédent déploiement.
Après la restauration, en exécutant à nouveau la commande kubectl exec -it -n demo-app demo-app-5b955c984d-x7pqf -c nginx-app -- wc -l /var/log/nginx/access.log
on peut voir que notre fichier comporte déjà plusieurs lignes alors qu’aucun accès n’a encore été fait sur le serveur web.
Stash
Installation
Stash peut s’installer en utilisant Helm en suivant les indications suivantes :
kubectl create ns stash helm-3 repo add appscode https://charts.appscode.com/stable/ helm-3 repo update helm-3 repo install --version 0.8.3 stash --namespace stash appscode/stash
Lancement d’une sauvegarde
L’intégralité de l’archivage de nos sauvegardes se fait sur du stockage s3. Stash a donc besoin d’un secret avec les informations d’authentification à ce stockage. En plus des identifiants, Stash demande un mot de passe pour Restic. Restic enregistre les données sur le stockage s3 en les chiffrant avec ce mot de passe. Ce secret est enregistré dans le namespace du projet à sauvegarder, ce qui permet de spécifier pour chaque projet/namespace un accès s3 et un secret Restic.
apiVersion: v1 kind: Secret metadata: name: s3-secret namespace: demo-app data: AWS_ACCESS_KEY_ID: czNfc3RvcmFnZV9rZXlfaWQ= # s3_storage_key_id AWS_SECRET_ACCESS_KEY: czNfc3RvcmFnZV9rZXlfc2VjcmV0 # s3_storage_key_secret RESTIC_PASSWORD: U3VwM3JSZXN0aWNQd2Q= # Sup3rResticPwd
Stash offre la mise en place de deux types de sauvegarde :
- une sauvegarde à chaud (en ligne) : Stash ajoute un sidecar conteneur au pod actuel du déploiement. Ce sidecar monte le volume en lecture seule (RO) et effectue les sauvegardes pendant que l’application tourne.
- une sauvegarde à froid (hors ligne) : Stash ajoute un init conteneur au pod et crée une cronjob Kubernetes. Cette cronjob lance les sauvegardes, le pod va être recréé à chaque sauvegarde pour lancer l’exécution de l’init conteneur.
Dans notre cas, nous allons effectuer une sauvegarde en ligne en appliquant le yaml ci-dessous.
apiVersion: stash.appscode.com/v1alpha1 kind: Restic metadata: name: rook-restic namespace: demo-app spec: type: online # default value selector: matchLabels: app: demo-app # Must match with the label of pod we want to backup. fileGroups: - path: /var/log/nginx retentionPolicyName: 'keep-last-5' backend: s3: endpoint: 'http://rook-ceph-rgw-my-store.rook-ceph.svc.cluster.local' bucket: stash-backup # Give a name of the bucket where you want to backup. prefix: demo-app # A prefix for the directory where repository will be created.(optional). storageSecretName: s3-secret schedule: '@every 5m' volumeMounts: - mountPath: /var/log/nginx name: log-data # name of volume set in deployment not claimName retentionPolicies: - name: 'keep-last-5' keepLast: 5 prune: true
Quand ce yaml est appliqué, le/les pods sont recréés pour ajouter le sidecar permettant la sauvegarde. Une fois la configuration appliquée, le premier snapshot se lance lors du prochain passage programmé (ici 5 minutes après le déploiement). Les snapshots peuvent être mis sur pause à tout moment en patchant l’objet restic
créé à l’aide de la commande kubectl patch restic -n demo-app rook-restic --type="merge" --patch='{"spec": {"paused": true}}'
. Les snapshots créés par Stash sont visibles avec la commande kubectl get -n demo-app snapshots.repositories.stash.appscode.com
.
Restauration de la sauvegarde
Avant de restaurer nos données, nous allons commencer par supprimer notre déploiement.
Stash a besoin de deux éléments pour restaurer une sauvegarde : le repository et le secret du stockage. Ces deux éléments se trouvent dans le namespace du projet à sauvegarder.
La suppression du namespace entraine la perte de ces deux données. Il est cependant possible de les recréer en ré-appliquant le secret et une configuration de repository pour pouvoir lancer la restauration d’une sauvegarde.
Dans notre cas, nous allons uniquement supprimer notre déploiement, le volume ainsi que notre ressource restic.
kubectl delete -n demo-app deployment demo-app kubectl delete -n demo-app pvc demo-app-log kubectl delete -n demo-app restics.stash.appscode.com rook-restic
Pour restaurer le système, nous allons d’abord créer un nouveau volume vide (de taille supérieure ou égale au précédent).
kubectl apply -f pvc-recovery.yml -n demo-app
kubectl get pvc -n demo-app persistentvolumeclaim/demo-app-log-recovery Bound pvc-5a0ba64e-7cf8-49d8-a5a9-ac071160da11 2Gi RWO rook-ceph-block 4s
Ensuite, nous allons lancer une restauration. Ce fichier yaml va aller copier les données de la sauvegarde vers le volume précédemment créé. Stash crée un job Kubernetes qui va monter le volume et copier les données à l’intérieur.
apiVersion: stash.appscode.com/v1alpha1 kind: Recovery metadata: name: s3-recovery namespace: demo-app spec: repository: name: deployment.demo-app namespace: demo-app snapshot: deployment.demo-app-70b545c2 # snapshot name to restore paths: # path want to restore - /var/log/nginx recoveredVolumes: # where we want to restore - mountPath: /var/log/nginx persistentVolumeClaim: claimName: demo-app-log-recovery
kubectl get recoveries.stash.appscode.com -n demo-app -w NAME REPOSITORY-NAMESPACE REPOSITORY-NAME SNAPSHOT PHASE AGE s3-recovery demo-app deployment.demo-app deployment.demo-app-70b545c2 Running 18s s3-recovery demo-app deployment.demo-app deployment.demo-app-70b545c2 Succeeded 39s
Une fois que la restauration est finie sans erreur, il ne reste plus qu’à appliquer le déploiement précédent en spécifiant le nom du volume restauré.
Alors, Velero ou Stash pour sauvegarder ses volumes persistants ?
Velero
- Utilise un seul et unique mot de passe pour tous les volumes sauvegardés. Il faut donc être vigilant et restreindre l’accès aux emplacements des sauvegardes.
- Tous les éléments servant aux sauvegardes ou à la restauration sont enregitrés dans le namespace de Velero. Si un namespace est supprimé, la restauration n’est pas compromise pour autant.
- Fonctionne avec un système de plugins et hook pour customiser/améliorer les sauvegardes.
- Offre des métriques pour le monitoring des sauvegardes.
- La sauvegarde de volume est une extension au système total de sauvegarde qu’offre Velero.
- Backends supportés : S3 ( AWS, Ceph, Minio…), ABS, GCS. Volume Snapshot providers: AWS EBS, AMD, GCED, Restic, Portworx, DigitalOcean, OpenEBS, AlibabaCloud.
- Fonctionne très bien pour la migration ou la sauvegarde de projets, mais les droits sont difficiles à gérer : favoriser l’utilisation uniquement par des admins.
Stash
- Le mot de passe de chiffrement pour Restic est définissable pour chaque namespace du cluster.
- Les données pour restaurer les sauvegardes sont enregistrées dans le même namespace que le projet à sauvegarder. De ce fait, en cas de suppression du namespace, la restauration est un peu plus complexe, mais reste possible.
- Offre des métriques pour le monitoring des sauvegardes.
- Se concentre sur les sauvegardes de volumes (pvc).
- Backends supportés : S3 (AWS, Ceph, Minio…), ABS, GCS, Openstack Swift, Backblaze B2, Local.
- Peut être utilisé pour facilement redimensionner un volume persistant.