title

Cloudflare R2を試してみる

2023/12/8

Cloudflare

Cloudflareが提供するオブジェクトストレージサービスを試してみます。


Cloudflare R2を試してみる

公式ドキュメントを読みながら進めてみます。

Cloudflare R2 - Get Started

CloudflareのCLIツールであるwrangler(ラングラー)を使用するみたいなので、npmでインストールをしてログインしてみます。

npm install -g wrangler
wrangler login

同意画面的なものが出てから先に進むと認証が完了します。

Wrangler認証画面

バケットを作成する

wrangler r2 bucket list

下記のエラーが出ます。

✘ [ERROR] A request to the Cloudflare API (/accounts/*****/r2/buckets) failed.
Please enable R2 through the Cloudflare Dashboard. [code: 10042]

CloudflareのダッシュボードからR2を開き、overviewへ移動するとSubscription画面になります。

R2 Subscription画面

ホビーユースの範囲では基本的に無料な感じです。とりあえず以下のように、

  • Class A operations
    • POST・PUT系のバケット作成やオブジェクトアップロード・コピー
  • Class B operations
    • GET系のバケット取得やサマリ取得
  • Free operations
    • DELETE系のバケット・オブジェクトの削除など

といった雰囲気で認識しています。

Cloudflare R2 - Pricing

改めてコマンドを実行します。

wrangler r2 bucket list
[]

何も作っていないので空の配列が返ってきました。とりあえず新しくバケットを作成します。

Cloudflare R2 - Create Buckets

wrangler r2 bucket create BUCKET_NAME

バケット名は、このサイトでの利用を想定してephy-devとしました。

wrangler r2 bucket list
[
  {
    "name": "ephy-dev",
    "creation_date": "2023-12-08T15:38:00.065Z"
  }
]

WebUIの方を確認してみるとバケットが作成されていることが確認できます。ファイル・フォルダでのアップロードを行うことができます。フォルダに関しては、Finderなどから直接ファイル自体をD&Dするだけで中身がアップロードされました。

Cloudflare R2のWebUI

ドメインの設定

R2のバケットに対して直接カスタムドメインも設定できるみたいなので、早速設定してみました。カスタムドメインを設定すればCloudflareのCacheが効くなどの恩恵が得られます。

設定はバケットのSettingsから行います。

Cloudflare R2 カスタムドメイン設定

おそらくですが、サブドメインなしで入力するとそのままメインのドメインとR2のバケットが紐づいてしまうかもしれないので、サブドメインは入れておきましょう。今回はstorage.ephy.devとしています。

Continueを押下して進むと設定は完了します。Cloudflareの管理画面からドメインのWebsiteを開いてDNS設定を確認しに行くと、自動で追加されていることが確認できました。

DNS設定画面

R2のバケットに戻り、先程アップロードした画像ファイルを開くとCustomDomainsの項目が増えていて、パブリックに参照できるリンクが添えられています。

R2にアップロードしたファイルのパブリックリンク

カスタムドメインに関しては、設定したいR2バケットとドメイン・Websiteを管理しているアカウントが同一である必要があったり、HTTPS必須などの制約があるようです。

Cloudflare R2 - Custom Domain

カスタムドメインを設定しない場合はCloudflareが提供するr2.devドメインを利用することが出来ます。ただし、「レートリミットなどが設定されているので開発用途に使用してね。」といった感じのことが書かれています。

開発用ドメインの設定

wranglerからのオブジェクト操作

wranglerからは以下のようなコマンドでオブジェクトを操作します。ヘルプからも確認ができます。

wrangler r2 object help

PUT

R2上での配置先とファイル名、--fileにローカルのファイルを指定という感じです。

wrangler r2 object put ephy-dev/test-put.jpg --file=my_file.jpg

GET

R2上のファイルパスとファイル名を指定して取得します。ファイルは実行した場所に保存されました。

wrangler r2 object get ephy-dev/test-put.jpg

DELETE

R2上のファイルパスとファイル名を指定して削除します。

wrangler r2 object delete ephy-dev/test-put.jpg

Cloudflare WorkersによるR2の操作

Cloudflare R2 - Workers API Usage

Cloudflare Workersのテンプレートを利用してプロジェクトを作成します。npm createコマンドでアレコレ対話式で選択していくだけで初期構築が終わります。

npm create cloudflare@latest

