INTEGRITY Документация

Подключение бакетов

Подключайте бакеты S3-совместимого объектного хранилища как пути локальной файловой системы. Обращайтесь к объектному хранилищу с помощью стандартных файловых операций. Для Cloudflare R2 в продакшене можно также подключать по имени привязки Worker R2, чтобы учетные данные оставались в среде выполнения Worker.

Требования для использования монтирований R2 binding в продакшене

Чтобы смонтировать бакет R2 в продакшене без передачи учётных данных в контейнер, добавьте привязку R2 и экспортируйте ContainerProxy из точки входа вашего Worker.

{
  "$schema": "./node_modules/wrangler/config-schema.json",
  "r2_buckets": [
    {
      "binding": "MY_BUCKET",
      "bucket_name": "my-r2-bucket"
    }
  ]
}
[[r2_buckets]]
binding = "MY_BUCKET"
bucket_name = "my-r2-bucket"
import { ContainerProxy } from "@cloudflare/sandbox";

export { ContainerProxy };
import { ContainerProxy } from "@cloudflare/sandbox";

export { ContainerProxy };

Если не указать endpoint, первый аргумент mountBucket() должен быть именем привязки R2 в Worker, например MY_BUCKET.

Когда монтировать бакеты

Подключайте S3-совместимые бакеты, если вам нужно:

Подключение бакета R2

import { getSandbox } from "@cloudflare/sandbox";

const sandbox = getSandbox(env.Sandbox, "data-processor");

// Mount R2 bucket by Worker binding name
await sandbox.mountBucket("MY_BUCKET", "/data");

// Access bucket with standard filesystem operations
await sandbox.exec("ls", { args: ["/data"] });
await sandbox.writeFile("/data/results.json", JSON.stringify(results));

// Use from Python
await sandbox.exec("python", {
	args: [
		"-c",
		`
import pandas as pd
df = pd.read_csv('/data/input.csv')
df.describe().to_csv('/data/summary.csv')
`,
	],
});
import { getSandbox } from '@cloudflare/sandbox';

const sandbox = getSandbox(env.Sandbox, 'data-processor');

// Mount R2 bucket by Worker binding name
await sandbox.mountBucket('MY_BUCKET', '/data');

// Access bucket with standard filesystem operations
await sandbox.exec('ls', { args: ['/data'] });
await sandbox.writeFile('/data/results.json', JSON.stringify(results));

// Use from Python
await sandbox.exec('python', { args: ['-c', `
import pandas as pd
df = pd.read_csv('/data/input.csv')
df.describe().to_csv('/data/summary.csv')
`] });

В этом примере MY_BUCKET это имя привязки из wrangler.toml. Оно не обязано совпадать с именем bucket в дашборде, хотя многие проекты используют одинаковые имена.

Учётные данные

Монтирования R2 binding не требуют учётных данных. Монтирования через удалённую конечную точку по-прежнему поддерживаются для Cloudflare R2 и других S3-совместимых провайдеров, и в этих сценариях можно использовать как автоматическое определение учётных данных, так и явно заданные учётные данные.

Автоматическое обнаружение

Если вы включаете endpoint, задайте учетные данные как секреты Worker, и SDK автоматически их обнаружит:

npx wrangler secret put R2_ACCESS_KEY_ID
npx wrangler secret put R2_SECRET_ACCESS_KEY
// Credentials automatically detected from environment for remote endpoint mounts
await sandbox.mountBucket("my-r2-bucket", "/data", {
	endpoint: "https://YOUR_ACCOUNT_ID.r2.cloudflarestorage.com",
});
// Credentials automatically detected from environment for remote endpoint mounts
await sandbox.mountBucket('my-r2-bucket', '/data', {
	endpoint: 'https://YOUR_ACCOUNT_ID.r2.cloudflarestorage.com'
});

Явные учётные данные

При необходимости передавайте учётные данные напрямую:

