В первой части мы рассмотрели подходы к созданию резервных копий контейнеров в кластере Kubernetes с использованием restic над каталогом данных и относительно новых возможностей CSI для создания и восстановления мгновенных снимков. Пришло время поговорить о возможностях автоматизации управления резервными копиями, о мониторинге процесса и иных важных DevOps-задачах.

Прежде всего для автоматизации нужно решить вопрос с доступом к данным и исключить необходимость установки дополнительных приложений (например. restic) на управляющий узел. В основном сценарии развертывания сервисы предполагается, что мы используем возможности Kubernetes по выделению емкости системы хранения через PersistentVoIumeClaim с режимом доступа ReadWriteOnce, который допускает монтирование раздела для записи только на один контейнер, что затрудняет восстановление данных из резервной копии. Также для создания резервной копии было бы желательно, чтобы доступ к данным был также у специального контейнера, содержащего все необходимые инструменты (например, Restic). Попробуем разобраться как это может быть реализовано и поговорим о концепции sidecar-контейнеров.

Прежде всего посмотрим на конфигурацию развертывания контейнера (может быть Deployment, DaemonSet или StatefulSet) и обнаружим, что в спецификации шаблона развертывания определение контейнеров перечисляется в списке containers. Такая схема определения позволяет запускать несколько контейнеров внутри одного Pod и все контейнеры будут разделять общий набор PersistentVolumeClaim и иметь доступ к опубликованным сетевым сервисам через localhost (все контейнеры в Pod разделяют общий ip-адрес). Таким образом можно добавить дополнительный контейнер с restic в развертывание и примонтировать к нему именованный раздел (в соответствии с названием в volumes или volumeClaimTemplates) и использовать его для получением доступа на чтение к данным из хранилища основного контейнера. Такой подход получил название "sideсаг-контейнер" и позволяет расширить функциональность основного контейнера без влияния на его доступность и конфигурацию. Так, мы можем добавить запуск образа контейнера, содержащего restic и подключить раздел с данными дополнительно к нему. Основная проблема состоит в том, что контейнер из образа restic/restic указывает точку входа в консольную утилиту restic и будет непрерывно перезапускаться. Чтобы этого избежать, можно поменять точку входа и отметить, что контейнер должен выполниться в режиме ожидания и мы в дальнейшем сможем подключиться к нему через kubectl ехес и выполнить команду restic backup когда наступит время для создания резервной копии. Также необходимо примонтировать раздел для хранения данных restic из системы хранения к соответствующему контейнеру, а также передать пароль для шифрования резервной копии через переменные окружения контейнера (может быть получен из Secret).

kubectl create secret -n postgres generic restic --from-literal=password=topsecret
postgres.yaml

apiVersion: v1
kind: Namespace
metadata:
  name: postgres
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  namespace: postgres
  name: postgres
spec:
  selector:
    matchLabels:
      app: postgres
  serviceName: postgres
  replicas: 1
  template:
    metadata:
      labels:
        app: postgres 
    spec:
      containers:
        - name: postgres
          image: postgres:13
          ports:
            - name: postgres
              containerPort: 5432
              protocol: TCP
          env:
            - name: POSTGRES_USER
              value: postgres
            - name: PGUSER
              value: postgres
            - name: POSTGRES_DB
              value: postgres
            - name: PGDATA
              value: /var/lib/postgresql/data/pgdata
            - name: POSTGRES_PASSWORD
              value: password
            - name: POD_IP
              valueFrom:
                fieldRef:
                  apiVersion: v1
                  fieldPath: status.podIP
          livenessProbe:
            exec:
              command:
                - sh
                - -c
                - exec pg_isready --host $POD_IP
            failureThreshold: 6
            initialDelaySeconds: 60
            periodSeconds: 10
            successThreshold: 1
            timeoutSeconds: 5
          readinessProbe:
            exec:
              command:
                - sh
                - -c
                - exec pg_isready --host $POD_IP
            failureThreshold: 3
            initialDelaySeconds: 5
            periodSeconds: 5
            successThreshold: 1
            timeoutSeconds: 3
          volumeMounts:
            - mountPath: /var/lib/postgresql/data/pgdata
              name: postgres
              subPath: postgres-db
        - name: restic
          image: restic/restic
          command: ["/bin/sh"]
          args: ["-c", "while true; do sleep 360; done"]
          env:
          - name: RESTIC_PASSWORD
            valueFrom:
              secretKeyRef:
                name: restic
                key: password
          volumeMounts:
            - mountPath: /data
              name: postgres
              subPath: postgres-db
            - mountPath: /backup
              name: backup
  volumeClaimTemplates:
  - metadata:
      name: postgres
    spec:
      storageClassName: "standard"
      accessModes: ["ReadWriteOnce"]
      resources:
        requests:
          storage: 4Gi
  - metadata:
      name: backup
    spec:
      storageClassName: "standard"
      accessModes: ["ReadWriteOnce"]
      resources:
        requests:
          storage: 4Gi

