Стадия жизненного цикла модуля: Общедоступная версия

У модуля есть требования для установки

Использование снапшотов

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

d8 k get volumesnapshotclasses.snapshot.storage.k8s.io

Затем вы сможете использовать VolumeSnapshotClass для создания снапшота из существующего тома:

apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
  name: my-first-snapshot
spec:
  volumeSnapshotClassName: sds-replicated-volume
  source:
    persistentVolumeClaimName: my-first-volume

Спустя небольшой промежуток времени снапшот будет готов:

$ d8 k describe volumesnapshots.snapshot.storage.k8s.io my-first-snapshot
...
Spec:
  Source:
    Persistent Volume Claim Name:  my-first-snapshot
  Volume Snapshot Class Name:      sds-replicated-volume
Status:
  Bound Volume Snapshot Content Name:  snapcontent-b6072ab7-6ddf-482b-a4e3-693088136d2c
  Creation Time:                       2020-06-04T13:02:28Z
  Ready To Use:                        true
  Restore Size:                        500Mi

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

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: my-first-volume-from-snapshot
spec:
  storageClassName: sds-replicated-volume-data-r2
  dataSource:
    name: my-first-snapshot
    kind: VolumeSnapshot
    apiGroup: snapshot.storage.k8s.io
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 500Mi

Клонирование CSI-томов

На основе концепции снапшотов также можно выполнить клонирование Persistent Volume, а именно, существующих PersistentVolumeClaim (PVC). Однако спецификация CSI допускает ряд ограничений при клонировании томов в неймспейсах или StorageClass-ах, отличных от оригинального PVC. Подробнее об ограничениях см. в документации Kubernetes.

Чтобы клонировать том, создайте новый PVC и укажите исходный PVC в dataSource:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: my-cloned-pvc
spec:
  storageClassName: sds-replicated-volume-data-r2
  dataSource:
    name: my-origin-pvc
    kind: PersistentVolumeClaim
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 500Mi

Экспорт и импорт содержимого томов по HTTP

Создание ресурсов DataExport и DataImport с помощью утилиты d8

Для создания и управления ресурсами DataExport и DataImport используется утилита d8.

Создание DataExport — целевой том передаётся позиционным аргументом <тип>/<имя>:

d8 data export create -n <namespace> <имя DataExport> <тип>/<имя> --ttl 5m --publish=(true/false)

Поддерживаемые типы целевых ресурсов: pvc/persistentvolumeclaim, vs/volumesnapshot, vd/virtualdisk, vds/virtualdisksnapshot.

Создание DataImport — целевой PVC задаётся манифестом через -f (позиционного аргумента тома нет):

d8 data import create -n <namespace> <имя DataImport> -f <манифест-pvc> --ttl 2m --publish --wffc

Ресурсы PVC могут быть экспортированы только в том случае, если они не смонтированы какими-либо подами в данный момент.

Пример: создание ресурса DataExport для PVC с именем data в неймспейсе project с TTL 5 минут:

d8 data export create -n project my-export pvc/data --ttl 5m

Для просмотра деталей созданного ресурса DataExport:

d8 k -n project get de my-export

Для скачивания файлов из экспортированных томов:

d8 data export download -n <namespace> <тип ресурса (pvc/vs/dataexport)>/<имя ресурса>/<путь к файлу> -o <имя файла> --publish=true

Пример:

d8 data export download -n project dataexport/my-export -o test_file.txt --publish=true

Альтернативные способы создания и использования DataExport и DataImport без утилиты d8

Помимо утилиты d8, ресурсы DataExport и DataImport можно создавать напрямую через YAML-манифест. В примере ниже для удобства используются переменные окружения, замените их значения на нужные:

export NAMESPACE="d8-storage-foundation"
export DATA_EXPORT_RESOURCE_NAME="example-dataexport"
export TARGET_TYPE="PersistentVolumeClaim"
export TARGET_NAME="fs-pvc-data-exporter-fs-0"
d8 k apply -f -<<EOF
apiVersion: storage-foundation.deckhouse.io/v1alpha1
kind: DataExport
metadata:
  name: ${DATA_EXPORT_RESOURCE_NAME}
  namespace: ${NAMESPACE}
spec:
  ttl: 10h
  targetRef:
    kind: ${TARGET_TYPE}
    name: ${TARGET_NAME}
EOF

После создания ресурса извлеките CA-сертификат:

d8 k -n $NAMESPACE get dataexport $DATA_EXPORT_RESOURCE_NAME  -o jsonpath='{.status.ca}' | base64 -d > ca.pem

Проверьте сертификат:

openssl x509 -in ca.pem -noout -text | head

Пример вывода:

Issuer: CN = data-exporter-CA
Signature Algorithm: ecdsa-with-SHA256

Извлеките URL из ресурса DataExport и проверьте экспорт:

export POD_URL=$(d8 k -n $NAMESPACE get dataexport $DATA_EXPORT_RESOURCE_NAME  -o jsonpath='{.status.url}')
echo "POD_URL: $POD_URL"

