Стадия жизненного цикла модуля: General Availability
У модуля есть требования для установки

Как экспортировать и скачать том без утилиты d8?

Ресурсы DataExport можно создать напрямую через YAML-манифест и обращаться к экспортеру по HTTP. В приведённом ниже примере используются переменные окружения для упрощения настройки; замените их значения на необходимые:

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"
d8 k 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-сертификат, выполнив следующую команду:

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 и извлечения необходимых данных вы можете подключиться к экспортеру одним из следующих способов:

Аутентификация с использованием сертификата и ключа из локального 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"}
]}

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

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

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

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

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

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

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

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

d8 k create -f - <<EOF
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
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"}
]}

Как импортировать и загрузить том без утилиты d8?

Создайте ресурс DataImport с шаблоном PVC через манифест, извлеките URL доступа и CA-сертификат из status ресурса, затем загрузите данные через HTTP API импорта.

Пример: создание ресурса DataImport:

d8 k apply -f -<<EOF
apiVersion: storage.deckhouse.io/v1alpha1
kind: DataImport
metadata:
  name: example-dataimport
  namespace: d8-storage-volume-data-manager
spec:
  ttl: 10h
  publish: true
  targetRef:
    kind: PersistentVolumeClaim
    pvcTemplate:
      metadata:
        name: imported-data
      spec:
        accessModes:
          - ReadWriteOnce
        resources:
          requests:
            storage: 1Gi
        storageClassName: local-path
        volumeMode: Filesystem
EOF

Извлеките URL и CA-сертификат:

export POD_URL=$(d8 k -n d8-storage-volume-data-manager get dataimport example-dataimport -o jsonpath='{.status.url}')
d8 k -n d8-storage-volume-data-manager get dataimport example-dataimport -o jsonpath='{.status.ca}' | base64 -d > ca.pem

Загрузите файл с помощью curl (обязательные заголовки см. в справочнике HTTP API ниже):

curl --cacert ca.pem \
  --cert client.crt --key client.key \
  -X PUT "${POD_URL}api/v1/files/myfile" \
  -H "Content-Length: $(stat -c%s ./local-file)" \
  -H "X-Content-Length: $(stat -c%s ./local-file)" \
  -H "X-Attribute-Permissions: 0644" \
  -H "X-Attribute-Uid: 0" \
  -H "X-Attribute-Gid: 0" \
  -H "X-Offset: 0" \
  --data-binary @./local-file

Сигнал о завершении импорта:

curl --cacert ca.pem \
  --cert client.crt --key client.key \
  -X POST "${POD_URL}api/v1/finished" \
  -H "Content-Length: 0"

Справочник HTTP API

Все конечные точки доступны как по короткому пути /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

Ошибки (Filesystem):

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

Экспорт: режим 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

Импорт: режим 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-Content-Length: 10 — обязательный заголовок, ожидаемый итоговый размер файла (целое неотрицательное число)
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-строка, содержащая список файлов с информацией о имени, типе и размере. Размеры файлов не кэшируются и вычисляются заново при каждом запросе директории.