ドキュメントバケットを整理する

CloudflareBeginner
オンラインで実践に進む

はじめに

サポートチームには、小規模なドキュメントストアが必要です。オブジェクトは、ファイルのバイトデータとメタデータを合わせたものです。バケットはオブジェクトをまとめ、キーはオブジェクトの完全な名前を表します。キーにスラッシュを含めると便利なプレフィックスとして使えますが、通常のファイルシステムのディレクトリが作成されるわけではありません。この実験では、プライベートバケットを作成し、2 つの合成ドキュメントをアップロードしてメタデータを確認し、正確なバイトデータをダウンロードします。その後、指定したドキュメントだけを削除してから、後片付けを行います。

まず、LabEx を Cloudflare アカウントに接続するを完了してください。この実験では、LabEx ターミナル、デバイス認証、学習用アカウントの確認、アカウント ID の設定について学びます。この実験は /home/labex/project/r2-lab で独立して開始でき、Node.js 22.22.0、Wrangler 4.131.1、AWS SDK 3.888.0 が準備されています。自分のコンピューターでは、まず Node.js をインストールし、その後 npm install で Wrangler と AWS SDK をプロジェクトの依存関係としてインストールします。

開始前に確認すること: 学習用アカウントで R2 のサブスクリプションが有効になっている必要があります。Cloudflare の R2 のセットアップにはチェックアウトの手順が含まれています。R2 が有効になっていない場合は、自分で確認してください。Free アカウントでは R2 は自動的に有効になりません。ストレージ料金と操作料金については、料金を確認してください。この演習では小さな合成ファイルだけを使用し、購入済みのドメインは必要ありません。バケットを管理する権限と、この新しいバケットに限定したユーザー R2 トークンを作成する権限が必要です。パブリックアクセスは無効のままにしてください。認証情報をこのレッスン、チャット、スクリーンショットに貼り付けないでください。

プライベートなドキュメントバケットを作成する

このステップでは、この VM を認証し、使い捨てのバケットを 1 つ作成します。デバイス認証によって学習用アカウントを確認します。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

表示されたデバイスコードを、自分のブラウザーで認証してください。同意する前に、学習用アカウントと、要求されているアカウントおよびユーザーの読み取りスコープを確認します。

npx wrangler login --device --browser=false --scopes account:read user:read
npx wrangler whoami --json

loggedIn: true であることを確認してください。アカウントが 1 つだけ表示される場合でも、アカウント名を確認します。以下の YOUR_ACCOUNT_ID を、そのアカウントの実際の 32 文字の ID に置き換えてください。openssl rand -hex 6 は 12 個のランダムな 16 進数文字を生成するため、以前の実行で作成した名前と重複しません。ヒアドキュメントによって標準の設定ファイルが作成され、シェルが変数をその中に展開します。

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

バケットを管理するには、Cloudflare プロフィールの API Tokens ページを開き、この実験にちなんだ名前でカスタムトークンを作成します。Account → Workers R2 Storage → Edit を許可し、Account Resources を、保存した ID の学習用アカウントに限定します。有効期限は短く設定してください。他のアカウントや関係のない権限は含めないでください。このアカウントレベルの権限でバケットを作成・削除できます。次のステップで使用するオブジェクト専用トークンでは、これらの操作はできません。

トークンを 1 回だけ、非表示の VM プロンプトに入力します。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

R2 の管理コマンドに限って --env-file=.env.management を使用します。通常の whoami は、引き続き VM のデバイス認証を確認します。

--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 バケット

この例では Standard ストレージと Public Access Disabled が表示されています。生成されるバケット名は異なります。

メタデータ付きでドキュメントをアップロードする

このステップでは、SDK にこのバケットだけへのアクセスを許可し、2 つのドキュメントを保存します。Content type は、クライアントがバイトデータをどのように解釈するかを示します。カスタムメタデータは、オブジェクトと一緒に独自の小さなラベルを保存します。どちらもアクセス制御ルールではありません。