await sandbox.mountBucket("my-r2-bucket", "/data", {
	endpoint: "https://YOUR_ACCOUNT_ID.r2.cloudflarestorage.com",
	credentials: {
		accessKeyId: env.R2_ACCESS_KEY_ID,
		secretAccessKey: env.R2_SECRET_ACCESS_KEY,
	},
});
await sandbox.mountBucket('my-r2-bucket', '/data', {
	endpoint: 'https://YOUR_ACCOUNT_ID.r2.cloudflarestorage.com',
	credentials: {
		accessKeyId: env.R2_ACCESS_KEY_ID,
		secretAccessKey: env.R2_SECRET_ACCESS_KEY
	}
});

Прокси учётных данных

При монтировании с явными учётными данными s3fs записывает их в файл паролей на диске контейнера. Скомпрометированный процесс контейнера может прочитать и похитить эти учётные данные или использовать их для доступа к хранилищу за пределами предполагаемого бакета.

Задайте credentialProxy: true чтобы учётные данные вообще не попадали в контейнер. Вместо передачи реальных учётных данных в контейнер Durable Object перехватывает все исходящие запросы S3 на сетевом уровне, повторно подписывает их реальными учётными данными и пересылает дальше. В контейнере хранятся только фиктивные учётные данные, бесполезные за пределами прокси.

Это работает с AWS SigV4 ↗ подпись для S3-совместимых конечных точек (включая R2) и подпись HMAC для Google Cloud Storage. Рекомендуется задать credentialProxy: true для всех точек монтирования эндпоинтов. По умолчанию используется значение false для обратной совместимости и станет значением по умолчанию в будущей версии Sandbox SDK.

await sandbox.mountBucket("my-bucket", "/data", {
	endpoint: "https://YOUR_ACCOUNT_ID.r2.cloudflarestorage.com",
	provider: "r2",
	credentials: {
		accessKeyId: env.R2_ACCESS_KEY_ID,
		secretAccessKey: env.R2_SECRET_ACCESS_KEY,
	},
	credentialProxy: true,
});
await sandbox.mountBucket('my-bucket', '/data', {
	endpoint: 'https://YOUR_ACCOUNT_ID.r2.cloudflarestorage.com',
	provider: 'r2',
	credentials: {
		accessKeyId: env.R2_ACCESS_KEY_ID,
		secretAccessKey: env.R2_SECRET_ACCESS_KEY
	},
	credentialProxy: true
});

Подключение подкаталогов бакета

Подключайте определенный подкаталог внутри бакета с помощью prefix опция. В точке монтирования видно только содержимое под этим префиксом:

// Mount only the /uploads/images/ subdirectory
await sandbox.mountBucket("MY_BUCKET", "/images", {
	prefix: "/uploads/images/",
});

// Files appear at mount point without the prefix
// Bound bucket: my-r2-bucket/uploads/images/photo.jpg
// Mounted path: /images/photo.jpg
await sandbox.exec("ls", { args: ["/images"] });

// Write to subdirectory
await sandbox.writeFile("/images/photo.jpg", imageData);
// Creates my-r2-bucket/uploads/images/photo.jpg

// Mount different prefixes to different paths
await sandbox.mountBucket("MY_BUCKET", "/training-data", {
	prefix: "/ml/training/",
});

await sandbox.mountBucket("MY_BUCKET", "/test-data", {
	prefix: "/ml/testing/",
});
// Mount only the /uploads/images/ subdirectory
await sandbox.mountBucket('MY_BUCKET', '/images', {
	prefix: '/uploads/images/'
});

// Files appear at mount point without the prefix
// Bound bucket: my-r2-bucket/uploads/images/photo.jpg
// Mounted path: /images/photo.jpg
await sandbox.exec('ls', { args: ['/images'] });

// Write to subdirectory
await sandbox.writeFile('/images/photo.jpg', imageData);
// Creates my-r2-bucket/uploads/images/photo.jpg

// Mount different prefixes to different paths
await sandbox.mountBucket('MY_BUCKET', '/training-data', {
	prefix: '/ml/training/'
});

await sandbox.mountBucket('MY_BUCKET', '/test-data', {
	prefix: '/ml/testing/'
});

Монтирования только для чтения

Защитите данные, монтируя бакеты в режиме только для чтения:

await sandbox.mountBucket("MY_BUCKET", "/data", {
	readOnly: true,
});

// Reads work
await sandbox.exec("cat", { args: ["/data/dataset.csv"] });