После создания ресурса DataExport и извлечения необходимых данных подключиться к экспортеру можно одним из следующих способов.

1. Аутентификация с помощью сертификата и ключа из локального kubeconfig

Этот способ использует существующие учётные данные из локального файла kubeconfig:

cat ~/.kube/config | grep "client-certificate-data" | awk '{print $2}' | base64 -d > client.crt
cat ~/.kube/config | grep "client-key-data" | awk '{print $2}' | base64 -d > client.key

Проверьте содержимое целевого PVC:

curl -v --cacert ca.pem ${POD_URL}api/v1/files/ --key client.key --cert client.crt

2. Аутентификация с помощью токена и ролей

Этот способ предполагает создание отдельного ServiceAccount с нужными правами доступа:

d8 k -n $NAMESPACE create serviceaccount data-exporter-test

Создайте токен:

export TOKEN=$(d8 k -n $NAMESPACE create token data-exporter-test --duration=24h)
echo $TOKEN

Запросы к эндпоинтам данных запущенного экспорта или импорта — /api/v1/files… и /api/v1/block…, как по короткому пути, так и по полному — авторизуются через SubjectAccessReview на глагол create для субресурса download в API-группе storage-foundation.deckhouse.io: dataexports/download — при чтении данных из тома, dataimports/download — при записи данных в том. Обратите внимание: импорт авторизуется тем же субресурсом download, а не upload — субресурса upload не существует, поэтому роль, выдающая права на него, не даёт доступа вовсе.

Выдайте права сервис-аккаунту одним из двух способов ниже.

Привязка встроенной роли (предпочтительный способ)

В составе модуля поставляется ClusterRole d8:use:capability:module:storage-foundation:transfer_volume_data. Она выдаёт create на оба субресурса download, а также позволяет создавать, читать и удалять сами ресурсы DataExport и DataImport — это то, что нужно клиенту, чтобы выполнить перенос самостоятельно. Роль не сложена ни в один из общекластерных уровней доступа, поэтому вместе с таким уровнем права переноса не приходят.

При этом роль входит в набор, который собирает ClusterRole d8:use:capability:module:state-snapshotter:backup_agent — одна роль, выдаваемая внешней системе резервного копирования вместо нескольких отдельных. Этот агрегат тоже выдаётся только явной привязкой, поэтому сами по себе права никому не достаются; но действуют они в каждом namespace, где привязана либо эта роль, либо агрегат. Учитывайте это при аудите того, кто может вычитать том из кластера или записать в него данные: привязки, у которых в roleRef стоит эта роль, — ещё не полный ответ, привязки агрегата дают то же самое. В кластере без модуля, в котором определён агрегат, агрегата не существует, и значимы только привязки этой роли.

Привяжите роль к сервис-аккаунту:

d8 k create -f - <<EOF
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: data-exporter-test-transfer-binding
  namespace: ${NAMESPACE}
subjects:
- kind: ServiceAccount
  name: data-exporter-test
  namespace: ${NAMESPACE}
roleRef:
  kind: ClusterRole
  name: d8:use:capability:module:storage-foundation:transfer_volume_data
  apiGroup: rbac.authorization.k8s.io
EOF
Создание роли вручную

Если встроенная роль разрешает больше, чем вы хотите выдать, создайте свою ClusterRole только с нужными субресурсами. Роль ниже разрешает скачивание экспортированных данных и ничего больше; добавьте dataimports/download в resources, чтобы разрешить ещё и загрузку данных в том:

d8 k create -f - <<EOF
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: data-exporter-test-role
rules:
- apiGroups: ["storage-foundation.deckhouse.io"]
  resources: ["dataexports/download"]
  verbs: ["create"]
EOF

Такая роль покрывает только HTTP-запросы: сами ресурсы DataExport и DataImport должен создать тот, у кого есть отдельные права на них.

Создайте RoleBinding:

d8 k create -f - <<EOF
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: data-exporter-test-role-binding
  namespace: ${NAMESPACE}
subjects:
- kind: ServiceAccount
  name: data-exporter-test
  namespace: ${NAMESPACE}
roleRef:
  kind: ClusterRole
  name: data-exporter-test-role
  apiGroup: rbac.authorization.k8s.io
EOF
Проверка доступа

Какую бы из двух ролей вы ни привязали, проверьте содержимое целевого PVC:

curl -H "Authorization: Bearer $TOKEN" \
-v --cacert ca.pem ${POD_URL}api/v1/files/

HTTP API: экспорт данных

Все эндпоинты доступны по короткому пути /api/v1/files|block и по полному пути /{namespace}/{kindShort}/{name}/api/v1/files|block.

Обозначения:

  • kindShort — короткое имя экспортируемого ресурса (persistentVolumeClaim => pvc);
  • name — имя экспортируемого ресурса.

