88. Резервное копирование, ориентированное на приложения

Обновлено: 2024-03-12
7 мин

Резервное копирование, ориентированное на приложения

В День 85 мы уже потратили некоторое время на обсуждение служб данных или приложений с интенсивным использованием данных, таких как базы данных. Для этих служб данных мы должны подумать о том, как управлять согласованностью, особенно когда речь идет о согласованности приложений.

В этой статье мы рассмотрим требования к защите данных приложения в последовательной манере.

Для этого мы выберем инструмент Kanister

Представляем Kanister

Kanister - это проект с открытым исходным кодом от Kasten, который позволяет нам управлять (резервное копирование и восстановление) данными приложений на Kubernetes. Вы можете развернуть Kanister как helm-приложение в своем кластере Kubernetes.

Kanister использует пользовательские ресурсы Kubernetes, основные пользовательские ресурсы, которые устанавливаются при развертывании Kanister, следующие

  • Profile - целевое место для хранения резервных копий и восстановления. Чаще всего это объектное хранилище.
  • Blueprint - шаги, которые необходимо предпринять для резервного копирования и восстановления базы данных, должны быть сохранены в Blueprint.
  • ActionSet - действия по перемещению целевой резервной копии в наш профиль, а также действия по восстановлению.

Описание выполнения

Прежде чем приступить к работе, мы должны рассмотреть рабочий процесс, который использует Kanister для защиты данных приложения. Во-первых, наш контроллер развертывается с помощью helm в нашем кластере Kubernetes, Kanister живет в своем собственном пространстве имен. Мы берем наш Blueprint, для которого существует множество поддерживаемых сообществом Blueprint, мы рассмотрим это более подробно в ближайшее время. Затем у нас есть рабочая нагрузка базы данных.

Затем мы создаем наш ActionSet.

ActionSet позволяет нам запускать действия, определенные в чертеже, против конкретной службы данных.

ActionSet, в свою очередь, использует функции Kanister (KubeExec, KubeTask, Resource Lifecycle) и выталкивает нашу резервную копию в целевое хранилище (Profile).

Если действие выполнено/не выполнено, соответствующий статус обновляется в наборе действий.

Развертывание Kanister

И снова мы будем использовать кластер minikube для создания резервной копии приложения. Если у вас он все еще работает с предыдущей сессии, то мы можем продолжать использовать его.

На момент написания статьи мы имеем версию образа 0.75.0. С помощью следующей команды helm мы установим kanister в наш кластер Kubernetes.

helm install kanister --namespace kanister kanister/kanister-operator --set image.tag=0.75.0 --create-namespace.

Мы можем использовать kubectl get pods -n kanister, чтобы убедиться, что pod запущен и работает, а также проверить, что наши пользовательские определения ресурсов теперь доступны (Если вы только установили Kanister, то вы увидите выделенные 3)

Развертывание базы данных

Развертывание mysql через helm:

APP_NAME=my-production-app
kubectl create ns ${APP_NAME}
helm repo add bitnami https://charts.bitnami.com/bitnami
helm install mysql-store bitnami/mysql --set primary.persistence.size=1Gi,volumePermissions.enabled=true --namespace=${APP_NAME}
kubectl get pods -n ${APP_NAME} -w

Заполните базу данных mysql исходными данными, выполнив следующее:

MYSQL_ROOT_PASSWORD=$(kubectl get secret --namespace ${APP_NAME} mysql-store -o jsonpath="{.data.mysql-root-password}" | base64 --decode)
MYSQL_HOST=mysql-store.${APP_NAME}.svc.cluster.local
MYSQL_EXEC="mysql -h ${MYSQL_HOST} -u root --password=${MYSQL_ROOT_PASSWORD} -DmyImportantData -t"
echo MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}

Создание MySQL CLIENT

Мы запустим другой образ контейнера, который будет выступать в качестве нашего клиента

APP_NAME=my-production-app
kubectl run mysql-client --rm --env APP_NS=${APP_NAME} --env MYSQL_EXEC="${MYSQL_EXEC}" --env MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD} --env MYSQL_HOST=${MYSQL_HOST} --namespace ${APP_NAME} --tty -i --restart='Never' --image docker.io/bitnami/mysql:latest --command -- bash
Примечание: если у вас уже запущен существующий mysql client pod, удалите его с помощью команды

kubectl delete pod -n ${APP_NAME} mysql-client

Добавление данных в MySQL