После применения конфигурации можно обнаружить, что теперь Pod запущен с двумя контейнерами:

$ kubectl apply -f postgres.yaml
$ kubectl get pod -n postgres

NAME         READY   STATUS    RESTARTS   AGE
postgres-0   2/2     Running   0          11s

И теперь мы можем выполнить инициализацию каталога для управления резервными копиями (однократно) и создать мгновенный снимок данных с использованием sidecar-контейнера restic:

kubectl exec -it -n postgres postgres-0 -c restic -- restic init --password-command "echo $RESTIC_PASSWORD" -r /backup
kubectl exec -it -n postgres postgres-0 -c restic -- restic backup --password-command "echo $RESTIC_PASSWORD" -r /backup /data

Для восстановления можно указать в target непосредственно каталог, куда примонтирован раздел оригинального контейнера (для sidecar ограничений на запись нет, поскольку все контейнеры разделяют единый PVC-раздел):

kubectl exec -it -n postgres postgres-0 -c restic -- restic restore --password-command "echo $RESTIC_PASSWORD" -r /backup latest --target /data

Для создания снимка (как и в первой части статьи) лучше, разумеется, использовать собственные инструменты исходного сервиса (например, pg_dump) и это также может быть предусмотрено в sidecar-контейнере. Образ для выгрузки состояния сервиса и восстановления может быть создан самостоятельно, например для PostgreSQL Dockerfile может выглядеть следующим образом:

FROM restic/restic

RUN apk update && \
    apk add postgresql-client && \
    echo '#!/bin/sh' >/opt/init.sh && \
    echo 'restic init --password-command "echo $RESTIC_PASSWORD" -r /backup' >>/opt/init.sh && \
    echo '#!/bin/sh' >/opt/backup.sh && \
    echo 'mkdir -p /data' >>/opt/backup.sh && \
    echo 'pg_dumpall -Upostgres -h localhost >/data/postgres.dump' >>/opt/backup.sh && \
    echo 'restic backup --password-command "echo $RESTIC_PASSWORD" -r /backup /data' >>/opt/backup.sh && \
    echo '#!/bin/sh' >/opt/restore.sh && \
    echo 'restic restore --echo "echo $RESTIC_PASSWORD" -r /backup --target /' >>/opt/restore.sh && \
    echo 'psql -Upostgres -h localhost </data/postgres.dump' >>/opt/restore.sh && \
    chmod +x /opt/backup.sh && \
    chmod +x /opt/restore.sh && \
    chmod +x /opt/init.sh

ENTRYPOINT /bin/sh

Созданный контейнер также может быть добавлен как sidecar к основному процессу PostgreSQL и предусматривает инициализацию репозитория (kubectl exec -it -n postgres postgres-0 -c restic /opt/init.sh), создание резервной копии из дампа базы (kubectl exec -it -n postgres postgres-0 -c restic /opt/backup.sh) и восстановление из последнего доступного дампа из репозитория (kubectl exec -it -n postgres postgres-0 -c restic /opt/restore.sh). Аналогично могут быть созданы контейнеры для других сервисов, либо можно воспользоваться существующими на Docker Hub:

Основной недостаток рассмотренного выше решения - необходимость ручного изменения конфигурации развертывания сервисов, запуска сценариев создания резервной копии и восстановления, подключения дополнительных контейнеров с монтированием PersistentVolumeClaim к соответствующим каталогам, где ожидается получить данные в сценарии резервного копирования. Можно ли как-то автоматизировать этот процесс? Да, и в этом нам поможет Dynamic Admission Control (а именно использование Mutating Webhooks).

Регистрация ресурсов ValidatingWebhooks (admissionregistration.k8s.io/v1/ValidatingWebhookConfiguration) и MutatingWebhooks (admissionregistration.k8s.io/v1/MutatingWebhookConfiguration) позволяют добавить дополнительную обработку при создании/изменении/удалению ресурсов (например, Deployment) и добавляют возможности для дополнительных проверок конфигурации (при валидации) или изменения создаваемого ресурса "на лету" (при мутации). Конфигурация мутации определяет адрес API для вызова при возникновении подходящих условий и набор правил (rules), описывающих на какие группы и версии API применяется правило (apiGroups, apiVersions), какие операции отслеживаются (например, CREATE, UPDATE), какие типы ресурсов будут наблюдаться (resources). Часто установка вебхуков выполняется при установке операторов - контейнеров, самостоятельно управляющих ресурсами для развертывания управляемого сервиса и регистрирующих точки подключения к службам, а также дополнительные версии API и типы ресурсов (используется механизмы CRD - Custom Resource Definition). Кроме возможностей встраивания дополнительных действий в процесс развертывания ресурсов такой подход позволяет создавать предметно-специфические типы и метаданные для ресурсов (например, для описания расписания резервного копирования).

