Ainsi, comme tout le monde le sait, plus récemment, DevOpsConfRussia2018 s'est tenue à Moscou dans «Infraspace» les 1er et 2 octobre. Pour ceux qui ne sont pas à l' aise , DevOpsConf est une conférence professionnelle sur l'intégration des processus de développement, de test et d'exploitation.
Notre entreprise a également participé à cette conférence. Nous étions ses partenaires, représentant l'entreprise sur notre stand, et avons également tenu une petite réunion. Au fait, c'était notre première participation à ce genre d'activité. La première conférence, le premier mitap, la première expérience.
De quoi parlions-nous? Mitap était sur le sujet «Sauvegardes dans Kubernetes».
Très probablement, en entendant ce nom, beaucoup diront: «Pourquoi revenir sur Kubernetes? Il n'a pas besoin d'être sauvegardé, il est apatride. »

Présentation ...
Commençons par un petit historique. Pourquoi est-il devenu nécessaire de couvrir ce sujet et pourquoi il est nécessaire.
En 2016, nous nous sommes familiarisés avec une technologie telle que Kubernetes et avons commencé à l'appliquer activement à nos projets. Bien sûr, il s'agit principalement de projets avec une architecture de microservices, ce qui implique à son tour l'utilisation d'un grand nombre de logiciels divers.
Avec le premier projet où nous avons utilisé Kubernetes, nous avions une question sur la façon de sauvegarder les services Stateful qui s'y trouvent, qui parfois pour une raison ou une autre tombent dans les k8.
Nous avons commencé à étudier et rechercher des pratiques existantes pour résoudre ce problème. Communiquez avec nos collègues et camarades: "Et comment ce processus est-il mené et construit par eux?"
Après avoir parlé, nous avons réalisé que tout cela se produit par différentes méthodes, moyens et avec un grand nombre de béquilles. Cependant, nous n'avons suivi aucune approche unifiée, même dans le cadre d'un projet .
Pourquoi est-ce si important? Puisque nos projets de services d'entreprise basés sur k8s, nous avions juste besoin de développer une méthodologie structurée pour résoudre ce problème.
Imaginez que vous travaillez avec un projet spécifique dans Coober. Il contient certains services avec état et vous devez sauvegarder leurs données. En principe, ici, vous pouvez faire quelques béquilles et l'oublier. Mais que faire si vous avez déjà deux projets sur k8? Et le deuxième projet utilise des services complètement différents dans son travail. Et s'il y a déjà cinq projets? Dix? Ou plus de vingt?
Bien sûr, mettre des béquilles est déjà difficile et peu pratique. Nous avons besoin d'une sorte d'approche unifiée qui pourrait être utilisée lorsque nous travaillons avec de nombreux projets à Cuba, et en même temps, afin que l'équipe d'ingénieurs puisse facilement et littéralement en quelques minutes apporter les modifications nécessaires aux sauvegardes de ces projets.
Dans le cadre de cet article, nous allons simplement parler de quel outil et quelle pratique nous utilisons pour résoudre ce problème au sein de notre entreprise.
Comment on fait ça?
Nxs-backup c'est quoi?
Pour les sauvegardes, nous utilisons notre propre outil open source - nxs-backup. Nous n'entrerons pas dans les détails de ce qu'il peut. Plus de détails peuvent être trouvés sur le lien suivant.
Passons maintenant à l'implémentation des sauvegardes dans k8s. Comment et ce que nous avons fait exactement.
Qu'est-ce que la sauvegarde?
Regardons un exemple de sauvegarde de notre propre Redmine. Dans ce document, nous allons sauvegarder la base de données MySQL et les fichiers de projet utilisateur.
Comment fait-on cela?
1 CronJob == 1 service
Sur les serveurs ordinaires et les clusters sur le matériel, presque tous les outils de sauvegarde sont principalement lancés via cron standard. Dans k8s, à cette fin, nous utilisons CronJob, c'est-à-dire que nous créons 1 CronJob pour 1 service, que nous sauvegardons. Tous ces CronJob sont situés dans le même espace de noms que le service lui-même.
Commençons par la base de données MySQL. Pour sauvegarder MySQL, nous avons besoin de 4 éléments, comme pour presque tous les autres services:
- ConfigMap (nxs-backup.conf)
- ConfigMap (mysql.conf pour nxs-backup)
- Secret (les accès au service sont stockés ici, dans ce cas MySQL). Habituellement, cet élément est déjà défini pour que le service fonctionne et peut être réutilisé.
- CronJob (pour chaque service qui lui est propre)
Allons dans l'ordre.
nxs-backup.conf
apiVersion: v1 kind: ConfigMap metadata: name: nxs-backup-conf data: nxs-backup.conf: |- main: server_name: Nixys k8s cluster admin_mail: admins@nixys.ru client_mail: - '' mail_from: backup@nixys.ru level_message: error block_io_read: '' block_io_write: '' blkio_weight: '' general_path_to_all_tmp_dir: /var/nxs-backup cpu_shares: '' log_file: /dev/stdout jobs: !include [conf.d
Ici, nous définissons les paramètres de base qui sont passés à notre outil, qui sont nécessaires à son fonctionnement. Il s'agit du nom du serveur, de l'e-mail pour les notifications, de la restriction de la consommation des ressources et d'autres paramètres.
Les configurations peuvent être spécifiées au format j2, ce qui permet l'utilisation de variables d'environnement.
mysql.conf
apiVersion: v1 kind: ConfigMap metadata: name: mysql-conf data: service.conf.j2: |- - job: mysql type: mysql tmp_dir: /var/nxs-backup/databases/mysql/dump_tmp sources: - connect: db_host: {{ db_host }} db_port: {{ db_port }} socket: '' db_user: {{ db_user }} db_password: {{ db_password }} target: - redmine_db gzip: yes is_slave: no extra_keys: '--opt --add-drop-database --routines --comments --create-options --quote-names --order-by-primary --hex-blob' storages: - storage: local enable: yes backup_dir: /var/nxs-backup/databases/mysql/dump store: days: 6 weeks: 4 month: 6
Ce fichier décrit la logique de sauvegarde du service correspondant, dans notre cas c'est MySQL.
Ici vous pouvez spécifier:
- Quel est le nom du travail (champ: travail)
- Type d'emploi (champ: type)
- Le répertoire temporaire nécessaire pour collecter les sauvegardes (champ: tmp_dir)
- Options de connexion MySQL (champ: connect)
- Base de données à sauvegarder (champ: cible)
- Besoin d'arrêter Slave avant de collecter (champ: is_slave)
- Clés supplémentaires pour mysqldump (champ: extra_keys)
- Stockage Stockage, c'est-à-dire dans quel stockage nous allons stocker une copie (champ: stockage)
- Le répertoire où nous allons stocker nos copies (champ: backup_dir)
- Schéma de stockage (champ: magasin)
Dans notre exemple, le type de stockage est défini sur local, c'est-à-dire que nous collectons et stockons les sauvegardes localement dans un répertoire spécifique du pod lancé.
Ici, par analogie avec ce fichier de configuration, vous pouvez spécifier les mêmes fichiers de configuration pour Redis, PostgreSQL ou tout autre service nécessaire, s'il est pris en charge par notre outil. Le fait qu'il supporte se trouve sur le lien donné plus haut.
MySQL secret
apiVersion: v1 kind: Secret metadata: name: app-config data: db_name: "" db_host: "" db_user: "" db_password: "" secret_token: "" smtp_address: "" smtp_domain: "" smtp_ssl: "" smtp_enable_starttls_auto: "" smtp_port: "" smtp_auth_type: "" smtp_login: "" smtp_password: ""
Nous gardons un accès secret pour nous connecter à MySQL lui-même et au serveur de messagerie. Ils peuvent être stockés dans un secret séparé, ou profiter de celui qui existe, bien sûr, s'il y en a un. Rien d'intéressant ici. Notre secret contient également le secret_token, qui est nécessaire au fonctionnement de notre Redmine.
CronJob MySQL
apiVersion: batch/v1beta1 kind: CronJob metadata: name: mysql spec: schedule: "00 00 * * *" jobTemplate: spec: template: spec: affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: kubernetes.io/hostname operator: In values: - nxs-node5 containers: - name: mysql-backup image: nixyslab/nxs-backup:latest env: - name: DB_HOST valueFrom: secretKeyRef: name: app-config key: db_host - name: DB_PORT value: '3306' - name: DB_USER valueFrom: secretKeyRef: name: app-config key: db_user - name: DB_PASSWORD valueFrom: secretKeyRef: name: app-config key: db_password - name: SMTP_MAILHUB_ADDR valueFrom: secretKeyRef: name: app-config key: smtp_address - name: SMTP_MAILHUB_PORT valueFrom: secretKeyRef: name: app-config key: smtp_port - name: SMTP_USE_TLS value: 'YES' - name: SMTP_AUTH_USER valueFrom: secretKeyRef: name: app-config key: smtp_login - name: SMTP_AUTH_PASS valueFrom: secretKeyRef: name: app-config key: smtp_password - name: SMTP_FROM_LINE_OVERRIDE value: 'NO' volumeMounts: - name: mysql-conf mountPath: /usr/share/nxs-backup/service.conf.j2 subPath: service.conf.j2 - name: nxs-backup-conf mountPath: /etc/nxs-backup/nxs-backup.conf subPath: nxs-backup.conf - name: backup-dir mountPath: /var/nxs-backup imagePullPolicy: Always volumes: - name: mysql-conf configMap: name: mysql-conf items: - key: service.conf.j2 path: service.conf.j2 - name: nxs-backup-conf configMap: name: nxs-backup-conf items: - key: nxs-backup.conf path: nxs-backup.conf - name: backup-dir hostPath: path: /var/backups/k8s type: Directory restartPolicy: OnFailure
Cet élément est peut-être le plus intéressant. Premièrement, afin de compiler le bon CronJob, vous devez déterminer où les sauvegardes collectées seront stockées.
Pour cela, nous avons un serveur séparé avec le nombre de ressources nécessaires. Dans l'exemple, un nœud de cluster distinct, nxs-node5, est alloué pour collecter les sauvegardes. La restriction sur le démarrage de CronJob sur les nœuds dont nous avons besoin est définie par la directive nodeAffinity.
Lorsque CronJob démarre, le répertoire correspondant lui est connecté via hostPath à partir du système hôte, qui est utilisé pour stocker des copies de sauvegarde.
Ensuite, ConfigMaps est connecté au CronJob spécifique, contenant la configuration pour nxs-backup, à savoir les fichiers nxs-backup.conf et mysql.conf dont nous venons de parler ci-dessus.
Ensuite, toutes les variables d'environnement nécessaires sont définies, qui sont définies directement dans le manifeste ou extraites de Secret'ov.
Ainsi, les variables sont transférées dans le conteneur et, via docker-entrypoint.sh, sont remplacées dans ConfigMaps aux endroits dont nous avons besoin avec les valeurs nécessaires. Pour MySQL, il s'agit de db_host, db_user, db_password. Dans ce cas, nous transmettons le port simplement comme une valeur dans le manifeste du CronJob, car il ne contient aucune information précieuse.
Eh bien, avec MySQL, tout semble clair. Voyons maintenant ce qui est nécessaire pour sauvegarder les fichiers d'application Redmine.
desc_files.conf
apiVersion: v1 kind: ConfigMap metadata: name: desc-files-conf data: service.conf.j2: |- - job: desc-files type: desc_files tmp_dir: /var/nxs-backup/files/desc/dump_tmp sources: - target: - /var/www/files gzip: yes storages: - storage: local enable: yes backup_dir: /var/nxs-backup/files/desc/dump store: days: 6 weeks: 4 month: 6
Il s'agit d'un fichier de configuration qui décrit la logique de sauvegarde des fichiers. Il n'y a rien d'inhabituel ici non plus, tous les mêmes paramètres sont définis comme pour MySQL, à l'exception des données d'autorisation, car ils ne le sont tout simplement pas. Bien qu'ils puissent l'être, si des protocoles de transfert de données sont impliqués: ssh, ftp, webdav, s3 et autres. Nous considérerons cette option un peu plus tard.
CronJob desc_files
apiVersion: batch/v1beta1 kind: CronJob metadata: name: desc-files spec: schedule: "00 00 * * *" jobTemplate: spec: template: spec: affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: kubernetes.io/hostname operator: In values: - nxs-node5 containers: - name: desc-files-backup image: nixyslab/nxs-backup:latest env: - name: SMTP_MAILHUB_ADDR valueFrom: secretKeyRef: name: app-config key: smtp_address - name: SMTP_MAILHUB_PORT valueFrom: secretKeyRef: name: app-config key: smtp_port - name: SMTP_USE_TLS value: 'YES' - name: SMTP_AUTH_USER valueFrom: secretKeyRef: name: app-config key: smtp_login - name: SMTP_AUTH_PASS valueFrom: secretKeyRef: name: app-config key: smtp_password - name: SMTP_FROM_LINE_OVERRIDE value: 'NO' volumeMounts: - name: desc-files-conf mountPath: /usr/share/nxs-backup/service.conf.j2 subPath: service.conf.j2 - name: nxs-backup-conf mountPath: /etc/nxs-backup/nxs-backup.conf subPath: nxs-backup.conf - name: target-dir mountPath: /var/www/files - name: backup-dir mountPath: /var/nxs-backup imagePullPolicy: Always volumes: - name: desc-files-conf configMap: name: desc-files-conf items: - key: service.conf.j2 path: service.conf.j2 - name: nxs-backup-conf configMap: name: nxs-backup-conf items: - key: nxs-backup.conf path: nxs-backup.conf - name: backup-dir hostPath: path: /var/backups/k8s type: Directory - name: target-dir persistentVolumeClaim: claimName: redmine-app-files restartPolicy: OnFailure
Rien de nouveau non plus concernant MySQL. Mais ici, un PV supplémentaire (target-dir) est monté, que nous allons sauvegarder - / var / www / files. Sinon, tout est pareil, nous stockons des copies localement sur le nœud dont nous avons besoin, pour lequel CronJob est affecté.
Résumé
Pour chaque service que nous voulons sauvegarder, nous créons un CronJob séparé avec tous les éléments connexes nécessaires: ConfigMaps et Secrets. Par analogie avec les exemples considérés, nous pouvons sauvegarder tout service similaire dans le cluster.
Je pense que, sur la base de ces deux exemples, tout le monde a une idée de la façon dont nous sauvegardons les services d'État à Cuba. Je pense que cela n'a aucun sens d'analyser en détail les mêmes exemples pour d'autres services, car, fondamentalement, ils se ressemblent tous et présentent de légères différences.
En fait, c'est ce que nous voulions atteindre, à savoir une sorte d' approche unifiée pour la construction du processus de sauvegarde. Et pour que cette approche puisse être appliquée à un grand nombre de projets différents basés sur k8s.
Où le stockons-nous?
Dans tous les exemples décrits ci-dessus, nous stockons des copies dans le répertoire local du nœud sur lequel le conteneur s'exécute. Mais personne ne se soucie de connecter le volume persistant en tant que stockage externe fonctionnel et d'y collecter des copies. Ou vous pouvez uniquement les synchroniser avec un magasin distant en utilisant le protocole souhaité sans enregistrer localement. Autrement dit, il existe de nombreuses variantes. Collectez d'abord localement, puis synchronisez. Ou pour collecter et stocker uniquement sur un stockage distant, etc. La configuration est assez flexible.
mysql.conf + s3
Ce qui suit est un exemple du fichier de configuration de sauvegarde MySQL, où les copies sont stockées localement sur le nœud où CronJob est exécuté, et également synchronisées dans s3.
apiVersion: v1 kind: ConfigMap metadata: name: mysql-conf data: service.conf.j2: |- - job: mysql type: mysql tmp_dir: /var/nxs-backup/databases/mysql/dump_tmp sources: - connect: db_host: {{ db_host }} db_port: {{ db_port }} socket: '' db_user: {{ db_user }} db_password: {{ db_password }} target: - redmine_db gzip: yes is_slave: no extra_keys: ' --opt --add-drop-database --routines --comments --create-options --quote-names --order-by-primary --hex-blob' storages: - storage: local enable: yes backup_dir: /var/nxs-backup/databases/mysql/dump store: days: 6 weeks: 4 month: 6 - storage: s3 enable: yes backup_dir: /nxs-backup/databases/mysql/dump bucket_name: {{ bucket_name }} access_key_id: {{ access_key_id }} secret_access_key: {{ secret_access_key }} s3fs_opts: {{ s3fs_opts }} store: days: 2 weeks: 1 month: 6
Autrement dit, s'il ne suffit pas de stocker des copies localement, vous pouvez les synchroniser avec n'importe quel stockage distant à l'aide du protocole approprié. Le nombre de stockage pour le stockage peut être quelconque.
Mais dans ce cas, vous devez encore apporter quelques modifications supplémentaires, à savoir:
- Connectez le ConfigMap approprié avec le contenu nécessaire pour l'autorisation avec AWS S3, au format j2
- Créer un secret approprié pour stocker les accès d'autorisation
- Définissez les variables d'environnement nécessaires tirées de Secret ci-dessus
- Ajustez docker-entrypoint.sh pour remplacer les variables correspondantes dans ConfigMap
- Reconstruisez l'image Docker, en ajoutant des utilitaires pour travailler avec AWS S3
Bien que ce processus soit loin d'être parfait, nous y travaillons. Par conséquent, dans un proche avenir, nous ajouterons à nxs-backup la possibilité de définir des paramètres dans le fichier de configuration à l'aide de variables d'environnement, ce qui simplifiera considérablement le travail avec le fichier de point d'entrée et minimisera le temps consacré à l'ajout de la prise en charge de la sauvegarde de nouveaux services.
Conclusion
C'est probablement tout.
L'utilisation de l'approche dont nous venons de parler, tout d'abord, nous permet de structurer et de sauvegarder des services de projet Stateful sur k8s. Autrement dit, il s'agit d'une solution toute faite et, surtout, de la pratique que vous pouvez appliquer dans vos projets, sans perdre de temps et d'efforts à rechercher et à affiner les solutions open source existantes.