Разработчикам и системным администраторам важно обеспечивать надежность и доступность данных в приложениях Kubernetes. Несмотря на высокую степень автоматизации и управления контейнерами, всегда остаются риски сбоев или человеческих ошибок — поэтому необходимо заранее задуматься о резервном копировании.

Меня зовут Филипп, я системный администратор в отделе Data- и ML-продуктов Selectel. В этой статье постараюсь раскрыть, какие есть решения для резервного копирования в Kubernetes, и на простом примере покажу, как с ними работать. Подробности под катом.

Оглавление

Резервное копирование в Kubernetes
Какие есть варианты
Подготовка рабочего окружения
Резервное копирование на базе K8up
Резервное копирование на базе Kasten K10 by Veeam

Резервное копирование в Kubernetes


Динамичная и распределенная архитектура Kubernetes обязывает использовать резервные копии. Необходимо учитывать состояние каждого контейнера, конфигурации, секрета и блока данных, которые находятся в постоянном хранилище.


Важно отметить, что в Kubernetes управление постоянным хранилищем осуществляется через основные компоненты:

  • PVC (Persistent Volume Claim) — это запрос на использование хранилища со стороны приложения или пользователя.
  • PV (Persistent Volume) — Физическое или логическое хранилище в кластере, которое может быть связано с внешними хранилищами.
  • Storage Class — определяет, как PV создается.
  • CSI (Container Storage Interface) — плагин, который позволяет взаимодействовать с различными внешними хранилищами.
  • Внешнее хранилище: Облачное или локальное хранилище, где фактически хранятся данные.


Также Kubernetes поддерживает парадигму Infrastructure as code, IaC. Это позволяет автоматизировать и стандартизировать развертывание и управление ресурсами с помощью GitOps. Однако даже в таком случае есть важная деталь, которую нельзя игнорировать: сохранность данных.

Пока ресурсы типа config-map, secret, deployment, service, ingress и другие могут быть воссозданы из кода по IaC, данные в PV восстановить в случае сбоя, удаления или взлома не получится.


Какие есть варианты




Можно придумать разные способы создания резервной копии постоянных данных. Это могут быть, например, кастомные скрипты, которые запускаются по расписанию (cron) и создают снапшоты (снимки) самих PV.

При создании снимков PV могут возникнуть проблемы, если во время процесса в базу данных активно записываются данные. Например, снапшот может получиться неполным или поврежденным. Тогда при попытке восстановиться есть риск, что база данных запустится некорректно.

Однако есть сложные системы, которые обеспечивают непрерывное резервное копирование и автоматическое восстановление в случае сбоя. Среди них — Velero.io, Kaste K10, Cohesity, Portworx и другие.

Выбор стратегии резервного копирования будет зависеть от текущей реализации. Например, если ваше высоконагруженное приложение запущено в Managed Kubernetes, а стремительно растущие базы данных — на сетевых дисках, необходимо подумать о том, чтобы резервное копирование не влияло на работоспособность IT-систем.

Например, если основная база данных — PostgreSQL, необходимо подумать о бэкапировании с помощью streaming replication и настроить резервное копирование на secondary-реплике через pg_dump, pg_basebackup, Barman или WAL-E. А также — определиться с периодичностью бэкапирования, его шифрованием, способом хранения, мониторингом и проверкой восстановления из резервных копий.

Далее мы рассмотрим два инструмента K8UP и Kasten K10 от Veeam, которые предоставляют различные функции для резервного копирования. Но прежде подготовим тестовый стенд в облаке Selectel.


Подготовка рабочего окружения


Подготовим тестовый стенд, в который установим простейшее приложение с постоянным хранилищем данных. За основу возьмем Managed Kubernetes, а в качестве хранилища резервных копий — объектное хранилище S3, к которому настроим доступ через сервисного пользователя и S3-ключ.

Подготовка кластера Managed Kubernetes


В панели управления нужно перейти в раздел Kubernetes:




Далее дождитесь статуса ACTIVE и скачайте kubeconfig для дальнейшей работы с кластером:


Для работы с кластером можно использовать Lens или Kubectl. Для kubectl необходимо указать переменную KUBECONFIG:

 #: export KUBECONFIG=<ПОЛНЫЙ ПУТЬ ДО KUBECONFIG ФАЙЛА>

В качестве проверки можно получить пространство имен кластера:

#: kubectl get namespace

Подготовка объектного хранилища



Далее нужно создать и настроить контейнер: ввести его название, выбрать Приватный тип, класс хранения Стандартное хранение и нажать кнопку.