Рассмотрим несколько операторов, представляющих возможности для управления резервным копированием:

Velero

Продукт с открытым исходным кодом (ранее назывался Haptio Ark), позволяет выполнять резервное копирование ресурсов кластера Kubernetes и управлять резервными копиями PersistentVolume через утилиту командной строки velero. Для настройки процесса копирования (например, добавления pre- и post-сценариев для подготовки и очистки дампов) используются аннотации в метаданных развертывания. Поддерживаются также возможности CSI Snapshotting.

Для создания резервной копии используется restic и внешние хранилища, которые могут быть подключены через плагины. Создание резервной копии выполняется командой velero backup.

velero backup create postgres --ordered-resources 'statefulsets=postgres/postgres' --include-namespaces=postgres

Также можно запланировать резервное копирование по расписанию:

velero schedule create postgres --schedule="* * * * *"

Расписание задается в синтаксисе Cron (минуты часы день месяц день_недели).

K8Up

Оператор для управления ресурсами резервного копирования. Позволяет работать с PV-разделами с типом доступа ReadWriteMany.

Установка может быть выполнена через helm:

helm repo add appuio https://charts.appuio.ch
helm repo update
helm install k8up appuio/k8up --create-namespace --namespace k8up-operator

После установки регистрируется версия API k8up.io/v1 и дополнительные ресурсы:

  • Backup - задает конфигурацию репозитория для restic

  • PreBackupPod - описание sidecar-контейнера и команды для запуска для подготовки дампа системы (также может быть задано в аннотации k8up.io/backupcommand у любого развертывания).

  • Schedule - определить расписание резервного копирования (в синтаксисе Cron).

  • Archive - создание холодной копии restic-репозитория для долговременного хранения.

Stash

Оператор использует механизмы Mutating Webhooks для установки sidecar-контейнеров с поддержкой restic в существующие/новые/изменяемые развертывания. Stash позволяет настраивать резервное копирование для PVC-разделов с режимом доступа ReadWriteOnce, поскольку управляющий контейнер добавляется к существующим подам и разделяет с ними общий доступ к хранилищам и ip-адрес.

$ helm repo add appscode https://charts.appscode.com/stable/
$ helm repo update
$ helm install stash appscode/stash \
  --version v2022.05.18 \
  --namespace stash --create-namespace \
  --set features.community=true               \
  --set-file global.license=/path

Файл opensource лицензии запрашивается на сайте проекта. После установки регистрируется новая версия API stash.appscode.com/v1alpha1 и stash.appscode.com/v1beta1 и дополнительные ресурсы:

  • Repository - описание расположения репозитория для Restic (может быть один на все развертывания или отдельный под каждое из них), поддерживается S3, Google Cloud Storage, Microsoft Azure Storage, а также REST для отправки в произвольный веб-ресурс.

  • Snapshot - обертка вокруг возможностей управления снимками Kubernetes.

  • BackupConfiguration - конфигурация резервного копирования (также может включать в себя hooks для подготовки данных и их очистки после завершения процесса).

  • BackupBlueprint - создание шаблона для описания резервного копирования, на него можно ссылаться в развертываниях через аннотации:

    • stash.appscode.com/backup-blueprint - название шаблона;

    • stash.appscode.com/schedule - расписание резервного копирования.

  • Function - шаг подготовки резервной копии, может включать в себя создание дампа базы данных или иные операции, необходимые для создания согласованной выгрузки. Функция определяется со ссылкой на образ sidecar-контейнера (для PostgreSQL можно использовать stashed/postgres-stash) и командную строку для запуска действия. Также функция может использоваться для отправки метрик по резервному копированию в Prometheus (образ appscode/stash:0.10.0, команда update-status). При описании аргументов команды можно использовать переменные окружения, предоставленные Stash (в том числе, информация о расположении ресурса).

  • Task - объединение последовательности функций (перечисляются как step) с параметрами. Task может быть указан для исполнения в описании конфигурации BackupConfiguration или BackupBlueprint.

  • RestoreSession - выполнение восстановления из указанного репозитория в каталог (обычно связывается с PVC по имени, в соответствии с названием в volumes/persistentVolumeClaims развертывании).