途中のテンプレ内容選択は一旦"Hello World" Workerを選択

├ What type of application do you want to create?
│ type "Hello World" Worker

Workerの方はお試しなので、先ほど作成したバケットとは別のバケットにするために別途作成します。

wrangler r2 bucket create ephy-worker-test

チュートリアルに沿ってwrangler.tomlを修正します。wranglerのwhoamiコマンドを使用すると自身のアカウントIDがわかります。見た感じですが、CloudflareのダッシュボードURLに含まれているハッシュ値みたいなやつがアカウントIDです。

wrangler whoami

アカウントIDの設定と一緒にR2のバケット名も設定します。bindingプロパティはJavaScript上で環境変数としてアクセスするための定数です。

account_id = "YOUR_ACCOUNT_ID"
workers_dev = true

[[r2_buckets]]
binding = "MY_BUCKET"
bucket_name = "ephy-worker-test"

とりあえずチュートリアルの通りにindex.jsを修正します。先程のbindingで設定した定数へのアクセスしているコードがあるので、bindingをMY_BUCKET以外に設定した場合は、適宜修正する必要があります。

hasValidHeader()で認証キーのチェックをし、ALLOW_LISTにはアクセスできるファイルが定義されています。

const ALLOW_LIST = ["cat-pic.jpg"];
const hasValidHeader = (request, env) => {
  return request.headers.get("X-Custom-Auth-Key") === env.AUTH_KEY_SECRET;
};
function authorizeRequest(request, env, key) {
  switch (request.method) {
    case "PUT":
    case "DELETE":
      return hasValidHeader(request, env);
    case "GET":
      return ALLOW_LIST.includes(key);
    default:
      return false;
  }
}
export default {
  async fetch(request, env, ctx) {
    const url = new URL(request.url);
    const key = url.pathname.slice(1);

    if (!authorizeRequest(request, env, key)) {
      return new Response("Forbidden", { status: 403 });
    }

    switch (request.method) {
      case "PUT":
        await env.MY_BUCKET.put(key, request.body);
        return new Response(`Put ${key} successfully!`);
      case "GET":
        const object = await env.MY_BUCKET.get(key);

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

        const headers = new Headers();
        object.writeHttpMetadata(headers);
        headers.set("etag", object.httpEtag);

        return new Response(object.body, {
          headers,
        });
      case "DELETE":
        await env.MY_BUCKET.delete(key);
        return new Response("Deleted!");

      default:
        return new Response("Method Not Allowed", {
          status: 405,
          headers: {
            Allow: "PUT, GET, DELETE",
          },
        });
    }
  },
};

動作させるためにAUTH_KEY_SECRETをwrangler経由で取得します。

wrangler secret put AUTH_KEY_SECRET
 ⛅️ wrangler 3.19.0
-------------------
✔ Enter a secret value: … ***********
🌀 Creating the secret for the Worker "***-*****-****"
✨ Success! Uploaded secret AUTH_KEY_SECRET

上記の操作で行った内容は、Workersの管理画面にあるSettingsに設定が追加されています。

バケット変数

先程のbindingsへもtomlの通りに設定されています。

デプロイします。

wrangler deploy

デプロイが完了したら試しにcurlコマンドでworkersのエンドポイントを叩いてみます。your-worker.devは自分のworkerのドメインに置き換えます。

curl https://your-worker.dev/cat-pic.jpg -X PUT --data-binary 'test'
Put cat-pic.jpg successfully!

cat-pic.jpgしか許可していないので以下のcurlを試すと前者はForbiddenになり、後者のみ成功します。

curl https://your-worker.dev/foo
curl https://your-worker.dev/cat-pic.jpg

R2のオブジェクトに対してアクセス制御や細かな処理を自前でやりたいという場合はWorkersを使用して処理するという感じのようです。画像最適化に関してはCloudflareにImagesみたいなサービスがあったような気がするのと、$5からなのでまたいつか触ってみようと思います。

Cloudflare R2 - Workers API Reference

終わりに

シンプルで使いやすいと思いました。Cloudflareにドメイン置いていたりする場合の手軽さもすごいですね。

今回試したこと以外にS3互換APIの提供もありドキュメントも充実しているようなので、機会があったら利用してみたいと思います。

Cloudflare R2 - S3 API compatibility

Cloudflare R2 - Presined URL