echo "create database myImportantData;" | mysql -h ${MYSQL_HOST} -u root --password=${MYSQL_ROOT_PASSWORD}
MYSQL_EXEC="mysql -h ${MYSQL_HOST} -u root --password=${MYSQL_ROOT_PASSWORD} -DmyImportantData -t"
echo "drop table Accounts" | ${MYSQL_EXEC}
echo "create table if not exists Accounts(name text, balance integer); insert into Accounts values('nick', 0);" |  ${MYSQL_EXEC}
echo "insert into Accounts values('albert', 112);" | ${MYSQL_EXEC}
echo "insert into Accounts values('alfred', 358);" | ${MYSQL_EXEC}
echo "insert into Accounts values('beatrice', 1321);" | ${MYSQL_EXEC}
echo "insert into Accounts values('bartholomew', 34);" | ${MYSQL_EXEC}
echo "insert into Accounts values('edward', 5589);" | ${MYSQL_EXEC}
echo "insert into Accounts values('edwin', 144);" | ${MYSQL_EXEC}
echo "insert into Accounts values('edwina', 233);" | ${MYSQL_EXEC}
echo "insert into Accounts values('rastapopoulos', 377);" | ${MYSQL_EXEC}
echo "select * from Accounts;" |  ${MYSQL_EXEC}
exit

Вы должны увидеть некоторые данные, как показано ниже.

Создание профиля Kanister

Kanister предоставляет CLI, kanctl и другую утилиту kando, которая используется для взаимодействия с провайдером объектного хранилища из blueprint и обе эти утилиты.

CLI Download

Я пошел и создал AWS S3 Bucket, который мы будем использовать в качестве цели профиля и места восстановления. Я буду использовать переменные окружения, чтобы иметь возможность показать вам команды, которые я выполняю с помощью kanctl для создания нашего профиля kanister.

kanctl create profile s3compliant --access-key $ACCESS_KEY --secret-key $SECRET_KEY --bucket $BUCKET --region eu-west-2 --namespace my-production-app.

Время чертежа

Не волнуйтесь, вам не нужно создавать свой собственный с нуля, если только ваш сервис данных не указан в Примерах Канистера, но, конечно, вклад сообщества - это то, как этот проект становится известным.

Мы будем использовать следующую схему.

apiVersion: cr.kanister.io/v1alpha1
kind: Blueprint
metadata:
  name: mysql-blueprint
actions:
  backup:
    outputArtifacts:
      mysqlCloudDump:
        keyValue:
          s3path: "{{ .Phases.dumpToObjectStore.Output.s3path }}"
    phases:
    - func: KubeTask
      name: dumpToObjectStore
      objects:
        mysqlSecret:
          kind: Secret
          name: '{{ index .Object.metadata.labels "app.kubernetes.io/instance" }}'
          namespace: '{{ .StatefulSet.Namespace }}'
      args:
        image: ghcr.io/kanisterio/mysql-sidecar:0.75.0
        namespace: "{{ .StatefulSet.Namespace }}"
        command:
        - bash
        - -o
        - errexit
        - -o
        - pipefail
        - -c
        - |
          s3_path="/mysql-backups/{{ .StatefulSet.Namespace }}/{{ index .Object.metadata.labels "app.kubernetes.io/instance" }}/{{ toDate "2006-01-02T15:04:05.999999999Z07:00" .Time  | date "2006-01-02T15-04-05" }}/dump.sql.gz"
          root_password="{{ index .Phases.dumpToObjectStore.Secrets.mysqlSecret.Data "mysql-root-password" | toString }}"
          mysqldump --column-statistics=0 -u root --password=${root_password} -h {{ index .Object.metadata.labels "app.kubernetes.io/instance" }} --single-transaction --all-databases | gzip - | kando location push --profile '{{ toJson .Profile }}' --path ${s3_path} -
          kando output s3path ${s3_path}
  restore:
    inputArtifactNames:
    - mysqlCloudDump
    phases:
    - func: KubeTask
      name: restoreFromBlobStore
      objects:
        mysqlSecret:
          kind: Secret
          name: '{{ index .Object.metadata.labels "app.kubernetes.io/instance" }}'
          namespace: '{{ .StatefulSet.Namespace }}'
      args:
        image: ghcr.io/kanisterio/mysql-sidecar:0.75.0
        namespace: "{{ .StatefulSet.Namespace }}"
        command:
        - bash
        - -o
        - errexit
        - -o
        - pipefail
        - -c
        - |
          s3_path="{{ .ArtifactsIn.mysqlCloudDump.KeyValue.s3path }}"
          root_password="{{ index .Phases.restoreFromBlobStore.Secrets.mysqlSecret.Data "mysql-root-password" | toString }}"
          kando location pull --profile '{{ toJson .Profile }}' --path ${s3_path} - | gunzip | mysql -u root --password=${root_password} -h {{ index .Object.metadata.labels "app.kubernetes.io/instance" }}
  delete:
    inputArtifactNames:
    - mysqlCloudDump
    phases:
    - func: KubeTask
      name: deleteFromBlobStore
      args:
        image: ghcr.io/kanisterio/mysql-sidecar:0.75.0
        namespace: "{{ .Namespace.Name }}"
        command:
        - bash
        - -o
        - errexit
        - -o
        - pipefail
        - -c
        - |
          s3_path="{{ .ArtifactsIn.mysqlCloudDump.KeyValue.s3path }}"
          kando location delete --profile '{{ toJson .Profile }}' --path ${s3_path}

