mcpField
C
cloudflare-r2

cloudflare-r2

github.com/bagelholeクラウド・DevOps1.2k339 回インストール23 週間前に更新
リポジトリ

cloudflare-r2 について

Cloudflare R2 オブジェクトストレージを扱うスキルです。ファイル、メディア、バックアップ、モデル成果物を保存する開発者向けに、S3 互換アクセスや Wrangler、Worker バインディング、公開バケットでの操作を説明します。

主な機能

  1. 01Wrangler によるバケットとオブジェクトの管理
  2. 02S3 API 経由の R2 アクセス
  3. 03バケットの Workers へのバインド
  4. 04公開アクセスとカスタムドメインの設定
  5. 05ライフサイクルと CORS ルールの設定

活用シーン

  1. 01ユーザーアップロードやメディアの保存
  2. 02バックアップや静的アセットの保管
  3. 03Workers を介したエッジでのファイル配信
  4. 04機械学習の成果物やトレーニングデータの保存

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/bagelhole/devops-security-agent-skills --skill cloudflare-r2

インストール後、Claude Code で/cloudflare-r2を入力して読み込まれたか確認します。

詳細

Cloudflare R2

Cloudflare のグローバルネットワークを基盤とする、エグレス料金が無料の S3 互換オブジェクトストレージです。

使用する場面

  • ユーザーのアップロードファイル、メディアファイル、バックアップ、静的アセットを保存する場合。
  • 読み取りの多いワークロードでエグレスコストをなくすために AWS S3 を置き換える場合。
  • Workers やパブリックバケットアクセスを利用して、エッジからファイルを配信する場合。
  • ベンダーロックインを避けるマルチクラウドストレージを構築する場合(S3 API 互換)。
  • ML モデルのアーティファクト、トレーニングデータ、推論結果を保存する場合。

前提条件

  • R2 を有効にした Cloudflare アカウント(ダッシュボード > R2 > subscribe)。
  • Wrangler CLI v3+ がインストールされていること:npm install -g wrangler。
  • wrangler login または CLOUDFLARE_API_TOKEN を使って認証済みであること。
  • S3 API にアクセスする場合:R2 > Manage R2 API Tokens で R2 API トークンを作成していること。

Wrangler によるバケット管理

バケットの作成と一覧表示
# Create a new bucket
npx wrangler r2 bucket create app-assets

# Create a bucket in a specific region (hint for data locality)
npx wrangler r2 bucket create eu-uploads --location=eu

# List all buckets
npx wrangler r2 bucket list

# Delete an empty bucket
npx wrangler r2 bucket delete old-bucket
オブジェクトの操作
# Upload a single file
npx wrangler r2 object put app-assets/images/logo.png --file=./logo.png

# Upload with content type
npx wrangler r2 object put app-assets/data/report.json \
  --file=./report.json \
  --content-type="application/json"

# Download an object
npx wrangler r2 object get app-assets/images/logo.png --file=./downloaded-logo.png

# Delete an object
npx wrangler r2 object delete app-assets/images/old-logo.png

# Get object metadata
npx wrangler r2 object head app-assets/images/logo.png

S3 互換 API アクセス

R2 は S3 API をサポートしているため、既存のツール(AWS CLI、boto3、s3cmd)をそのまま利用できます。

R2 API トークンの生成
  1. R2 > Manage R2 API Tokens > Create API token に移動します。
  2. 権限を選択します:Object Read & Write、または Object Read only。
  3. 可能であれば、特定のバケットに対象を限定します。
  4. Access Key ID と Secret Access Key を保存します。
AWS CLI の設定
# Configure a named profile for R2
aws configure --profile r2
# Access Key ID: <your-r2-access-key>
# Secret Access Key: <your-r2-secret-key>
# Region: auto
# Output: json

# Use the R2 endpoint
export R2_ENDPOINT="https://<ACCOUNT_ID>.r2.cloudflarestorage.com"

# List buckets
aws s3 ls --endpoint-url=$R2_ENDPOINT --profile=r2

# Sync a directory
aws s3 sync ./dist s3://app-assets/static/ \
  --endpoint-url=$R2_ENDPOINT \
  --profile=r2

# Copy a file
aws s3 cp ./backup.tar.gz s3://app-assets/backups/backup-$(date +%Y%m%d).tar.gz \
  --endpoint-url=$R2_ENDPOINT \
  --profile=r2

# List objects with prefix
aws s3 ls s3://app-assets/images/ \
  --endpoint-url=$R2_ENDPOINT \
  --profile=r2

# Remove objects by prefix
aws s3 rm s3://app-assets/tmp/ --recursive \
  --endpoint-url=$R2_ENDPOINT \
  --profile=r2
Python boto3 クライアント
import boto3

s3 = boto3.client(
    "s3",
    endpoint_url="https://<ACCOUNT_ID>.r2.cloudflarestorage.com",
    aws_access_key_id="<R2_ACCESS_KEY>",
    aws_secret_access_key="<R2_SECRET_KEY>",
    region_name="auto",
)

# Upload file
s3.upload_file("./report.pdf", "app-assets", "reports/report.pdf")

# Generate presigned URL (valid for 1 hour)
url = s3.generate_presigned_url(
    "get_object",
    Params={"Bucket": "app-assets", "Key": "reports/report.pdf"},
    ExpiresIn=3600,
)
print(url)

# List objects
response = s3.list_objects_v2(Bucket="app-assets", Prefix="images/", MaxKeys=100)
for obj in response.get("Contents", []):
    print(f"{obj['Key']} - {obj['Size']} bytes")

