KUBERNETES : SAUVEGARDER SES APPLICATIONS A L’ETAT, OU STATEFUL

Certaines applications déployées dans une grappe de serveurs ou cluster Kubernetes utilisent des volumes persistants (pvc). C’est ce dont nous allons parler dans cet article et notamment du moyen de sauvegarder ces volumes afin d’éviter une perte de données.

Par Maud Laurent, administratrice système @ Groupe LINKBYNET

Pourquoi se concentrer sur les applications à état, ou Stateful ?

Parce que les applications sans état, ou 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 l’inverse, 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 devront 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 permet de sauvegarder les volumes ainsi que 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 volumes.
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

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 journaux, ou 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 objets (S3) et blocs 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 des journaux NGINX, nous allons utiliser cette commande :watch -n 30 curl IP_Master:NodePortet la laisser tourner en arrière-plan.

Notre volume stocke les journaux 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 ultérieurement que la restauration a fonctionné correctement.

Velero

Installation

[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 yamlpour 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 stockagekubectl 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 partiespec.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 sera faite, mais il est possible de les programmer avec la commandevelero schedule create demo-app-schedule-backup --schedule="@every 5m" --include-namespace demo-app --ttl 0h30m00sou 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 veleroque de faire une suppression avec kubectl de la ressource Backup.

Restauration de la sauvegarde

Avant de restaurer un des Backups enregistrés précédemment, nous allons supprimer le Namespace de déploiement kubectl delete ns demo-app.

La liste des Backups est disponible avec la commande velero backup get.

On peut créer une restauration avec la commande velero restore create --from-backup demo-app-backupou 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 getet d’avoir plus d’information avec les mots clésdescribe oulogs.
Une fois la restauration terminée, nous avons de nouveau toutes nos données disponibles. Pour le vérifier, on peut exécuter la commandekubectl 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.logon 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

Installation

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 conteneur sidecar 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 conteneur d’initialisation, ou Init container, 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 du conteneur d’initialisation.
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 snapshots 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 modifiant l’objetrestic 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 lieu de stockage et le Secret du stockage. Ces deux éléments se trouvent dans le Namespace du projet à sauvegarder.

La suppression du Namespace entraîne la perte de ces deux données. Il est cependant possible de les recréer en réappliquant le Secret et une configuration de stockage 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 une 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 enregistré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 module d’extension, ou plugins, et de point d’encrage, ou hook, pour personnaliser/améliorer les sauvegardes.
  • Offre des métriques pour la surveillance des sauvegardes.
  • Velero offre la sauvegarde de tout le système, en autre les volumes.
  • Fournisseurs de stockage supportés : S3 (AWS, Ceph, Minio…), ABS, GCS. Fournisseurs de volume snapshot : 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 administrateurs.

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 la surveillance des sauvegardes.
  • Se concentre sur les sauvegardes de volumes (pvc).
  • Fournisseurs de stockage supportés : S3 (AWS, Ceph, Minio…), ABS, GCS, Openstack Swift, Backblaze B2, Local.
  • Peut être utilisé pour facilement redimensionner un volume persistant.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée.