Стадия жизненного цикла модуля: General Availability

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

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

export NAMESPACE="d8-storage-volume-data-manager"
export DATA_EXPORT_RESOURCE_NAME="example-dataexport"
export TARGET_TYPE="PersistentVolumeClaim"
export TARGET_NAME="fs-pvc-data-exporter-fs-0"
kubectl apply -f -<<EOF
apiVersion: storage.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-сертификат, выполнив следующую команду:

kubectl -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=$(kubectl -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

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

..
..

< 
* TLSv1.2 (IN), TLS header, Supplemental data (23):
{"apiVersion": "v1", "items": [{"name":"4.txt","size":12,"modTime":"2025-04-01 08:02:00.228156524 +0000 UTC","type":"file"}
,{"name":"hello","size":5,"modTime":"2025-03-03 10:53:06.895434814 +0000 UTC","type":"file"}
,{"name":"7.txt","size":12,"modTime":"2025-04-01 08:02:00.228156524 +0000 UTC","type":"file"}
,{"name":"lost+found","modTime":"2025-03-03 10:29:31 +0000 UTC","type":"dir"}
,{"name":"8.txt","size":12,"modTime":"2025-04-01 08:02:00.228156524 +0000 UTC","type":"file"}
,{"name":"10.txt","size":13,"modTime":"2025-04-01 08:02:00.228156524 +0000 UTC","type":"file"}
,{"name":"9.txt","size":12,"modTime":"2025-04-01 08:02:00.228156524 +0000 UTC","type":"file"}
,{"name":"3.txt","size":12,"modTime":"2025-04-01 08:02:00.228156524 +0000 UTC","type":"file"}
,{"name":"2.txt","size":12,"modTime":"2025-04-01 08:02:00.228156524 +0000 UTC","type":"file"}
,{"name":"1.txt","size":12,"modTime":"2025-04-01 08:02:00.228156524 +0000 UTC","type":"file"}
,{"name":"6.txt","size":12,"modTime":"2025-04-01 08:02:00.228156524 +0000 UTC","type":"file"}
,{"name":"5.txt","size":12,"modTime":"2025-04-01 08:02:00.228156524 +0000 UTC","type":"file"}
]}

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

Этот метод предполагает создание отдельного ServiceAccount с соответствующими правами доступа. Создайте ServiceAccount, используя команду:

kubectl -n $NAMESPACE create serviceaccount data-exporter-test

Создайте ClusterRole, выполнив следующую команду:

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

Создайте токен, запустив команды:

export TOKEN=$(kubectl create token data-exporter-test --duration=24h)
echo $TOKEN

Создайте ClusterRoleBinding, применив манифест:

kubectl 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/

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

..
..

< 
* TLSv1.2 (IN), TLS header, Supplemental data (23):
{"apiVersion": "v1", "items": [{"name":"4.txt","size":12,"modTime":"2025-04-01 08:02:00.228156524 +0000 UTC","type":"file"}
,{"name":"hello","size":5,"modTime":"2025-03-03 10:53:06.895434814 +0000 UTC","type":"file"}
,{"name":"7.txt","size":12,"modTime":"2025-04-01 08:02:00.228156524 +0000 UTC","type":"file"}
,{"name":"lost+found","modTime":"2025-03-03 10:29:31 +0000 UTC","type":"dir"}
,{"name":"8.txt","size":12,"modTime":"2025-04-01 08:02:00.228156524 +0000 UTC","type":"file"}
,{"name":"10.txt","size":13,"modTime":"2025-04-01 08:02:00.228156524 +0000 UTC","type":"file"}
,{"name":"9.txt","size":12,"modTime":"2025-04-01 08:02:00.228156524 +0000 UTC","type":"file"}
,{"name":"3.txt","size":12,"modTime":"2025-04-01 08:02:00.228156524 +0000 UTC","type":"file"}
,{"name":"2.txt","size":12,"modTime":"2025-04-01 08:02:00.228156524 +0000 UTC","type":"file"}
,{"name":"1.txt","size":12,"modTime":"2025-04-01 08:02:00.228156524 +0000 UTC","type":"file"}
,{"name":"6.txt","size":12,"modTime":"2025-04-01 08:02:00.228156524 +0000 UTC","type":"file"}
,{"name":"5.txt","size":12,"modTime":"2025-04-01 08:02:00.228156524 +0000 UTC","type":"file"}
]}

API data export

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

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

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

Режим Filesystem

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

Query (опционально, для дополнительных атрибутов):
- attribute=stat      — вернуть статические атрибуты (uid, gid, permissions, modtime)
- attribute=hash.md5  — вернуть MD5 для обычных файлов

Ответ (файл):

HTTP/1.1 200 OK
Content-Type: application/octet-stream
Content-Length: <size>
Content-Disposition: attachment; filename=<basename>
X-Attribute-Uid: <uid>
X-Attribute-Gid: <gid>
X-Attribute-Permissions: <perm>
X-Attribute-Modtime: <RFC3339>
X-Attribute-Hash-Md5: <md5>  # если запрошено attribute=hash.md5
X-Type: file

<file content>

Ответ (символическая ссылка):

HTTP/1.1 200 OK
Content-Type: application/x-symlink
Content-Length: 0 - постоянное значение для символической ссылки
X-Attribute-Uid: <uid>
X-Attribute-Gid: <gid>
X-Attribute-Permissions: <perm>
X-Attribute-Modtime: <RFC3339>
X-Link-Target: /dir/file
X-Type: link
  • Листинг директории
GET /api/v1/files/{path/} HTTP/1.1  # путь к директории должен заканчиваться '/'

Query (опционально): attribute=stat | attribute=hash.md5

Ответ:

HTTP/1.1 200 OK
Content-Type: application/json
Transfer-Encoding: chunked
X-Attribute-Uid: <uid>
X-Attribute-Gid: <gid>
X-Attribute-Permissions: <perm>
X-Attribute-Modtime: <RFC3339>
X-Type: dir

{"apiVersion":"v1","items":[{...}]}
  • Получение данных о прогрессе загрузки файла или метаданных директории
HEAD /api/v1/files/{path|path/} HTTP/1.1

Ответ (файл):

HTTP/1.1 200 OK
Content-Type: application/octet-stream
Content-Length: <size>
Content-Disposition: attachment; filename=<basename>
X-Attribute-Uid: <uid>
X-Attribute-Gid: <gid>
X-Attribute-Permissions: <perm>
X-Attribute-Modtime: <RFC3339>
X-Type: file
X-Attribute-Hash-Md5: <md5>  # если запрошено attribute=hash.md5

Ответ (директория):

HTTP/1.1 200 OK
Content-Type: application/json
Transfer-Encoding: chunked
X-Attribute-Uid: <uid>
X-Attribute-Gid: <gid>
X-Attribute-Permissions: <perm>
X-Attribute-Modtime: <RFC3339>
X-Type: dir

Ответ (символическая ссылка):

HTTP/1.1 200 OK
Content-Type: application/x-symlink
Content-Length: 0 - постоянное значение для символической ссылки
X-Attribute-Uid: <uid>
X-Attribute-Gid: <gid>
X-Attribute-Permissions: <perm>
X-Attribute-Modtime: <RFC3339>
X-Link-Target: /dir/file
X-Type: link

Режим Block

  • Получение загруженных данных блочного устройства (raw-образ)
GET /api/v1/block HTTP/1.1

Ответ:

HTTP/1.1 200 OK
Content-Type: application/octet-stream
Content-Length: <device_size>
Content-Disposition: attachment; filename=data.img

<raw block content>
  • Запрос прогресса загрузки блочного устройства
HEAD /api/v1/block HTTP/1.1

Ответ:

HTTP/1.1 200 OK
Content-Type: application/octet-stream
Content-Length: <device_size>
Content-Disposition: attachment; filename=data.img

API data import

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

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

  • kindShort - короткое наименование испортируемого ресурса (persistentVolumeClaim => pvc)
  • name - имя ресурса в который происходит импорт

Режим Filesystem

  • Загрузка файла
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-Attribute-ModTime: дата и время в формате RFC3339 (2024-12-27T10:16:53.4537715+03:00)

Ответ при частичной загрузке:

HTTP/1.1 204 No Content
X-Next-Offset: 10 - текущее смещение. Целое неотрицательное число

Ответ при завершении загрузки:

HTTP/1.1 201 Created
  • Запрос текущего прогресса загрузки
HEAD /api/v1/files/{path} HTTP/1.1
Content-Length: 0
HTTP/1.1 200 OK
X-Next-Offset: 10 - текущее смещение. Целое неотрицательное число
  • Завершение импорта данных (сигнал на остановку файлового сервера)
POST /api/v1/finished HTTP/1.1
Content-Length: 0
HTTP/1.1 200 OK

Режим Block

  • Загрузка 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 - опционально; ожидаемый итоговый размер (целое неотрицательное)

Ответ при частичной загрузке:

HTTP/1.1 204 No Content
X-Next-Offset: 10 - текущее смещение. Целое неотрицательное число

Ответ при завершении загрузки:

HTTP/1.1 201 Created
X-Next-Offset: 10 - итоговое смещение (совпадает с X-Content-Length)
  • Запрос текущего прогресса загрузки
HEAD /api/v1/block HTTP/1.1
Content-Length: 0
HTTP/1.1 200 OK
X-Next-Offset - 10 - текущее смещение. Целое неотрицательное число
X-Device-Size - 1024 - текущий размер устройства в байтах
  • Завершение импорта данных (сигнал на остановку файлового сервера)
POST /api/v1/finished HTTP/1.1
Content-Length: 0
HTTP/1.1 200 OK

Ошибки (Filesystem)

400 Bad Request           — некорректные/отсутствующие обязательные заголовки, неверные значения X-Offset / X-Content-Length / X-Attribute-ModTime
405 Method Not Allowed    — метод не поддерживается (кроме PUT/HEAD/POST)
403 Forbidden             — нет прав записи в файловую систему
404 Not Found             — HEAD для несуществующего файла
409 Conflict              — параллельная запись; смещение не совпадает с ожидаемым, файл уже загружён целиком
500 Internal Server Error — внутренние ошибки доступа к ФС, установка атрибутов и прочее

Ошибки (Block)

400 Bad Request           — некорректный путь, неверные заголовки 
405 Method Not Allowed    — метод не поддерживается (кроме PUT/HEAD/POST)
403 Forbidden             — недостаточно прав на запись в блочное устройство
409 Conflict              — параллельная запись; смещение не совпадает с текущим
416 Requested Range Not Satisfiable — смещение меньше 0 или больше размера устройства; Content-Length превышает оставшийся размер
422 Unprocessable Entity  — записанный объём превысил объявленный X-Content-Length
500 Internal Server Error — внутренние ошибки сервера

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

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

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