Stateful Backups in Kubernetes

Wie jeder sicher weiß, fand die DevOpsConfRussia2018 zuletzt am 1. und 2. Oktober in Moskau im „Infraspace“ statt. Für diejenigen, die sich nicht wohl fühlen , ist DevOpsConf eine professionelle Konferenz zur Integration von Entwicklungs-, Test- und Betriebsprozessen.


Auch unser Unternehmen hat an dieser Konferenz teilgenommen. Wir waren seine Partner, vertraten das Unternehmen an unserem Stand und hielten auch ein kleines Treffen ab. Dies war übrigens unsere erste Teilnahme an dieser Art von Aktivität. Die erste Konferenz, der erste Mitap, die erste Erfahrung.


Worüber haben wir gesprochen? Mitap befasste sich mit dem Thema „Backups in Kubernetes“.


Viele, die diesen Namen wahrscheinlich hören, werden sagen: „Warum zurück zu Kubernetes? Es muss nicht gesichert werden, es ist zustandslos. “



Einführung ...


Beginnen wir mit einem kleinen Hintergrund. Warum wurde es notwendig, dieses Thema zu behandeln und warum wird es benötigt?


2016 haben wir eine Technologie wie Kubernetes kennengelernt und begonnen, sie aktiv auf unsere Projekte anzuwenden. Dies sind natürlich hauptsächlich Projekte mit Microservice-Architektur, und dies erfordert wiederum die Verwendung einer großen Anzahl unterschiedlicher Software.


Bei dem ersten Projekt, bei dem wir Kubernetes verwendeten, hatten wir eine Frage zur Sicherung der dort befindlichen Stateful-Dienste, die manchmal aus irgendeinem Grund in k8s fallen.


Wir begannen zu studieren und nach bestehenden Praktiken zu suchen, um dieses Problem zu lösen. Kommunizieren Sie mit unseren Kollegen und Kameraden: "Und wie wird dieser Prozess von ihnen durchgeführt und aufgebaut?"


Nach dem Gespräch stellten wir fest, dass dies alles mit unterschiedlichen Methoden, Mitteln und mit einer großen Anzahl von Krücken geschieht. Wir haben jedoch auch im Rahmen eines Projekts keinen einheitlichen Ansatz verfolgt.


Warum ist das so wichtig? Da unser Unternehmen Projekte auf der Basis von k8s betreut, mussten wir lediglich eine strukturierte Methodik zur Lösung dieses Problems entwickeln.


Stellen Sie sich vor, Sie arbeiten mit einem bestimmten Projekt in Coober. Es enthält einige Stateful-Dienste, und Sie müssen deren Daten sichern. Im Prinzip kann man hier ein paar Krücken machen und es vergessen. Aber was ist, wenn Sie bereits zwei Projekte auf k8s haben? Und das zweite Projekt verwendet in seiner Arbeit völlig andere Dienste. Und wenn es schon fünf Projekte gibt? Zehn? Oder mehr als zwanzig?


Das Anlegen von Krücken ist natürlich schon schwierig und unpraktisch. Wir brauchen einen einheitlichen Ansatz, der bei der Arbeit mit vielen Projekten in Kuba und gleichzeitig verwendet werden kann, damit das Ingenieurteam in wenigen Minuten einfach und buchstäblich die erforderlichen Änderungen an den Sicherungen dieser Projekte vornehmen kann.


Im Rahmen dieses Artikels werden wir nur darüber sprechen, welches Tool und welche Praxis wir verwenden, um dieses Problem in unserem Unternehmen zu lösen.


Wie machen wir das?


Nxs-Backup was ist das?

Für Backups verwenden wir unser eigenes Open Source-Tool - nxs-backup. Wir werden nicht auf die Details eingehen, was er kann. Weitere Details finden Sie unter folgendem Link .


Fahren wir nun mit der Implementierung von Backups in k8s fort. Wie und was genau wurde von uns gemacht.


Was ist ein Backup?


Schauen wir uns ein Beispiel für ein Backup unserer eigenen Redmine an. Darin werden wir die MySQL-Datenbank und Benutzerprojektdateien sichern.


Wie machen wir das?


1 CronJob == 1 Service

Auf normalen Servern und Clustern auf Hardware werden fast alle Sicherungstools meist über reguläres Cron gestartet. In k8s verwenden wir zu diesem Zweck CronJobs, d. H. Wir erstellen 1 CronJob für 1 Dienst, den wir sichern werden. Alle diese CronJobs befinden sich im selben Namespace wie der Dienst selbst.