S3 互換 API により、標準的なストレージ SDK から R2 にアクセスできます。Wrangler のデバイストークンとは別に、アクセスキーのペアを使用します。R2 Overview で Account Details → Manage API Tokens を開き、この実験で生成したリソース名にちなんだ名前で User API token を作成します。Object Read & Write を選択し、この新しいバケットだけに限定します。フォームに有効期限の項目がある場合は、短い期限を選択してください。すべてのバケットや Admin アクセスは選択しないでください。1 回だけ表示されるシークレットを保存するまで、このトークンのページを開いたままにします。

VM で次の Bash プロンプトを使用します。read -s は入力内容を非表示にし、umask 077 は認証情報ファイルを自分のユーザーだけが読めるようにします。これらは AWS SDK の標準環境変数名です。Access Key ID と Secret Access Key をそれぞれのプロンプトに貼り付け、Enter キーを押してください。一般的な API トークンの値は貼り付けないでください。

トークンのフォームで TTL24 hours に設定し、作成前に対象バケットと 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 は、指定したキーにバイトデータを保存します。どちらも合成ドキュメントです。残す handbook によって、後で指定したオブジェクトだけを削除しても、関係のないキーが削除されないことを確認できます。

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

成功メッセージは、2 つの API 呼び出しが await によって完了した後にだけ表示されます。プラットフォームのチェックでは、実際のオブジェクトとメタデータを個別に読み取ります。

メタデータを一覧表示し、ダウンロードしたバイトデータを比較する

このステップでは、すべてのオブジェクトをダウンロードせずにキーを確認し、その後 report を取得します。ListObjectsV2 はキーを一覧表示し、HeadObject はメタデータだけを取得します。この小さなバケットは 1 ページの一覧に収まります。本番環境の一覧では、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.txtretained/handbook.txt が含まれます。report の ContentTypetext/plain で、メタデータは team: bluerevision: 1 です。メタデータの出力順序は異なる場合があります。

cmp はバイトデータを比較し、ファイルが一致した場合は何も表示しません。比較に成功した場合だけ、次のメッセージが表示されます。

cmp document.txt download.txt && printf "Downloaded bytes match\n"

同じバケットの Dashboard のオブジェクト一覧を更新し、report の詳細を開きます。キーと Content type を SDK の出力と比較してください。現在の Dashboard にカスタムメタデータの項目が表示されない場合は、CLI の出力をメタデータの根拠として使用します。

レポートオブジェクトの種類、カスタムメタデータ、プレビュー

この例では text/plain、revision 1、team blue と合成レポートのプレビューが表示されています。バケット名と作成日時は異なります。

指定した report だけを削除する

このステップでは、完全なオブジェクトキーを 1 つ削除し、handbook は残します。プレフィックスは再帰的に削除できるディレクトリではありません。API には report のキーを正確に指定してください。

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 だけです。プラットフォームのチェックでも handbook をダウンロードし、内容が変更されていないことを確認します。すべてを削除する前に、このチェックを実行してください。

自分が作成したバケットを整理する

このステップでは、残っているオブジェクトを削除してから、空になったバケットを削除します。リモート側の削除が確認されるまで、認証情報を有効なままにしてください。

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 の認証情報は失効しません。

失効させた後、ローカルの認証情報ファイルを削除し、この VM からログアウトします。

rm .env.s3 .env.management
npx wrangler logout

構造化された ID 情報を確認します。ログアウト後は終了ステータスが 0 以外になることが想定されています。

npx wrangler whoami --json || true

loggedIn: false であることを確認してください。通常の Dashboard ログインは維持します。プラットフォームのチェックでは、ローカルの認証情報削除と Wrangler のログアウトを確認します。2 つのトークンの失効は、この候補実験で手動で行う Dashboard のチェックポイントです。ファイルを削除しただけでは、トークンが失効したとは判断されません。

まとめ

プライベートな R2 バケットを作成し、オブジェクトのバイトデータとメタデータを保存して、ドキュメントの一覧表示とダウンロードを行いました。また、指定したオブジェクトだけが削除されることを確認し、バケットへのアクセスを整理しました。