Amazon S3 と API の互換性がある Cloudflare R2 を AWS CLI から使ってみる

Amazon S3 と互換性があるので AWS CLI から使ってみました。ポイントは --profile オプションと --endpoint-url オプションを指定することです。

ウィスキー、シガー、パイプをこよなく愛する大栗です。

先日 Cloudflare では Platform Week と題して、様々な発表を行っていました。その中でAmazon S3 と API に互換性のある R2 のオープンベータを開始したとの発表もありました。S3 と互換性があるということで AWS CLI から使ってみました。

Cloudflare R2

Cloudflare R2 は 2021 年 9 月に発表されたオブジェクトストレージで、Amazon S3 と API の互換性を持ち、エグレス料金がかからないという特徴を持ったサービスです。

R2 の API

R2 には Cloudflare Workers からアクセスする時に使用する In-Worker API と <ACCOUNT_ID>.r2.cloudflarestorage.com の形式の URL でバケットを公開する S3-compatible API という 2 種類の API があります。以下のエントリでは Workers から Service Binding を使って In-Worker API で R2 へアクセスしていました。

もう一つの API である S3-compatible API は、API レベルで Amazon S3 と互換性があります。ただし、全ての Amazon S3 の API が実装されている訳ではないのでご注意ください。一般的に使用される API は概ね実装されているためご安心ください。詳しくはドキュメントをご参照ください。

S3 API compatibility

R2 の制限

特徴 制限
バケット アカウントあたり1000バケット
バケットごとのデータストレージ 無制限
オブジェクトサイズ オブジェクトあたり 5 TB
最大アップロードサイズ 5 GB
バケットごとの Class A 操作 1 秒あたり 250 回
バケットごとの Class B 操作 1 秒あたり 1000 回

R2 の料金

R2 の料金での特徴的な点はエグレス料金つまり、アウトバウントのデータ転送量に対する料金が無料である点です。

R2 で発生する料金は保存しているデータ量と以下の 2 種類の操作に基づいて課金されます。

  • Class A 操作:状態を変化させる傾向があるより高い操作
    • ListBuckets,PutBucket,ListObjects,PutObject,CopyObject,CompleteMultipartUpload,CreateMultipartUpload,UploadPart,UploadPartCopyが含まれます。
  • Class B 操作:既存の状態を読み取る傾向のある操作
    • HeadBucket,HeadObject,GetObjectが含まれます。

開発者向けの無料枠もあり、支払い枠のレートでも代表的なオブジェクトストレージサービスより安価になっています。

無料枠 支払いレート
ストレージ 10 GB/月 $0.015/GB月
Class A 操作 1,000,000 リクエスト/月 $4.50/100万リクエスト
Class B 操作 10,000,000 リクエスト/月 $0.36/100万リクエスト

R2 のパフォーマンス

Cloudflare の最優先事項は、パフォーマンスと信頼性の向上とのことです。オープンベータ中は、バケット当たり最大 1,000 rps の GET 操作と 100 rps の PUT 操作を維持できます。

また R2 ではリージョンを選択することはありません。R2 のビジョンとして自動的にグローバル分散したストレージを構築してリクエスト元に最も近いストレージリージョンにシームレスに配置するというものがあります。

現在は主に北米にデータを保存しているため、他の地域からのアクセスではレイテンシが高くなる可能性があります。リージョン間でオブジェクトの自動移行する機能を追加する前に、オブジェクトを作成できるリージョンを追加してこの問題に対応する予定となっています。Durable Objects の管轄制限と同様に、プライバシー規制に準拠するように R2 バケットの配置場所を制限することも可能になります。

やってみた

実際に AWS CLI を使用して R2 にアクセスしてみます。ここで AWS CLI は現時点での 2 系の最新バージョンである 2.7.4 を使用しています。

$ aws --version
aws-cli/2.7.4 Python/3.9.11 Darwin/21.4.0 exe/x86_64 prompt/off

認証情報の発行

Cloudflare のコンソールで R2 の画面を表示します。後で使用するため Account ID をメモしておきます。Manage R2 API Tokensをクリックします。

Create API Tokenをクリックします。

Token nameにトークンの名称を入力して、Permissionsで権限を選択します。書き込みをしたいためEditを選択して、Create API Tokenをクリックしてトークンを発行します。

発行されたトークンのAccess Key IDSecret Access Keyをメモします。

AWS CLI に認証情報を設定します。

aws configureコマンドを実行します。ここでは--profileオプションを付けて、デフォルトでないプロファイルを作成しています。AWS Access Key IDAWS Secret Access Keyに先程発行した Access Key ID と Secret Access Key を各々入力します。今の所 R2 にはリージョンがないのでDefault region nameは何も入力しません。Default output formatは JSON にしておきました。

$ aws configure --profile <Profile Name>
AWS Access Key ID [None]: <Access Key ID>
AWS Secret Access Key [None]: <Secret Access Key>
Default region name [None]:
Default output format [None]: json

これで準備が完了しました。

AWS CLI の実行

AWS CLI で R2 にアクセスします。注意点は以下の 2 点です。

  1. 作成したプロファイルを指定する
  2. --endpoint-urlオプションで自分の R2 の URL を指定する

R2 の URL は <ACCOUNT_ID>.r2.cloudflarestorage.com となります。ACCOUNT_IDはR2 の画面の右上で確認できます。

AWS CLI には S3 を操作するためのコマンド群が 2 種類あり、API レベルのコマンドであるaws s3apiと、高レベルコマンドのaws s3です。ここでは一般的に使用される高レベルコマンドのaws s3を使用していきます。

まずはバケットの一覧を見てみます。まだバケットを作成していないため何も表示されません。

$ aws s3 ls --profile <Profile Name> --endpoint-url https://<ACCOUNT_ID>.r2.cloudflarestorage.com