Beginnen wir mit der MySQL-Datenbank. Um MySQL zu sichern, benötigen wir wie bei fast jedem anderen Dienst 4 Elemente:


  • ConfigMap (nxs-backup.conf)
  • ConfigMap (mysql.conf für nxs-backup)
  • Geheim (Zugriffe auf den Dienst werden hier gespeichert, in diesem Fall MySQL). Normalerweise ist dieses Element bereits für den Dienst definiert und kann wiederverwendet werden.
  • CronJob (für jeden Dienst seinen eigenen)

Lass uns in Ordnung gehen.


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/*.conf] 

Hier legen wir die grundlegenden Parameter fest, die an unser Werkzeug übergeben werden und die für dessen Betrieb erforderlich sind. Dies ist der Name des Servers, eine E-Mail für Benachrichtigungen, eine Einschränkung des Ressourcenverbrauchs und andere Parameter.


Konfigurationen können im j2-Format angegeben werden, wodurch Umgebungsvariablen verwendet werden können.


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 

Diese Datei beschreibt die Sicherungslogik für den entsprechenden Dienst, in unserem Fall MySQL.


Hier können Sie angeben:


  • Wie heißt Job (Feld: Job)
  • Auftragstyp (Feld: Typ)
  • Das temporäre Verzeichnis, das zum Sammeln von Sicherungen benötigt wird (Feld: tmp_dir)
  • MySQL-Verbindungsoptionen (Feld: Verbinden)
  • Zu sichernde Datenbank (Feld: Ziel)
  • Der Slave muss vor dem Sammeln gestoppt werden (Feld: is_slave)
  • Zusätzliche Schlüssel für mysqldump (Feld: extra_keys)
  • Speicher Speicher, d. H. In welchem ​​Speicher speichern wir eine Kopie (Feld: Speicher)
  • Das Verzeichnis, in dem wir unsere Kopien speichern (Feld: backup_dir)
  • Speicherschema (Feld: Speicher)

In unserem Beispiel ist der Speichertyp auf lokal festgelegt, d. H. Wir sammeln und speichern Sicherungen lokal in einem bestimmten Verzeichnis des gestarteten Pods.


In Analogie zu dieser Konfigurationsdatei können Sie hier dieselben Konfigurationsdateien für Redis, PostgreSQL oder einen anderen erforderlichen Dienst angeben, sofern dies von unserem Tool unterstützt wird. Die Tatsache, dass es unterstützt, finden Sie unter dem zuvor angegebenen Link.


Geheime MySQL

 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: "" 

Wir behalten den geheimen Zugriff, um eine Verbindung zu MySQL selbst und dem Mailserver herzustellen. Sie können entweder in einem separaten Geheimnis gespeichert werden oder natürlich das vorhandene nutzen, falls es eines gibt. Nichts interessantes hier. Unser Geheimnis enthält auch das secret_token, das für den Betrieb unserer Redmine notwendig ist.


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 

Vielleicht ist dieses Element das interessanteste. Um den richtigen CronJob zu kompilieren, müssen Sie zunächst festlegen, wo die gesammelten Backups gespeichert werden.


Dafür haben wir einen separaten Server mit der notwendigen Anzahl an Ressourcen. In diesem Beispiel wird ein separater Clusterknoten, nxs-node5, zum Sammeln von Sicherungen zugewiesen. Die Einschränkung beim Starten von CronJob auf den von uns benötigten Knoten wird durch die Anweisung nodeAffinity festgelegt.


Beim Start von CronJob wird das entsprechende Verzeichnis über hostPath vom Host-System aus damit verbunden, in dem Sicherungskopien gespeichert werden.


Als nächstes werden ConfigMaps mit dem spezifischen CronJob verbunden, der die Konfiguration für nxs-backup enthält, nämlich die Dateien nxs-backup.conf und mysql.conf, über die wir gerade gesprochen haben.


Anschließend werden alle erforderlichen Umgebungsvariablen festgelegt, die direkt im Manifest definiert oder aus Secret'ov abgerufen werden.


Daher werden die Variablen in den Container übertragen und über docker-entrypoint.sh in ConfigMaps an den von uns benötigten Stellen durch die erforderlichen Werte ersetzt. Für MySQL ist dies db_host, db_user, db_password. In diesem Fall übergeben wir den Port einfach als Wert im CronJob-Manifest, da er keine wertvollen Informationen enthält.


Nun, mit MySQL scheint alles klar zu sein. Nun wollen wir sehen, was zum Sichern von Redmine-Anwendungsdateien erforderlich ist.


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 

Dies ist eine Konfigurationsdatei, die die Sicherungslogik für Dateien beschreibt. Auch hier gibt es nichts Ungewöhnliches, mit Ausnahme der Berechtigungsdaten werden dieselben Parameter wie für MySQL eingestellt, da dies einfach nicht der Fall ist. Obwohl dies der Fall sein kann, wenn Protokolle für die Datenübertragung beteiligt sind: ssh, ftp, webdav, s3 und andere. Wir werden diese Option etwas später in Betracht ziehen.


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 

Auch in Bezug auf MySQL nichts Neues. Aber hier ist eine zusätzliche PV (Zielverzeichnis) gemountet, die wir sichern werden - / var / www / files. Ansonsten ist alles gleich, wir speichern Kopien lokal auf dem Knoten, den wir benötigen, für den CronJob zugewiesen ist.


Zusammenfassung

Für jeden Service, den wir sichern möchten, erstellen wir einen separaten CronJob mit allen erforderlichen verwandten Elementen: ConfigMaps und Secrets. In Analogie zu den betrachteten Beispielen können wir jeden ähnlichen Dienst im Cluster sichern.


Ich denke, basierend auf diesen beiden Beispielen hat jeder eine Vorstellung davon, wie wir die staatlichen Dienste in Kuba unterstützen. Ich denke, es macht keinen Sinn, dieselben Beispiele für andere Dienste im Detail zu analysieren, da sie im Grunde alle gleich sind und geringfügige Unterschiede aufweisen.


Eigentlich wollten wir dies erreichen, nämlich einen einheitlichen Ansatz für den Aufbau des Sicherungsprozesses. Und damit dieser Ansatz auf eine Vielzahl unterschiedlicher Projekte auf Basis von k8s angewendet werden kann.


Wo lagern wir es?


In allen oben beschriebenen Beispielen speichern wir Kopien im lokalen Verzeichnis des Knotens, auf dem der Container ausgeführt wird. Aber niemand stört sich daran, Persistent Volume als funktionierenden externen Speicher anzuschließen und dort Kopien zu sammeln. Oder Sie können sie nur mit dem gewünschten Protokoll mit einem Remote-Speicher synchronisieren, ohne sie lokal zu speichern. Das heißt, es gibt viele Variationen. Zuerst lokal sammeln, dann synchronisieren. Oder nur auf einem Remote-Speicher usw. zu sammeln und zu speichern. Die Konfiguration ist sehr flexibel.


mysql.conf + s3

Das folgende Beispiel zeigt die MySQL-Sicherungskonfigurationsdatei, in der Kopien lokal auf dem Knoten gespeichert werden, auf dem CronJob ausgeführt wird, und auch in s3 synchronisiert werden.


 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 

Wenn es nicht ausreicht, Kopien lokal zu speichern, können Sie sie mit dem entsprechenden Protokoll mit jedem Remotespeicher synchronisieren. Die Anzahl der Speicherplätze kann beliebig sein.


In diesem Fall müssen Sie jedoch noch einige zusätzliche Änderungen vornehmen, nämlich:


  • Verbinden Sie die entsprechende ConfigMap mit dem für die Autorisierung mit AWS S3 erforderlichen Inhalt im j2-Format
  • Erstellen Sie ein geeignetes Geheimnis zum Speichern von Autorisierungszugriffen
  • Stellen Sie die erforderlichen Umgebungsvariablen aus Secret oben ein
  • Passen Sie docker-entrypoint.sh an, um die entsprechenden Variablen in ConfigMap zu ersetzen
  • Erstellen Sie das Docker-Image neu und fügen Sie Dienstprogramme für die Arbeit mit AWS S3 hinzu

Dieser Prozess ist zwar alles andere als perfekt, aber wir arbeiten daran. Daher werden wir in naher Zukunft nxs-backup die Möglichkeit hinzufügen, Parameter in der Konfigurationsdatei mithilfe von Umgebungsvariablen zu definieren, was die Arbeit mit der Einstiegspunktdatei erheblich vereinfacht und den Zeitaufwand für das Hinzufügen von Sicherungsunterstützung für neue Dienste minimiert.


Fazit


Das ist wahrscheinlich alles.


Mit dem Ansatz, über den wir gerade gesprochen haben, können wir zunächst Stateful-Projektdienste für k8s strukturieren und als Vorlage sichern. Das heißt, dies ist eine vorgefertigte Lösung und vor allem die Praxis, die Sie in Ihren Projekten anwenden können, ohne Zeit und Mühe bei der Suche und Aktualisierung vorhandener Open Source-Lösungen zu verschwenden.

Source: https://habr.com/ru/post/de426543/


All Articles