Чтобы добавить его, мы воспользуемся командой kubectl create -f mysql-blueprint.yml -n kanister.

Создаем наш ActionSet и защищаем наше приложение

Теперь мы создадим резервную копию данных MySQL с помощью ActionSet, определяющего резервное копирование для этого приложения. Создайте ActionSet в том же пространстве имен, что и контроллер.

kubectl get profiles.cr.kanister.io -n my-production-app Эта команда покажет нам профиль, который мы ранее создали, здесь может быть настроено несколько профилей, поэтому мы можем захотеть использовать определенные профили для разных ActionSet’ов.

Затем мы создадим наш ActionSet следующей командой с помощью kanctl.

kanctl create actionset --action backup --namespace kanister --blueprint mysql-blueprint --statefulset my-production-app/mysql-store --profile my-production-app/s3-profile-dc5zm --secrets mysql=my-production-app/mysql-store.

Из приведенной выше команды видно, что мы определяем blueprint, который мы добавили в пространство имен, statefulset в нашем пространстве имен my-production-app, а также секреты для входа в приложение MySQL.

Проверьте состояние ActionSet, взяв имя ActionSet и используя эту команду kubectl --namespace kanister describe actionset backup-qpnqv.

Наконец, мы можем пойти и подтвердить, что теперь у нас есть данные в нашем ведре AWS S3.

Восстановление

Нам нужно нанести некоторый ущерб, прежде чем мы сможем что-либо восстановить, мы можем сделать это, уронив нашу таблицу, возможно, это был несчастный случай, а возможно и нет.

Подключитесь к нашему MySQL pod.

APP_NAME=my-production-app
kubectl run mysql-client --rm --env APP_NS=${APP_NAME} --env MYSQL_EXEC="${MYSQL_EXEC}" --env MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD} --env MYSQL_HOST=${MYSQL_HOST} --namespace ${APP_NAME} --tty -i --restart='Never' --image  docker.io/bitnami/mysql:latest --command -- bash

Вы можете увидеть, что наша база данных importantdata находится там с помощью echo "SHOW DATABASES;" | ${MYSQL_EXEC}.

Затем для удаления мы запустили echo "DROP DATABASE myImportantData;" | ${MYSQL_EXEC}.

И подтвердили, что все исчезло, сделав несколько попыток показать нашу базу данных.

Теперь мы можем использовать Kanister, чтобы вернуть наши важные данные в рабочее состояние, используя команду kubectl get actionset -n kanister, чтобы узнать имя нашего ActionSet, который мы взяли ранее. Затем мы создадим ActionSet восстановления для восстановления наших данных, используя kanctl create actionset -n kanister --action restore --from "backup-qpnqv".

Мы можем подтвердить, что наши данные восстановлены, используя следующую команду для подключения к нашей базе данных.

APP_NAME=my-production-app
kubectl run mysql-client --rm --env APP_NS=${APP_NAME} --env MYSQL_EXEC="${MYSQL_EXEC}" --env MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD} --env MYSQL_HOST=${MYSQL_HOST} --namespace ${APP_NAME} --tty -i --restart='Never' --image  docker.io/bitnami/mysql:latest --command -- bash

Теперь мы находимся внутри клиента MySQL, мы можем выполнить команду echo "SHOW DATABASES;" | ${MYSQL_EXEC} и мы увидим, что база данных восстановлена. Мы также можем выполнить команду echo "select * from Accounts;" | ${MYSQL_EXEC} для проверки содержимого базы данных, и наши важные данные будут восстановлены.

В следующем посте мы рассмотрим аварийное восстановление в Kubernetes.

Ресурсы