Файловый режим (экспорт)

  • Получить файл
    GET /api/v1/files/{path} HTTP/1.1

    Query (опционально, для дополнительных атрибутов):
      attribute=stat      — вернуть статические атрибуты (uid, gid, права, modtime)
      attribute=hash.md5  — вернуть MD5 для обычных файлов
  • Просмотр содержимого директории
    GET /api/v1/files/{path/} HTTP/1.1  # путь к директории должен заканчиваться на '/'
  • Метаданные без тела
    HEAD /api/v1/files/{path|path/} HTTP/1.1

Основные ошибки файлового режима:

    400 Bad Request  — несоответствие типа (файл/директория), запрещённые ссылки и т. п.
    404 Not Found    — путь не найден
    405 Method Not Allowed — методы, отличные от GET/HEAD
    500 Internal Server Error — внутренняя ошибка

Блочный режим (экспорт)

  • Скачать блочное устройство (raw-образ)
    GET /api/v1/block HTTP/1.1
  • Метаданные без тела
    HEAD /api/v1/block HTTP/1.1

HTTP API: импорт данных

Все эндпоинты доступны по короткому пути /api/v1/files|block и по полному пути /{namespace}/{kindShort}/{name}/api/v1/files|block.

Файловый режим (импорт)

  • Загрузить файл
    PUT /api/v1/files/{path} HTTP/1.1
    Content-Length: <bytes> — обязательный заголовок
    X-Attribute-Permissions: 0775 — обязательный заголовок (восьмеричное 0000–0777)
    X-Attribute-Uid: <uid> — обязательный заголовок
    X-Attribute-Gid: <gid> — обязательный заголовок
    X-Offset: 0 — смещение от начала файла (неотрицательное целое)
    X-Content-Length: 10 — обязательный заголовок, ожидаемый полный размер файла
    X-Attribute-ModTime: метка времени RFC3339
  • Запрос текущего прогресса загрузки
    HEAD /api/v1/files/{path} HTTP/1.1
    Content-Length: 0
  • Завершение импорта (сигнал на остановку файлового сервера)
    POST /api/v1/finished HTTP/1.1
    Content-Length: 0

Блочный режим (импорт)

  • Загрузить raw-образ блочного устройства
    PUT /api/v1/block HTTP/1.1
    Content-Length: <bytes> — обязательный заголовок
    X-Attribute-Permissions: 0775 — обязательный заголовок (восьмеричное 0000–0777)
    X-Attribute-Uid: <uid> — обязательный заголовок
    X-Attribute-Gid: <gid> — обязательный заголовок
    X-Offset: 0 — опционально; смещение от начала устройства
    X-Content-Length: 10 — опционально; ожидаемый полный размер
  • Запрос текущего прогресса загрузки
    HEAD /api/v1/block HTTP/1.1
    Content-Length: 0
  • Завершение импорта (сигнал на остановку файлового сервера)
    POST /api/v1/finished HTTP/1.1
    Content-Length: 0

Важные замечания по работе с API

При работе с экспортированными данными через HTTP API учитывайте следующее:

  • Скачивание файлов: файлы скачиваются обычными GET-запросами с указанием пути к файлу в URL, например GET /api/v1/files/largeimage.iso. Путь к файлу не должен заканчиваться на /. Этот способ поддерживается стандартными инструментами (браузеры, curl и т. д.). Возобновление скачивания поддерживается, сжатие — нет.
  • Просмотр директорий: доступ к директории выполняется аналогичным GET-запросом, где путь к директории должен заканчиваться на /, например GET /api/v1/files/ для корня или GET /api/v1/files/directory/ для поддиректории.
  • Список файлов: при обращении к директории в теле ответа возвращается JSON со списком файлов (имя, тип и размер). Размеры файлов не кэшируются и пересчитываются при каждом запросе.

Модуль storage-volume-data-manager можно держать включённым одновременно с storage-foundation. Оба модуля продолжают работать: каждый обслуживает свои собственные ресурсы DataExport и DataImport независимо — своя API-группа, своё пространство имён и свои финализаторы на задействованных томах. При этом один и тот же живой том (PersistentVolumeClaim или VirtualDisk) следует экспортировать только через один из двух модулей. Причин тому две, и они независимы:

  • Публичные адреса совпадают. Оба модуля собирают status.publicURL на одном и том же хосте и из одного и того же пути /<namespace>/<вид-таргета>/<имя-таргета>, поэтому экспорт одного и того же тома получает один и тот же публичный адрес независимо от того, какой из двух модулей его создал. Если экспорт удалить в одном модуле и затем создать для того же тома в другом, адрес переиспользуется, и клиент, запомнивший адрес, не сможет определить, чей экспорт ему отвечает. Всегда берите адрес из поля status.publicURL того ресурса, который создали сами.
  • Экспорт живого тома забирает его PersistentVolume. Том на время экспорта перепривязывается к экспортеру, поэтому один и тот же том нельзя экспортировать через оба модуля одновременно: экспорт, начатый вторым, до публикации не доходит вовсе и завершается конфликтом за PersistentVolume. Прежде чем экспортировать тот же том через другой модуль, удалите первый экспорт или дождитесь истечения его срока.