Worker バインディング

API トークンを使わずにサーバー側からアクセスできるよう、R2 バケットを Workers または Pages Functions にバインドします。

Wrangler の設定
# wrangler.toml
name = "asset-worker"
main = "src/index.ts"
compatibility_date = "2024-09-01"

[[r2_buckets]]
binding = "ASSETS"
bucket_name = "app-assets"

[[r2_buckets]]
binding = "UPLOADS"
bucket_name = "user-uploads"
R2 操作用 Worker
// src/index.ts
interface Env {
  ASSETS: R2Bucket;
  UPLOADS: R2Bucket;
}

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const url = new URL(request.url);

    // GET — serve file from R2
    if (request.method === "GET") {
      const key = url.pathname.slice(1); // strip leading /
      const object = await env.ASSETS.get(key);

      if (!object) {
        return new Response("Not Found", { status: 404 });
      }

      const headers = new Headers();
      object.writeHttpMetadata(headers);
      headers.set("etag", object.httpEtag);
      headers.set("cache-control", "public, max-age=86400");

      return new Response(object.body, { headers });
    }

    // PUT — upload file to R2
    if (request.method === "PUT") {
      const key = url.pathname.slice(1);
      const contentType = request.headers.get("content-type") || "application/octet-stream";

      await env.UPLOADS.put(key, request.body, {
        httpMetadata: { contentType },
        customMetadata: { uploadedAt: new Date().toISOString() },
      });

      return new Response(JSON.stringify({ key, status: "uploaded" }), {
        headers: { "Content-Type": "application/json" },
      });
    }

    // DELETE — remove file
    if (request.method === "DELETE") {
      const key = url.pathname.slice(1);
      await env.UPLOADS.delete(key);
      return new Response(null, { status: 204 });
    }

    return new Response("Method Not Allowed", { status: 405 });
  },
};
Worker での署名付き URL の生成
// Generate time-limited signed URLs using Workers
import { AwsClient } from "aws4fetch";

interface Env {
  R2_ACCESS_KEY: string;
  R2_SECRET_KEY: string;
  R2_ACCOUNT_ID: string;
}

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const aws = new AwsClient({
      accessKeyId: env.R2_ACCESS_KEY,
      secretAccessKey: env.R2_SECRET_KEY,
    });

    const url = new URL(request.url);
    const key = url.searchParams.get("key");
    if (!key) return new Response("Missing key", { status: 400 });

    const r2Url = `https://${env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com/app-assets/${key}`;

    const signed = await aws.sign(new Request(r2Url), {
      aws: { signQuery: true },
    });

    return Response.json({ url: signed.url });
  },
};

パブリックバケットアクセス

Worker を使わずにファイルを直接配信するには、パブリックアクセスを有効にします。

  1. R2 > bucket > Settings > Public access に移動します。
  2. 有効にして、カスタムドメイン(例:assets.example.com)を設定します。
  3. オブジェクトには https://assets.example.com/<key> でアクセスできます。
# Or enable via the r2.dev subdomain (for testing)
# Bucket Settings > R2.dev subdomain > Allow Access
# URL: https://pub-<hash>.r2.dev/<key>

ライフサイクルルール

オブジェクトの有効期限切れや移行を自動化するよう設定します。

# Set lifecycle rules via the Cloudflare dashboard:
# R2 > bucket > Settings > Object lifecycle rules

# Or via API
curl -X PUT "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/r2/buckets/app-assets/lifecycle" \
  -H "Authorization: Bearer $CF_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "rules": [
      {
        "id": "expire-tmp-files",
        "enabled": true,
        "conditions": { "prefix": "tmp/" },
        "actions": { "deleteObject": { "daysAfterCreationDate": 7 } }
      },
      {
        "id": "expire-old-logs",
        "enabled": true,
        "conditions": { "prefix": "logs/" },
        "actions": { "deleteObject": { "daysAfterCreationDate": 90 } }
      }
    ]
  }'

CORS の設定

# Set CORS policy for browser-based uploads
curl -X PUT "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/r2/buckets/app-assets/cors" \
  -H "Authorization: Bearer $CF_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "corsRules": [
      {
        "allowedOrigins": ["https://example.com"],
        "allowedMethods": ["GET", "PUT", "HEAD"],
        "allowedHeaders": ["Content-Type", "Authorization"],
        "maxAgeSeconds": 3600
      }
    ]
  }'

トラブルシューティング

症状原因対処法
S3 API 経由で NoSuchBucket エラーが発生するエンドポイントまたはバケット名が誤っているエンドポイントが https://<ACCOUNT_ID>.r2.cloudflarestorage.com であることを確認します
SignatureDoesNotMatchシークレットキーが誤っているか、エンドポイントが一致していないR2 API トークンを再生成し、リージョンが auto であることを確認します
アップロードは成功するが、GET が 404 を返すキーのパスが一致していない(先頭にスラッシュがある)R2 のキーは / で始まらないようにします
大きなファイルのアップロードが遅い単一ストリームでアップロードしているマルチパートアップロードを使用し、wrangler で --expected-size を設定します
ブラウザーで CORS エラーが発生するバケットに CORS 設定がないオリジンドメインを対象とする CORS ルールを追加します
Worker バインディングが undefined を返すwrangler.toml のバインディング名が一致していないbinding 名が Env インターフェースのプロパティ名と一致することを確認します
パブリックアクセスで 403 が返るパブリックアクセスが有効になっていないバケットの Settings > Public access で有効にします

関連スキル

コメント