Создание сервисного пользователя и ключа S3






Важно: S3 access и secret keys необходимо сохранить в надежном месте.

Установка тестового приложения в кластере


Теперь осталось установить в кластер простейшее тестовое приложение. Для этого подготовим манифест создания Storage Class, Deployment и Persistent Volume Claim и применим его:

# nginx-pvc.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: default
  annotations:
    storageclass.kubernetes.io/is-default-class: 'true'
provisioner: cinder.csi.openstack.org
parameters:
  availability: ru-9a
  fsType: ext4
  type: fast.ru-9a
reclaimPolicy: Delete
allowVolumeExpansion: true
volumeBindingMode: Immediate
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nginx-pvc
  namespace: default
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx-container
          image: nginx:latest
          ports:
            - containerPort: 80
          volumeMounts:
            - name: nginx-volume
              mountPath: /usr/share/nginx/html
      volumes:
        - name: nginx-volume
          persistentVolumeClaim:
            claimName: nginx-pvc

#: kubectl apply -f nginx-pvc.yaml

Готово — теперь можем начать работу с инструментами резервного копирования в Kubernetes.

Резервное копирование на базе K8up


K8up (/keɪtæpp/ или просто «кетчуп») — это оператор для автоматизации резервного копирования данных в Kubernetes, в основе которого лежит restic.

Возможности инструмента

  • Автоматическое копирование данных из PVC и БД.
  • Запуск резервного копирования по требованию и расписанию.
  • Сохранение копий в разных местах, включая Amazon S3 и Minio.
  • Восстановление данных через командную строку.
  • Возможность создавать резервные копии, которые учитывают особенности приложений и включают в себя результаты работы любого инструмента, способного выдавать данные в стандартный поток вывода.

Установка


Рассмотрим процесс установки и настройки K8up в подготовленном рабочем окружении. Для наглядности представим схему работы инструмента.


Устанавливаем Custom Resource Definition:

#: kubectl apply -f https://github.com/k8up-io/k8up/releases/download/k8up-4.4.1/k8up-crd.yaml

Для удобства устанавливаем K8up в отдельное пространство имен:

#: kubectl create namespace k8up
#: helm repo add k8up-io https://k8up-io.github.io/k8up
#: helm install -n k8up k8up k8up-io/k8up

Настройка хранилища для резервных копий


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

Секреты для доступа по S3

Ранее мы подготовили объектное хранилище. Теперь создадим секреты, чтобы у K8up был к нему доступ. Укажем токен доступа к S3 и пароль для restic-репозитория:

# secrets.yaml
apiVersion: v1
kind: Secret
metadata:
  name: "s3-secrets"
  namespace: k8up
stringData:
  bucket: "<BUCKET NAME>"
  password: ”<S3 SECRET KEY>”
  username: ”<S3 ACCESS KEY>”
---
apiVersion: v1
kind: Secret
metadata:
  name: "restic-repository-password"
  namespace: k8up
stringData:
  password: "secret_pass" # Это пароль для restic repository - создается при инициализации, его можно поменять на любой, желательно надежный и состоящий из более чем 16 символов

Применим описанный манифест в кластер:

#: kubectl apply -f secrets.yaml

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

Создание снимка из PVC


Теперь попробуем сделать бэкап данных приложения, которое мы задеплоили на этапе подготовки рабочего окружения. Для этого необходимо создать Custom Resource для K8up, тем самым объяснить, что и как мы хотим копировать.

Создадим CR-Backup, который сделает резервное копирование всех PVC в пространстве имен, где будет создан данный ресурс, а после — применим манифест:

# backup.yaml
apiVersion: k8up.io/v1
kind: Backup
metadata:
  name: k8up-test-swift
  namespace: k8up
spec:
  failedJobsHistoryLimit: 4
  successfulJobsHistoryLimit: 0
  backend:
    repoPasswordSecretRef:
      name: "restic-repository-password"
      key: "password"
    s3:
      accessKeyIDSecretRef:
        key: username
        name: s3-secrets
      bucket: test
      endpoint: s3.ru-1.storage.selcloud.ru
      secretAccessKeySecretRef:
        key: password
        name: s3-secrets

#: kubectl apply -f backup.yaml

После создания ресурса K8up автоматически начнет поиск и создание снимков всех PVC с режимами доступа к хранилищу типа RMX (ReadMany, Execute) и RWO (ReadWriteOnce). Если все было выполнено правильно, мы должны увидеть успешно завершенный под с именем “backup-k8up-....”, который подмонтировал к себе нужный PVC (в нашем случае — nginx-pvc) и создал снимок файловой системы с помощью restic, пример успешно завершенного пода представлен на скриншоте ниже:


Под K8up.


То же самое можно проверить через CLI с помощью команд:

#: kubectl get -A backups.k8up.io
#: kubectl get -A snapshots.k8up.io

В K8up также можно настроить резервное копирование по расписанию, добавить проверку целостности с помощью custom-resource — Schedule.

Восстановление из снимка PVC


Локальное восстановление


Можно установить утилиту restic и подмонтировать снимок локально, указав чувствительные данные для доступа к объектному хранилищу. Но прежде необходимо узнать SNAPSHOT ID:

RESTIC_REPOSITORY="s3://s3.ru-1.storage.selcloud.ru/<S3-BUCKET NAME>/<S3 K8UP FOLDER PATH>" \
AWS_ACCESS_KEY_ID="" \
AWS_SECRET_ACCESS_KEY="" \
RESTIC_PASSWORD="" \
restic snapshots

Теперь мы можем скопировать к себе резервную копию командой:

RESTIC_REPOSITORY="s3://s3.ru-1.storage.selcloud.ru/<S3-BUCKET NAME>/<S3 K8UP FOLDER PATH>" \
AWS_ACCESS_KEY_ID="" \
AWS_SECRET_ACCESS_KEY="" \
RESTIC_PASSWORD="" \
restic restore <SNAPSHOT ID> --target ~/Desktop/mnt/

В папке ~/Desktop/mnt/ можно увидеть все файлы, находящиеся в снимке.

Восстановление в кластере Custom Resource


Для демонстрации изменим index.html — вернем предыдущее состояние файла до внесения правок. Просто добавим какой-то текст в файл index.html, например Hacker destroyed this site!


Теперь восстановим предыдущее состояние файла в кластере, для этого необходимо создать customResource Restore и новый PVC, в который будем восстанавливать данные:

# restore.yaml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: restore-test-mfw
  namespace: k8up
  annotations:
    # set to "true" to include in future backups
    k8up.io/backup: "false"
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
---
apiVersion: k8up.io/v1
kind: Restore
metadata:
  name: restore-test-mfw
  namespace: k8up
spec:
  podSecurityContext:
    fsGroup: 65532
    fsGroupChangePolicy: OnRootMismatch
  restoreMethod:
    folder:
      claimName: restore-test-mfw
  backend:
    envFrom:
    - secretRef:
        name: "open-stack-secret"
    repoPasswordSecretRef:
      name: "restic-repository-password"
      key: "password"
    swift:
      path: "/container-path"
      container: "cmlp-testing-rpd-5921-ff-research-k8up"

В качестве восстановленной PVC можем указать новую, в которую восстановили бэкап при помощи следующей CLI команды:

kubectl patch -n k8up deployment nginx-deployment -p '{"spec":{"template":{"spec":{"volumes":[{"name":"nginx-volume","persistentVolumeClaim":{"claimName":"restore-test-mfw"}}]}}}}'

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

Создание бэкапа баз данных


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

В результате мы пришли к выводу, что делать бэкап базы данных лучше встроенными утилитами — например, pg_dump, mongodump и другими. K8up позволяет нам использовать механизм запуска команд в поде, к которому будут добавлены аннотации.

Добавление аннотаций


Процесс создания аннотаций и их описание есть в документации K8up. Ниже — основные конфигурации для них.

annotations:
        k8up.io/backupcommand: sh -c 'mongodump --username=$MONGODB_ROOT_USER --password=$MONGODB_ROOT_PASSWORD --archive'
            k8up.io/file-extension: .archive

Конфигурация аннотации MongoDB.

annotations:
      k8up.io/backupcommand: sh -c 'PGDATABASE="$POSTGRES_DB" PGUSER="$POSTGRES_USER" PGPASSWORD="$POSTGRES_PASSWORD" pg_dump --clean'
      k8up.io/file-extension: .sql

Конфигурация аннотации PostgreSQL.

<SNIP>
template:
  metadata:
    labels:
      app: my-db
    annotations:
      k8up.io/backupcommand: sh -c 'PGDATABASE="$POSTGRES_DB" PGUSER="$POSTGRES_USER" PGPASSWORD="$POSTGRES_PASSWORD" pg_dump --clean'
      k8up.io/file-extension: .sql
      k8up.io/backupcommand-container: postgres
  spec:
    containers:
      - name: pgbouncer
      - name: postgres
      - name: prometheus-exporter
        ...
<SNIP>

Сборка аннотации на определенный контейнер.

Резервное копирование на базе Kasten K10 by Veeam



Kasten K10 создан специально для Kubernetes и представляет собой платформу управления данными Cloud Native для операций «второго дня».

Возможности инструмента


Инструмент предоставляет командам DevOps простую, масштабируемую и безопасную систему для резервного копирования и восстановления приложений Kubernetes. Kasten K10 можно интегрировать с реляционными и NoSQL-базами данных и основными дистрибутивами Kubernetes.

Важно: бесплатная версия Kasten K10 ограничена пятью нодами.

Установка


PRE-FLIGHT проверки


Проверим наличие всех необходимых компонентов для Kasten K10:

#: curl <a href="https://docs.kasten.io/tools/k10_primer.sh">https://docs.kasten.io/tools/k10_primer.sh</a> | bash

В выводе команды мы увидим, чего не хватает для корректной работы K10. В нашем случае отсутствует CRD для создания снапшотов. Решим эту проблему.

Выведем имеющиеся для Volume CRD:

#: kubectl api-resources | grep volume

Теперь мы можем установить недостающие CRD и проверить, что они появились в списке api-resources:

#: kubectl apply -f https://raw.githubusercontent.com/selectel/mks-csi-snapshotter/master/deploy/setup-snapshot-controller.yaml

#: kubectl api-resources | grep volume
persistentvolumeclaims                pvc                     v1                                          true         PersistentVolumeClaim
persistentvolumes                     pv                      v1                                          false        PersistentVolume
volumesnapshotclasses                 vsclass,vsclasses   snapshot.storage.k8s.io/v1                  false        VolumeSnapshotClass
volumesnapshotcontents                vsc,vscs                snapshot.storage.k8s.io/v1                  false        VolumeSnapshotContent
volumesnapshots                       vs                      snapshot.storage.k8s.io/v1                  true         VolumeSnapshot
volumeattachments                                         storage.k8s.io/v1                           false        VolumeAttachment

Повторно прогоняем проверки PRE-FLIGHT:

#: curl <a href="https://docs.kasten.io/tools/k10_primer.sh">https://docs.kasten.io/tools/k10_primer.sh</a> | bash

Ошибка по CRD-Base пропала, но появилась новая: CSI Provisioner doesn't have VolumeSnapshotClass — Error. Все верно: CRD есть, а VolumeSnapshotClass отсутствует. Попробуем решить эту проблему.

Рабочий вариант — вручную подготовить манифест. Приведу пример для Cinder OpenStack. Подробнее о драйверах можно почитать на сайте.

apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshotClass
driver: cinder.csi.openstack.org
metadata:
  annotations:
    k10.kasten.io/is-snapshot-class: "true"
  name: csi-hostpath-snapclass-v1
deletionPolicy: Delete

Применим манифест и проверим наличие ресурса VolumeSnapshotClass:

#: kubectl apply -f VolumeSnapshotClass.yaml
volumesnapshotclass.snapshot.storage.k8s.io/cinder-csi-openstack-org created
#: kubectl get -f VolumeSnapshotClass.yaml
NAME                           DRIVER                         DELETIONPOLICY   AGE
csi-hostpath-snapclass-v1   cinder.csi.openstack.org   Delete               20s

Теперь можем снова запустить проверку PRE-FLIGHT:

#: curl https://docs.kasten.io/tools/k10_primer.sh | bash

Все пункты должны быть в статусе ОК.

Дополнительно необходимо добавить аннотацию k10.kasten.io/volume-snapshot-class в default storage-class:

#: kubectl annotate storageclass default \
    k10.kasten.io/volume-snapshot-class=csi-hostpath-snapclass-v1

На этом предварительные настройки для работы Kasten K10 закончены. Теперь можем перейти к установке самого инструмента.

Установка Kasten K10


Добавляем репозиторий Kasten:

#: helm repo add kasten https://charts.kasten.io/

Создаем отдельное пространство имен:

#: kubectl create namespace kasten-io

Устанавливаем Helm-чарт с включением sidecar kanister, который необходим для создания снапшотов. С версии Kasten K10 6.5.0 он по умолчанию выключен.

#: helm install k10 kasten/k10 --namespace=kasten-io --set injectKanisterSidecar.enabled=true --set-string injectKanisterSidecar.namespaceSelector.matchLabels.k10/injectKanisterSidecar=true

Дожидаемся полной установки компонентов и пробрасываем порт на локальный хост:

#: kubectl get pods --namespace kasten-io --watch

#: kubectl --namespace kasten-io port-forward service/gateway 8080:8000

Теперь можем открыть веб-интерфейс по следующему адресу:

http://127.0.0.1:8080/k10/#/dashboard

Настройка хранилища для резервных копий


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

Инициализация профиля S3


Как и в случае с K8up, настроим объектное хранилище в качестве основного для всех резервных копий. Это можно сделать как через веб-интерфейс, так и с помощью YAML-манифестов.

Создадим и настроим S3-профиль хранения резервных копий в объектном хранилище Selectel. Для этого перейдем на страницу профилей (http://127.0.0.1:8080/k10/#/profiles/location) — нас встретит такая страница:



Важно: в качестве endpoint нужно указать s3.ru-1.storage.selcloud.ru

Далее необходимо сохранить профиль. Если все сделали правильно, в списке увидите профиль со статусом VALID:


Настройка резервного копирования


Приступим к настройке периодических резервных копий нашего приложения.

Укажем label для пространства имен, куда установлено приложение. В нашем случае namespace=default:

#: kubectl label namespace default k10/injectKanisterSidecar=true

Создадим Policy. Это можно сделать из веб-панели Kasten K10, но мы воспользуемся манифестом:

#sample-backup-action.yam
apiVersion: config.kio.kasten.io/v1alpha1
kind: Policy
metadata:
  name: test
  namespace: kasten-io
spec:
  frequency: "@hourly"
  paused: false
  actions:
    - action: backup
      backupParameters:
        profile:
          name: selectel-s3
          namespace: kasten-io
    - action: export
      exportParameters:
        frequency: "@hourly"
        migrationToken:
          name: test-migration-token
          namespace: kasten-io
        exportData:
          enabled: true
      retention: {}
  retention:
    hourly: 24
    daily: 7
    weekly: 4
    monthly: 12
    yearly: 7
  selector:
    matchExpressions:
      - key: k10.kasten.io/appNamespace
        operator: In
        values:
          - default

Применим манифест в кластере:

#: kubectl create -f sample-backup-action.yaml

Если все сделано правильно, должен создаться backupaction. Его можно вывести с помощью специальной команды:

#: kubectl get backupactions.actions.kio.kasten.io
NAME              CREATED AT             STATE      PCT
scheduled-pwbvk   2023-11-02T09:08:00Z   Complete   100

Во вкладке Action веб-интерфейса можно проверить статус резервного копирования:


Копируем файл в приложение


Для проверки восстановления из резервной копии скопируем файл, например, Snapshot-class.yaml и вручную запустим бэкапирование. После удалим файл и восстановимся из резервной копии.

Копируем файл Snapshot-class.yaml:

#: kubectl cp Snapshot-class.yaml default/nginx-deployment-5dcc6d978b-lc96j:/usr/share/nginx/html/

Вручную запускаем резервное копирование. Это можно сделать на странице Policies:


Дожидаемся завершения резервного копирования (статус можно отслеживать на странице Dashboard), а после — удаляем файл из примонтированной папки в контейнере:

#: kubectl --namespace default exec -it nginx-deployment-5dcc6d978b-ps6rs -- /bin/sh
Defaulted container "nginx-container" out of: nginx-container, kanister-sidecar
# rm /usr/share/nginx/html/Snapshot-class.yaml

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


Теперь можем проверить, восстанавливается ли система из резервной копии.

Перейдем во вкладку Application → Item-Menu → Restore:


Выберем нужную точку восстановления:


В появившемся окне Restore Point выберем приложение, поставим галочку рядом с Data-Only Restore и нажмем кнопку Restore:


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


Готово — резервное копирование на базе Kasten K10 работает!

Подводим итоги


Организация резервного копирования — сложный и комплексный процесс. Необходимо подумать о надежном хранении бэкапов, мониторинге их состояния, а также проверке на восстановление из них.

В своей ML-платформе мы используем инструмент K8up для резервного копирования важной информации — например, служебных данных ClearML. В качестве хранилища используем объектное хранилище. K8up создает файловые копии один раз в день, а также проверяет их на целостность.

Если вам интересно посмотреть, как это работает на практике, протестируйте нашу ML-платформу. Мы разворачиваем ее индивидуально для каждого клиента и можем включить в сборку такие open source-инструменты, как ClearML или Kubeflow. В общем, все для того, чтобы вы смогли организовать полный цикл обучения и тестирования ML-моделей.

Комментарии (0)