← Sandbox SDK / sandbox / tutorials
Постоянное хранение данных с помощью R2
Подключайте бакеты объектного хранилища как пути локальной файловой системы, чтобы сохранять данные между жизненными циклами песочницы. В этом руководстве используется Cloudflare R2, но тот же подход работает с любым S3-совместимым провайдером.
В этом руководстве показано, как сохранять внешний каталог данных, смонтированный в /data. Если вы хотите, чтобы рабочий проект находился в /workspace чтобы сохранить, см. Резервное копирование и восстановление.
Время выполнения: 20 минут
Что вы создадите
Worker, который обрабатывает данные, сохраняет результаты в бакете R2, подключённом как локальный каталог, и демонстрирует, что данные сохраняются даже после уничтожения и повторного создания песочницы.
Ключевые понятия, которые вы изучите:
- Подключение бакетов R2 как путей файловой системы
- Автоматическое сохранение данных между циклами существования песочницы
- Работа со смонтированным хранилищем с помощью стандартных файловых операций
Предварительные требования
- Зарегистрируйтесь для получения Аккаунт Cloudflare ↗.
- Установка
Node.js↗.
менеджер версий Node.js
Используйте менеджер версий Node, например Volta ↗ или nvm ↗ чтобы избежать проблем с правами доступа и переключать версии Node.js. Wrangler, о котором пойдёт речь далее в этом руководстве, требует версию Node 16.17.0 или более поздней версии.
Также потребуется:
- Docker ↗ локальный запуск
- Бакет R2 (создайте его в Панель управления Cloudflare ↗)
1. Создайте проект
npm create cloudflare@latest -- data-pipeline --template=cloudflare/sandbox-sdk/examples/minimalcd data-pipeline2. Настройте привязку R2
Добавьте привязку (binding) бакета R2 в wrangler.json:
{
"name": "data-pipeline",
"compatibility_date": "2025-11-09",
"durable_objects": {
"bindings": [
{ "name": "Sandbox", "class_name": "Sandbox" }
]
},
"r2_buckets": [
{
"binding": "DATA_BUCKET",
"bucket_name": "my-data-bucket"
}
]
}Замените my-data-bucket на имя вашего бакета R2. Сначала создайте бакет в Панель управления Cloudflare ↗.
3. Постройте обработчик данных
Замените src/index.ts с кодом, который монтирует R2 и обрабатывает данные:
import { getSandbox } from "@cloudflare/sandbox";
export { Sandbox } from "@cloudflare/sandbox";
export default {
async fetch(request, env) {
const url = new URL(request.url);
const sandbox = getSandbox(env.Sandbox, "data-processor");
// Mount R2 bucket to /data directory
await sandbox.mountBucket("my-data-bucket", "/data", {
endpoint: "https://YOUR_ACCOUNT_ID.r2.cloudflarestorage.com",
});
if (url.pathname === "/process") {
// Process data and save to mounted R2
const result = await sandbox.exec("python", {
args: [
"-c",
`
import json
import os
from datetime import datetime
# Read input (or create sample data)
data = [
{'id': 1, 'value': 42},
{'id': 2, 'value': 87},
{'id': 3, 'value': 15}
]
# Process: calculate sum and average
total = sum(item['value'] for item in data)
avg = total / len(data)
# Save results to mounted R2 (/data is the mounted bucket)
result = {
'timestamp': datetime.now().isoformat(),
'total': total,
'average': avg,
'processed_count': len(data)
}
os.makedirs('/data/results', exist_ok=True)
with open('/data/results/latest.json', 'w') as f:
json.dump(result, f, indent=2)
print(json.dumps(result))
`,
],
});
return Response.json({
message: "Data processed and saved to R2",
result: JSON.parse(result.stdout),
});
}
if (url.pathname === "/results") {
// Read results from mounted R2
const result = await sandbox.exec("cat", {
args: ["/data/results/latest.json"],
});
if (!result.success) {
return Response.json(
{ error: "No results found yet" },
{ status: 404 },
);
}
return Response.json({
message: "Results retrieved from R2",
data: JSON.parse(result.stdout),
});
}
if (url.pathname === "/destroy") {
// Destroy sandbox to demonstrate persistence
await sandbox.destroy();
return Response.json({
message: "Sandbox destroyed. Data persists in R2!",
});
}
return new Response(
`
Data Pipeline with Persistent Storage
Endpoints:
- POST /process - Process data and save to R2
- GET /results - Retrieve results from R2
- POST /destroy - Destroy sandbox (data survives!)
Try this flow:
1. POST /process (processes and saves to R2)
2. POST /destroy (destroys sandbox)
3. GET /results (data still accessible from R2)
`,
{ headers: { "Content-Type": "text/plain" } },
);
},
};import { getSandbox, type Sandbox } from '@cloudflare/sandbox';
export { Sandbox } from '@cloudflare/sandbox';
interface Env {
Sandbox: DurableObjectNamespace<Sandbox>;
DATA_BUCKET: R2Bucket;
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
const sandbox = getSandbox(env.Sandbox, 'data-processor');
// Mount R2 bucket to /data directory
await sandbox.mountBucket('my-data-bucket', '/data', {
endpoint: 'https://YOUR_ACCOUNT_ID.r2.cloudflarestorage.com'
});
if (url.pathname === '/process') {
// Process data and save to mounted R2
const result = await sandbox.exec('python', {
args: ['-c', `
import json
import os
from datetime import datetime
# Read input (or create sample data)
data = [
{'id': 1, 'value': 42},
{'id': 2, 'value': 87},
{'id': 3, 'value': 15}
]
# Process: calculate sum and average
total = sum(item['value'] for item in data)
avg = total / len(data)
# Save results to mounted R2 (/data is the mounted bucket)
result = {
'timestamp': datetime.now().isoformat(),
'total': total,
'average': avg,
'processed_count': len(data)
}
os.makedirs('/data/results', exist_ok=True)
with open('/data/results/latest.json', 'w') as f:
json.dump(result, f, indent=2)
print(json.dumps(result))
`]
});
return Response.json({
message: 'Data processed and saved to R2',
result: JSON.parse(result.stdout)
});
}
if (url.pathname === '/results') {
// Read results from mounted R2
const result = await sandbox.exec('cat', {
args: ['/data/results/latest.json']
});
if (!result.success) {
return Response.json({ error: 'No results found yet' }, { status: 404 });
}
return Response.json({
message: 'Results retrieved from R2',
data: JSON.parse(result.stdout)
});
}
if (url.pathname === '/destroy') {
// Destroy sandbox to demonstrate persistence
await sandbox.destroy();
return Response.json({ message: 'Sandbox destroyed. Data persists in R2!' });
}
return new Response(`
Data Pipeline with Persistent Storage
Endpoints:
- POST /process - Process data and save to R2
- GET /results - Retrieve results from R2
- POST /destroy - Destroy sandbox (data survives!)
Try this flow:
1. POST /process (processes and saves to R2)
2. POST /destroy (destroys sandbox)
3. GET /results (data still accessible from R2)
`, { headers: { 'Content-Type': 'text/plain' } });
}
};4. Разверните в продакшене
Создайте API-токены R2:
- Перейдите в R2 > Обзор в Панель управления Cloudflare ↗
- Выберите Управление API-токенами R2
- Создайте токен с Object Read & Write разрешения
- Скопируйте Access Key ID и Secret Access Key
Настройте учётные данные как секреты Worker:
npx wrangler secret put AWS_ACCESS_KEY_ID
# Paste your R2 Access Key ID
npx wrangler secret put AWS_SECRET_ACCESS_KEY
# Paste your R2 Secret Access KeyСекреты Worker зашифрованы и доступны только развёрнутому Worker. SDK автоматически обнаруживает эти учётные данные, если mountBucket() вызывается.
Разверните ваш Worker:
npx wrangler deployПосле развертывания wrangler выводит URL вашего Worker (например, https://data-pipeline.yourname.workers.dev).
5. Проверка сохранения данных
Теперь протестируйте на развёрнутом Worker. Замените YOUR_WORKER_URL на фактический URL вашего Worker:
# 1. Process data (saves to R2)
curl -X POST https://YOUR_WORKER_URL/process
# Returns: { "message": "Data processed...", "result": { "total": 144, "average": 48, ... } }
# 2. Verify data is accessible
curl https://YOUR_WORKER_URL/results
# Returns the same results from R2
# 3. Destroy the sandbox
curl -X POST https://YOUR_WORKER_URL/destroy
# Returns: { "message": "Sandbox destroyed. Data persists in R2!" }
# 4. Access results again (from new sandbox)
curl https://YOUR_WORKER_URL/results
# Still works! Data persisted across sandbox lifecycleГлавная идея в следующем: после уничтожения песочницы следующий запрос создаёт новый экземпляр песочницы, монтирует тот же бакет R2 и обнаруживает, что данные никуда не делись.
Что вы узнали
В этом руководстве вы создали конвейер обработки данных, который демонстрирует сохранение файловой системы через монтирование бакета R2:
- Подключение бакетов: Используйте
mountBucket()чтобы сделать R2 доступным как локальный каталог - Стандартные операции с файлами: Доступ к подключённым бакетам с помощью привычных команд файловой системы (
cat, Pythonopen(), и т. д.) - Автоматическое сохранение данных: Данные, записанные в подключённые каталоги, сохраняются после уничтожения песочницы
- Выбор подходящей модели сохранения данных: Используйте подключение бакетов для внешних каталогов хранения, например
/data, и продумайте резервное копирование и восстановление, если вам нужно постоянное рабочее пространство в рамках/workspace - Управление учётными данными: Настройте доступ к R2 с помощью переменных окружения или явных учётных данных
Следующие шаги
- Руководство по подключению бакетов - Полный справочник по монтированию
- Storage API - Полная документация по API
- Переменные окружения - Параметры конфигурации учетных данных
Дополнительные материалы
- Документация R2 - Узнайте больше о Cloudflare R2
- Руководство по фоновым процессам - Длительная обработка данных
- Концепция песочниц - Изучение жизненного цикла песочницы