Введение
Вашей команде поддержки нужно небольшое хранилище документов. Объект — это байты файла вместе с метаданными; бакет объединяет объекты, а ключ — полное имя объекта. Косые черты в ключах образуют удобные префиксы, но не создают обычные каталоги файловой системы. Вы создадите приватный бакет, загрузите два синтетических документа, изучите их метаданные, скачаете точные байты и удалите только выбранный документ перед очисткой.
Сначала выполните лабораторную работу Подключите LabEx к своей учётной записи Cloudflare. В ней объясняются терминал LabEx, авторизация устройства, подтверждение учебной учётной записи и настройка идентификатора учётной записи. Эта лабораторная работа запускается независимо в /home/labex/project/r2-lab; необходимые Node.js 22.22.0, Wrangler 4.131.1 и AWS SDK 3.888.0 уже подготовлены. На собственном компьютере сначала установите Node.js, а затем Wrangler и AWS SDK как зависимости проекта с помощью npm install.
Перед началом: для вашей учебной учётной записи должна быть активна подписка R2. На странице Cloudflare R2 setup есть процедура оформления; самостоятельно изучите её, если R2 ещё не активирован. Бесплатная учётная запись не активирует R2 автоматически. Ознакомьтесь со страницей pricing, чтобы узнать о плате за хранение и операции. В этом упражнении используются небольшие синтетические файлы и не требуется приобретённый домен. Вам нужны разрешения на управление бакетами и создание пользовательского токена R2, ограниченного этим новым бакетом. Оставьте публичный доступ отключённым. Никогда не вставляйте учётные данные в эту лабораторную работу, чат или снимки экрана.
Создайте приватный бакет документов
На этом шаге вы авторизуете эту виртуальную машину и создадите один временный бакет. Авторизация устройства подтверждает вашу учебную учётную запись. Управление бакетами R2 использует отдельный API-токен, ограниченный этой учётной записью.
Запустите Bash для выполнения приведённых ниже команд, затем перейдите в подготовленный проект и проверьте его инструменты. Не закрывайте этот терминал: переменные с именами ресурсов должны оставаться доступными.
bash
cd /home/labex/project/r2-lab
export PATH="$PWD/.tools/node-v22.22.0-linux-x64/bin:$PATH"
node --version
npx wrangler --version
Откройте показанный код устройства в собственном браузере. Перед подтверждением согласия проверьте учебную учётную запись, запрошенную учётную запись и области чтения account и user:
npx wrangler login --device --browser=false --scopes account:read user:read
npx wrangler whoami --json
Убедитесь, что указано loggedIn: true. Прочитайте имя учётной записи, даже если в списке только одна запись. В приведённой ниже команде замените YOUR_ACCOUNT_ID фактическим 32-символьным идентификатором этой учётной записи. Команда openssl rand -hex 6 генерирует двенадцать случайных шестнадцатеричных символов, поэтому имя ресурса в этой лабораторной работе не совпадёт с именем предыдущего запуска. Here-документ записывает стандартный файл конфигурации; оболочка подставляет в него значения переменных.
ACCOUNT_ID=YOUR_ACCOUNT_ID
RUN_ID=$(openssl rand -hex 6)
NAME="labex-c05-r01-$RUN_ID"
BUCKET="$NAME-docs"
cat > wrangler.jsonc <<JSON
{"name":"$NAME","account_id":"$ACCOUNT_ID","compatibility_date":"2026-07-30","r2_buckets":[{"binding":"DOCUMENTS","bucket_name":"$BUCKET"}]}
JSON
Для управления бакетами откройте страницу API Tokens в профиле Cloudflare и создайте пользовательский токен с именем этой лабораторной работы. Выдайте разрешение Account → Workers R2 Storage → Edit и ограничьте Account Resources учебной учётной записью, идентификатор которой вы сохранили. Установите короткий срок действия. Не добавляйте другие учётные записи и несвязанные разрешения. Это разрешение на уровне учётной записи позволяет создавать и удалять бакеты; токен только для работы с объектами из следующего шага этого делать не может.
Один раз скопируйте токен в скрытый запрос этой виртуальной машины. umask 077 ограничивает доступ к файлу вашим пользователем, а read -s скрывает ввод. В файле используется стандартная переменная токена Wrangler; файл исключён из Git.
umask 077
read -r -s -p 'R2 management API token: ' R2_MANAGEMENT_TOKEN; printf '\n'
printf 'CLOUDFLARE_API_TOKEN=%s\n' "$R2_MANAGEMENT_TOKEN" > .env.management
unset R2_MANAGEMENT_TOKEN
Используйте --env-file=.env.management только для команд управления R2; обычная команда whoami продолжит проверять авторизацию устройства на виртуальной машине.
Размещайте --env-file в конце каждой команды Wrangler, чтобы имя команды не попадало в список аргументов с файлами. Если после создания каждого бакета Wrangler предложит добавить привязку в конфигурацию, введите n и нажмите Enter. Нужная привязка уже указана в конфигурации.
npx wrangler r2 bucket create "$BUCKET" --env-file=.env.management
Выведите список бакетов и найдите точное сгенерированное имя. Остальные бакеты относятся к другим рабочим процессам — не изменяйте их.
npx wrangler r2 bucket list --env-file=.env.management
В Dashboard откройте Storage & databases → R2 → Overview, выберите именно этот бакет и проверьте пустой список объектов. В его настройках оставьте отключёнными URL для разработки с публичным доступом и пользовательские домены. Имя бакета в Dashboard подтверждает его идентичность; последующие проверки скачивания подтвердят сохранённые байты.