バケットを作成します。R2 にはリージョンがないので指定せずに実行するとエラーになってしまいました。リージョンをautoで指定する必要があります。

aws s3 mb s3://test-bucket-1 --profile <Profile Name> --endpoint-url https://<ACCOUNT_ID>.r2.cloudflarestorage.com
make_bucket failed: s3://test-bucket-1 An error occurred (InvalidRegionName) when calling the CreateBucket operation: The region name 'aws-global' is not valid. Must be 'auto'

リージョンをautoで指定して再度実行します。なおバケット名ですが S3 ではグローバルで一意の名称にしなければなりませんでしたが、R2 ではアカウント内で一意であればよいためアカウントが異なれば同じ名称のバケットを作成できます

aws s3 mb s3://test-bucket-1 --region auto --profile <Profile Name> --endpoint-url https://<ACCOUNT_ID>.r2.cloudflarestorage.com
make_bucket: test-bucket-1

もう一つバケットを作成します。

aws s3 mb s3://test-bucket-2 --region auto --profile <Profile Name> --endpoint-url https://<ACCOUNT_ID>.r2.cloudflarestorage.com
make_bucket: test-bucket-2

バケット一覧を確認します。

$ aws s3 ls --profile cloudflare-r2-test --endpoint-url https://016710547c15a9a30e9598cbfff93d9a.r2.cloudflarestorage.com
2022-05-30 13:52:43 test-bucket-1
2022-05-30 14:00:58 test-bucket-2

ファイルをアップロードするためaws s3 cpコマンドを使用します。このようにファイルをアップロードできます。`

$ aws s3 cp ./test1.txt s3://test-bucket-1/folder1/ --profile <Profile Name> --endpoint-url https://<ACCOUNT_ID>.r2.cloudflarestorage.com
upload: ./test1.txt to s3://test-bucket-1/folder1/test1.txt
$ aws s3 ls s3://test-bucket-1/folder1/ --profile <Profile Name> --endpoint-url https://<ACCOUNT_ID>.r2.cloudflarestorage.com
2022-05-30 14:55:22         34 test1.txt

バケット間でファイルをコピーします。

aws s3 cp s3://test-bucket-1/folder1/test1.txt s3://test-bucket-2/folder2/ --profile <Profile Name> --endpoint-url https://<ACCOUNT_ID>.r2.cloudflarestorage.com
copy: s3://test-bucket-1/folder1/test1.txt to s3://test-bucket-2/folder2/test1.txt

もう一つファイルをアップロードして、バケット間で同期してみます。

$ aws s3 cp ./test2.txt s3://test-bucket-1/folder1/ --profile <Profile Name> --endpoint-url https://<ACCOUNT_ID>.r2.cloudflarestorage.com
upload: ./test2.txt to s3://test-bucket-1/folder1/test2.txt
$ aws s3 sync s3://test-bucket-1 s3://test-bucket-2 --profile <Profile Name> --endpoint-url https://<ACCOUNT_ID>.r2.cloudflarestorage.com
copy: s3://test-bucket-1/folder1/test1.txt to s3://test-bucket-2/folder1/test1.txt
copy: s3://test-bucket-1/folder1/test2.txt to s3://test-bucket-2/folder1/test2.txt

次に--aclオプションでpublic-readを付けてコピーを実行してみます。x-amz-aclヘッダーでpublic-readは実装されていないためエラーとなります。そもそも現時点では R2 単体でオブジェクトを公開する機能がないためだと思われます。

$ aws s3 cp s3://test-bucket-1/folder1/test1.txt s3://test-bucket-2/folder3/ --acl public-read --profile <Profile Name> --endpoint-url https://<ACCOUNT_ID>.r2.cloudflarestorage.com
copy failed: s3://test-bucket-1/folder1/test1.txt to s3://test-bucket-2/folder3/test1.txt An error occurred (NotImplemented) when calling the CopyObject operation: Header 'x-amz-acl' with value 'public-read' not implemented

--aclオプションをpublic-readではなくprivateにすると問題なくコピーできました。パブリックバケットは今後のロードマップとして明記してある機能ですので実装を待ちましょう。

aws s3 cp s3://test-bucket-1/folder1/test1.txt s3://test-bucket-2/folder3/ --acl private --profile <Profile Name> --endpoint-url https://<ACCOUNT_ID>.r2.cloudflarestorage.com
copy: s3://test-bucket-1/folder1/test1.txt to s3://test-bucket-2/folder3/test1.txt

また、実装されていないListMultipartUploadsAPI も試してみましょう。こちらは API レベルのコマンドであるaws s3apiで実行してみます。するとListMultipartUploadsが実装されていないというエラーが表示されます。

$ aws s3api list-multipart-uploads --bucket test-bucket-1 --profile cloudflare-r2-test --endpoint-url https://<ACCOUNT_ID>.r2.cloudflarestorage.com

An error occurred (NotImplemented) when calling the ListMultipartUploads operation: ListMultipartUploads not implemented

最後に

Amazon S3 と互換性があるということなので、AWS CLI を使ってアクセスしてみました。R2 が使える機能であれば問題なく使用できそうだということがわかりました。しかし Amazon S3 に実装されており R2 に実装されていない機能は当然使用できないためエラーになります。現時点では Cloudflare Workers との連携やエグレス料金がかからないメリットなどが重要な場合など利用用途を明確にして使用するべきかと思います。将来的にリクエスト元に最も近いストレージリージョンにシームレスにデータを配置するというビジョンがあるので、とても期待をしているサービスです。

現在はオープンベータですが、先に検証をしておくと一般提供になったときに直ぐに使い始めることができます。積極的に検証して、Cloudflare へフィードバックをしていきましょう。