// Writes fail
await sandbox.writeFile("/data/new-file.txt", "data"); // Error: Read-only filesystem
await sandbox.mountBucket('MY_BUCKET', '/data', {
	readOnly: true
});

// Reads work
await sandbox.exec('cat', { args: ['/data/dataset.csv'] });

// Writes fail
await sandbox.writeFile('/data/new-file.txt', 'data');  // Error: Read-only filesystem

Локальная разработка

Вы также можете монтировать бакеты R2 при локальной разработке с помощью wrangler dev передав localBucket опция. Продакшен-монтирования привязки R2 и локальные localBucket монтирования в обоих случаях обходятся без явных учётных данных, но представляют собой разные пути выполнения. В продакшене используется перехват исходящего трафика без учётных данных с наложением на целевой путь. При локальной разработке применяется периодическая синхронизация с привязкой R2.

await sandbox.mountBucket("MY_BUCKET", "/data", {
	localBucket: true,
});

// Access files using standard operations
await sandbox.exec("ls", { args: ["/data"] });
await sandbox.writeFile("/data/results.json", JSON.stringify(results));
await sandbox.mountBucket('MY_BUCKET', '/data', {
	localBucket: true
});

// Access files using standard operations
await sandbox.exec('ls', { args: ['/data'] });
await sandbox.writeFile('/data/results.json', JSON.stringify(results));

readOnly и prefix опции работают так же в локальном режиме:

// Read-only local mount
await sandbox.mountBucket("MY_BUCKET", "/data", {
	localBucket: true,
	readOnly: true,
});

// Mount a subdirectory
await sandbox.mountBucket("MY_BUCKET", "/images", {
	localBucket: true,
	prefix: "/uploads/images/",
});
// Read-only local mount
await sandbox.mountBucket('MY_BUCKET', '/data', {
	localBucket: true,
	readOnly: true
});

// Mount a subdirectory
await sandbox.mountBucket('MY_BUCKET', '/images', {
	localBucket: true,
	prefix: '/uploads/images/'
});

Особенности локальной разработки

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

Отмонтирование buckets

// Mount for processing
await sandbox.mountBucket("MY_BUCKET", "/data");

// Do work
await sandbox.exec("python process_data.py");

// Clean up
await sandbox.unmountBucket("/data");
// Mount for processing
await sandbox.mountBucket('MY_BUCKET', '/data');

// Do work
await sandbox.exec('python process_data.py');

// Clean up
await sandbox.unmountBucket('/data');

Другие провайдеры

SDK поддерживает любое S3-совместимое объектное хранилище. Ниже приведены примеры для популярных провайдеров:

Amazon S3

await sandbox.mountBucket("my-s3-bucket", "/data", {
	endpoint: "https://s3.us-west-2.amazonaws.com",
	credentials: {
		accessKeyId: env.AWS_ACCESS_KEY_ID,
		secretAccessKey: env.AWS_SECRET_ACCESS_KEY,
	},
});
await sandbox.mountBucket('my-s3-bucket', '/data', {
	endpoint: 'https://s3.us-west-2.amazonaws.com',
	credentials: {
		accessKeyId: env.AWS_ACCESS_KEY_ID,
		secretAccessKey: env.AWS_SECRET_ACCESS_KEY
	}
});

Google Cloud Storage

await sandbox.mountBucket("my-gcs-bucket", "/data", {
	endpoint: "https://storage.googleapis.com",
	credentials: {
		accessKeyId: env.GCS_ACCESS_KEY_ID,
		secretAccessKey: env.GCS_SECRET_ACCESS_KEY,
	},
});
await sandbox.mountBucket('my-gcs-bucket', '/data', {
	endpoint: 'https://storage.googleapis.com',
	credentials: {
		accessKeyId: env.GCS_ACCESS_KEY_ID,
		secretAccessKey: env.GCS_SECRET_ACCESS_KEY
	}
});

Другие S3-совместимые провайдеры

Для таких провайдеров, как Backblaze B2, MinIO, Wasabi и других, используйте стандартную схему монтирования:

await sandbox.mountBucket("my-bucket", "/data", {
	endpoint: "https://s3.us-west-000.backblazeb2.com",
	credentials: {
		accessKeyId: env.ACCESS_KEY_ID,
		secretAccessKey: env.SECRET_ACCESS_KEY,
	},
});
await sandbox.mountBucket('my-bucket', '/data', {
	endpoint: 'https://s3.us-west-000.backblazeb2.com',
	credentials: {
		accessKeyId: env.ACCESS_KEY_ID,
		secretAccessKey: env.SECRET_ACCESS_KEY
	}
});

Настройки для конкретных провайдеров см. в вики s3fs-fuse ↗ для поддерживаемых провайдеров и рекомендуемых флагов.

Устранение неполадок

Ошибка «R2 binding не найден»

Ошибка: R2 binding "MY_BUCKET" not found in Worker env

Решение: Убедитесь, что у Worker есть r2_buckets привязку и то, что mountBucket() использует имя привязки, а не имя бакета в панели управления:

{
  "$schema": "./node_modules/wrangler/config-schema.json",
  "r2_buckets": [
    {
      "binding": "MY_BUCKET",
      "bucket_name": "my-r2-bucket"
    }
  ]
}
[[r2_buckets]]
binding = "MY_BUCKET"
bucket_name = "my-r2-bucket"

Монтирование R2 без учётных данных сразу завершается ошибкой

Решение: Убедитесь, что точка входа Worker экспортирует ContainerProxy. Если вы используете более старую версию Wrangler, вам может также понадобиться enable_ctx_exports флаг совместимости.

Ошибка отсутствия учетных данных

Ошибка: MissingCredentialsError: No credentials found

Решение: Эта ошибка возникает только при подключении удалённой S3-совместимой конечной точки с помощью параметра endpoint. Задайте учётные данные как секреты Worker:

npx wrangler secret put R2_ACCESS_KEY_ID
npx wrangler secret put R2_SECRET_ACCESS_KEY

или

npx wrangler secret put AWS_ACCESS_KEY_ID
npx wrangler secret put AWS_SECRET_ACCESS_KEY

Ошибка сбоя подключения

Ошибка: S3FSMountError: mount failed

Распространённые причины:

Проверьте конфигурацию привязки или эндпоинта:

try {
	await sandbox.mountBucket("MY_BUCKET", "/data");
} catch (error) {
	console.error("Mount failed:", error.message);
	// Check binding name, ContainerProxy export, or remote endpoint configuration
}
try {
	await sandbox.mountBucket('MY_BUCKET', '/data');
} catch (error) {
	console.error('Mount failed:', error.message);
	// Check binding name, ContainerProxy export, or remote endpoint configuration
}

Ошибка «путь уже смонтирован»

Ошибка: InvalidMountConfigError: Mount path already in use

Решение: Сначала отмонтируйте или используйте другой путь:

// Unmount existing
await sandbox.unmountBucket("/data");

// Or use different path
await sandbox.mountBucket("bucket2", "/storage", { endpoint: "..." });
// Unmount existing
await sandbox.unmountBucket('/data');

// Or use different path
await sandbox.mountBucket('bucket2', '/storage', { endpoint: '...' });

Медленный доступ к файлам

Файловые операции с примонтированными бакетами выполняются медленнее, чем с локальной файловой системой, из-за сетевых задержек.

Решение: Скопируйте часто используемые файлы локально:

// Copy to local filesystem
await sandbox.exec("cp", {
	args: ["/data/large-dataset.csv", "/workspace/dataset.csv"],
});

// Work with local copy (faster)
await sandbox.exec("python", {
	args: ["process.py", "/workspace/dataset.csv"],
});

// Save results back to bucket
await sandbox.exec("cp", {
	args: ["/workspace/results.json", "/data/results/output.json"],
});
// Copy to local filesystem
await sandbox.exec('cp', { args: ['/data/large-dataset.csv', '/workspace/dataset.csv'] });

// Work with local copy (faster)
await sandbox.exec('python', { args: ['process.py', '/workspace/dataset.csv'] });

// Save results back to bucket
await sandbox.exec('cp', { args: ['/workspace/results.json', '/data/results/output.json'] });

Рекомендации