В примере показаны класс хранения Standard и Public Access Disabled. Имя созданного вами бакета будет другим.
Загрузите документы с метаданными
На этом шаге вы предоставите SDK доступ только к этому бакету и сохраните два документа. Content type сообщает клиенту, как интерпретировать байты; custom metadata хранит рядом с объектом небольшие пользовательские метки. Ни одно из этих свойств не является правилом управления доступом.
S3-совместимый API предоставляет стандартным SDK доступ к R2. Для него используется отдельная пара ключей доступа, а не токен устройства Wrangler. В разделе R2 Overview откройте Account Details → Manage API Tokens, затем создайте User API token с именем, содержащим сгенерированное имя ресурса этой лабораторной работы. Выберите Object Read & Write, ограничьте токен именно этим новым бакетом и установите короткий срок действия, если форма предлагает такую возможность. Не выбирайте все бакеты и доступ Admin. Не закрывайте страницу токена, пока не сохраните одноразовый секрет.
Используйте следующие запросы Bash в виртуальной машине. read -s скрывает ввод, а umask 077 делает файл с учётными данными доступным только вашему пользователю. Это стандартные переменные окружения AWS SDK. Вставьте Access Key ID и Secret Access Key в соответствующие запросы и нажмите Enter. Не вставляйте значение общего API-токена.
В форме токена выберите 24 hours в поле TTL и перед созданием проверьте точное имя бакета и разрешение Object Read & Write. Отзовите токен в конце лабораторной работы; срок действия служит лишь запасной мерой.
umask 077
read -r -s -p 'Access Key ID: ' AWS_ACCESS_KEY_ID; printf '\n'
read -r -s -p 'Secret Access Key: ' AWS_SECRET_ACCESS_KEY; printf '\n'
printf 'AWS_ACCESS_KEY_ID=%s\nAWS_SECRET_ACCESS_KEY=%s\n' "$AWS_ACCESS_KEY_ID" "$AWS_SECRET_ACCESS_KEY" > .env.s3
unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY
Создайте повторно используемый клиент стандартного SDK. SDK требует строку региона; для R2 используется auto. Чтение существующей конфигурации гарантирует, что операции CLI и SDK направлены к одной и той же учётной записи и одному и тому же бакету.
cat > storage.mjs <<'JS'
import { S3Client } from "@aws-sdk/client-s3";
import { readFileSync } from "node:fs";
const config = JSON.parse(readFileSync("wrangler.jsonc", "utf8"));
export const Bucket = config.r2_buckets[0].bucket_name;
export const s3 = new S3Client({
region: "auto",
endpoint: `https://${config.account_id}.r2.cloudflarestorage.com`,
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
}
});
JS
Теперь создайте программу загрузки. PutObjectCommand сохраняет байты по указанному ключу. Оба документа синтетические; оставшийся справочник подтвердит, что последующее выборочное удаление не удаляет другие ключи.
cat > upload.mjs <<'JS'
import { PutObjectCommand } from "@aws-sdk/client-s3";
import { readFileSync } from "node:fs";
import { s3, Bucket } from "./storage.mjs";
await s3.send(new PutObjectCommand({
Bucket, Key: "documents/report.txt", Body: readFileSync("document.txt"),
ContentType: "text/plain", Metadata: { team: "blue", revision: "1" }
}));
await s3.send(new PutObjectCommand({
Bucket, Key: "retained/handbook.txt", Body: readFileSync("retained.txt"),
ContentType: "text/plain"
}));
console.log("Uploaded two synthetic documents");
JS
--env-file загружает значения учётных данных, не выводя их:
node --env-file=.env.s3 upload.mjs
Строка об успешной загрузке выводится только после завершения обоих ожидаемых API-вызовов. Проверка платформы независимо читает реальные объекты и метаданные.
Просмотрите метаданные и сравните скачанные байты
На этом шаге вы просмотрите ключи, не скачивая каждый объект, а затем получите отчёт. ListObjectsV2 выводит ключи, а HeadObject получает только метаданные. Этот небольшой бакет помещается на одной странице списка; в рабочей среде при значении IsTruncated true списки должны продолжаться с помощью токенов продолжения.
cat > inspect.mjs <<'JS'
import { ListObjectsV2Command, HeadObjectCommand, GetObjectCommand } from "@aws-sdk/client-s3";
import { writeFileSync } from "node:fs";
import { s3, Bucket } from "./storage.mjs";
const page = await s3.send(new ListObjectsV2Command({ Bucket }));
console.log(page.Contents.map(object => object.Key));
const metadata = await s3.send(new HeadObjectCommand({ Bucket, Key: "documents/report.txt" }));
console.log({ contentType: metadata.ContentType, metadata: metadata.Metadata });
const object = await s3.send(new GetObjectCommand({ Bucket, Key: "documents/report.txt" }));
writeFileSync("download.txt", await object.Body.transformToByteArray());
JS
node --env-file=.env.s3 inspect.mjs
В списке должны присутствовать documents/report.txt и retained/handbook.txt. Для отчёта должны отображаться text/plain, team: blue и revision: 1. Порядок вывода метаданных может отличаться.
cmp сравнивает байты и ничего не выводит, если файлы совпадают. Следующее сообщение появится только при успешном сравнении:
cmp document.txt download.txt && printf "Downloaded bytes match\n"
Обновите список объектов этого же бакета в Dashboard и откройте сведения об отчёте. Сравните его ключ и тип содержимого с выводом SDK. Если текущий Dashboard не показывает пользовательское поле метаданных, используйте вывод CLI как подтверждение метаданных.