Пример конфигурации для настройки резервного копирования базы данных:

apiVersion: stash.appscode.com/v1beta1
kind: BackupConfiguration
metadata:
  name: postgres
  namespace: postgres
spec:
  driver: Restic
  repository:
    name: postgres
  schedule: "0 * * * *"		#каждый час
  target:
    alias: app-data
    ref:
      apiVersion: apps/v1	#определение ресурса для резервного копирования
      kind: StatefulSet
      name: postgres
    paths:
    - /source/data				#каталоги для копирования (обычно сюда монтируется PVC)
    exclude:
    - /source/data/tmp/*	#исключения из копирования
    volumeMounts:
    - name: data
      mountPath: /source/data
  hooks:
    preBackup:
      exec:
        command:					#сценарий для запуска перед выполнением restic
          - /bin/sh
          - -c
          - echo "Sample PreBackup hook demo"
      containerName: my-app-container
    postBackup:						#сценарий для очистки после выполнения restic
      exec:
        command:
          - /bin/sh
          - -c
          - echo "Sample PostBackup hook demo"
      containerName: my-app-container
  retentionPolicy:				#политика удаления старых копий (здесь - оставить 5 последних)
    name: 'keep-last-5'
    keepLast: 5
    prune: true

При выполнении резервной копии создается ресурс BackupSession, из которого можно получить информацию о состоянии. При успешном применении можно увидеть, что к развертыванию, которое указано в .spec.target.ref присоединяется sidecar-контейнер stashed/stash с запуском команды run-backup.

По умолчанию доступны 3 функции: update-status (отправка метрик в Prometheus), pvc-backup (сохранение данных из автономного Read-Write-Many PVC), pvc-restore (восстановление в автономный PVC). Для создания своей функции можно реализовать образ контейнера с точкой входа для создания дампа базы данных и отправки в restic-репозиторий, для настройки можно использовать переменные окружения:

  • REPOSITORY_PROVIDER - провайдер для репозитория (например, S3);

  • REPOSITORY_BUCKET - название S3 Bucket;

  • REPOSITORY_ENDPOINT - адрес для подключения к S3 (или другому хранилищу);

  • REPOSITORY_PREFIX - префикс пути в репозитории;

  • REPOSITORY_SECRET_NAME - название секрета, содержащего пароль к репозиторию;

  • REPOSITORY_SECRET_NAMESPACE - пространство имен, где хранится секрет репозитория;

  • RETENTION_KEEP_LAST - количество резервных копий для сохранения и ротации.

Пример определения функции можно посмотреть здесь. Далее полученная функция интегрируется в задачу:

apiVersion: stash.appscode.com/v1beta1
kind: Task
metadata:
  name: postgres-backup
spec:
  steps:
  - name: postgres-backup
    params:
    - name: secretVolume
      value: secret-volume
  - name: update-status
    params:
    - name: outputDir
      value: /tmp/output
    - name: secretVolume
      value: secret-volume
  volumes:
  - name: secret-volume
    secret:
      secretName: ${REPOSITORY_SECRET_NAME}

И далее задача может быть добавлена к конфигурации резервного копирования:

apiVersion: stash.appscode.com/v1beta1
kind: BackupConfiguration
metadata:
  name: postgres
  namespace: postgres
spec:
  driver: Restic
  repository:
    name: postgres
  task:
    name: postgres-backup
    ...

Оператор Stash поддерживает присоединение sidecar-контейнеров к DaemonSet (добавляется к каждому запущенному Pod на всех допустимых узлах), StatefulSet (развертывание пересоздается с подключением дополнительного контейнера для каждой реплики сервиса), Deployment (дополнительный контейнер добавляется к существующему развертыванию без перезапуска).

Итак мы рассмотрели различные подходы к управлению резервным копированием, включая автоматическое управление через CRD-ресурсы в операторах Stash и k8up. Сейчас нет общепринятого решения для управления резервными копиями и появляется большое количество инструментов, основанных на CSI Snapshotting (например, Gemini или TrilioVault), но мы рассмотрели наиболее популярные и функциональные продукты, которые могут помочь вам настроить предсказуемое и надежное резервное копирование и избавить от переживаний о возможной потере данных.

Так;е хочу напомнить о том, что уже 2 июня мои коллеги из OTUS проведут бесплатный урок по теме: "Контроллеры репликации, сеты репликации и балансировка нагрузки". Регистрация на урок доступна по ссылке ниже.

Зарегистрироваться на бесплатный урок.

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