В примере показаны text/plain, revision 1, team blue и предпросмотр синтетического отчёта. Имя бакета и дата создания будут отличаться.
Удалите только выбранный отчёт
На этом шаге вы удалите один полный ключ объекта, сохранив справочник. Префикс — не каталог, который можно удалить рекурсивно; передайте API ровно ключ отчёта.
cat > remove-report.mjs <<'JS'
import { DeleteObjectCommand, ListObjectsV2Command } from "@aws-sdk/client-s3";
import { s3, Bucket } from "./storage.mjs";
await s3.send(new DeleteObjectCommand({ Bucket, Key: "documents/report.txt" }));
const page = await s3.send(new ListObjectsV2Command({ Bucket }));
console.log(page.Contents.map(object => object.Key));
JS
node --env-file=.env.s3 remove-report.mjs
Должен остаться только retained/handbook.txt. Проверка платформы также скачивает справочник, чтобы подтвердить, что его содержимое не изменилось. Выполните эту проверку до перехода к полной очистке.
Очистите принадлежащий вам бакет
На этом шаге вы удалите оставшийся объект, а затем пустой бакет. Не отключайте учётные данные, пока не подтвердите удаление удалённых ресурсов.
cat > cleanup.mjs <<'JS'
import { DeleteObjectCommand, ListObjectsV2Command } from "@aws-sdk/client-s3";
import { s3, Bucket } from "./storage.mjs";
await s3.send(new DeleteObjectCommand({ Bucket, Key: "retained/handbook.txt" }));
const page = await s3.send(new ListObjectsV2Command({ Bucket }));
console.log("Remaining objects:", page.KeyCount);
JS
node --env-file=.env.s3 cleanup.mjs
Убедитесь, что выведено Remaining objects: 0. Если вы открыли новый терминал, прочитайте сгенерированное имя бакета из конфигурации; node -p выводит это единственное поле.
BUCKET=$(node -p "JSON.parse(require('fs').readFileSync('wrangler.jsonc')).r2_buckets[0].bucket_name")
npx wrangler r2 bucket delete "$BUCKET" --env-file=.env.management
В ответ на запрос подтвердите удаление только точного бакета этой лабораторной работы. Снова выведите список бакетов: успешный список не должен содержать это имя. Ошибка аутентификации или сети не подтверждает удаление.
npx wrangler r2 bucket list --env-file=.env.management
Обновите тот же список в Dashboard, затем выполните проверку платформы этого шага, пока вы ещё авторизованы.
Отзовите учётные данные лабораторной работы и выйдите из системы
На этом шаге вы закроете доступ, оставшийся после выполнения упражнения. На странице R2 API Tokens отзовите только токен для работы с объектами, созданный для этой лабораторной работы. На странице API Tokens в профиле отзовите отдельный токен управления R2, созданный для этой лабораторной работы. Удаление бакета не отзывает токен, а выход из Wrangler не отзывает учётные данные S3.
После отзыва удалите локальные файлы с учётными данными и выйдите из системы на этой виртуальной машине:
rm .env.s3 .env.management
npx wrangler logout
Проверьте структурированную информацию об идентификации. Ненулевой код завершения после выхода из системы ожидаем:
npx wrangler whoami --json || true
Убедитесь, что указано loggedIn: false; обычный вход в Dashboard оставьте активным. Проверки платформы подтверждают удаление локальных файлов с учётными данными и выход из Wrangler. Отзыв обоих токенов выполняется вручную в Dashboard и является отдельным контрольным пунктом этой лабораторной работы; по удалению файлов он не определяется.
Резюме
Вы создали приватный бакет R2, сохранили байты объектов и метаданные, просмотрели и скачали документы, подтвердили выборочное удаление и закрыли доступ к бакету.



