Cloudflare R2のオブジェクトをrcloneでCloud Storageに自動バックアップする
最近はじめた新しいプロジェクトでは、オブジェクトストレージとしてCloudflare R2を選んでいます。R2の何よりも魅力は良心的な料金設定です。
バージョニングに対応してない問題
R2は2023年10月時点ではS3やGoogle Cloud Storageにあるようなバージョニング機能には対応していません。
要望は挙がっていますが(R2 Object Versioning and Replication - Cloudflare Community)今のところ特に動きはありません。
バージョニング機能なしの場合、コンソールやAPI経由でうっかりオブジェクトを削除/上書きしてしまったようなときに復元ができません。ユーザーのアップロードしたファイルを保管するようなWebサービスであれば、これは大きな不安要素になると思います。
rcloneでCloud Storageへバックアップ
バックアップ無しでサービスを運営したくないので、自分のプロジェクトではCloud Run jobsにより定期的にrcloneを実行し、R2からCloud Storageへとバックアップを取ることにしました。
Cloud Run jobsによりrcloneを定期実行する方法について、探してもほとんど情報が見つからなかったので記事にまとめることにしました。
(ちなみに)S3にしなかった理由
バックアップ用途では、Cloud StorageよりもS3の方が値段が安く抑えられそうでした。しかし、自分はGoogle Cloudの方が使い慣れており、Cloud Run jobsを使えば定期的にrcloneを実行する構成も組みやすそうだと考えて、Cloud Storageを選ぶことにしました。
1. rclone.confを作成
まずrclone.conf
を作成し、R2とCloud Storageの設定を定義します。
# Cloud Storage
[gcs]
type = google cloud storage
bucket_acl = private
# R2
[r2]
# s3互換なので`s3`とする
type = s3
provider = Cloudflare
endpoint = https://[CloudflareのアカウントIDをここに].r2.cloudflarestorage.com
アクセスキーの指定について
ここではr2
とgcs
の設定ともに、access_key_id
とsecret_access_key
を指定していません。
まず、Cloud StorageへのアクセスについてはrcloneをCloud Run上で実行するため、Cloud Runの実行サービスアカウントに適切なロールを付与してあげる(後述)ことでアクセスキーの指定は不要となります。
r2の方は、アクセスキーが必要になりますが、これらをrclone.conf
にハードコーディングするのは避けたいので、Cloud Runの環境変数として指定するのが良いと思います。具体的には以下の2つの命名で環境変数を設定すればr2
に対するアクセスキーとして認識してくれます。
RCLONE_CONFIG_R2_SECRET_ACCESS_KEY
RCLONE_CONFIG_R2_ACCESS_KEY_ID
(参考: https://rclone.org/docs/#config-file)
この記事では解説を省きますが、Secret Managerにシークレットを登録しておき、Cloud Runのデプロイコマンドを呼び出すときに環境変数として設定するのが安全だと思います。
2. Dockerfileを作成
rclone/rcloneというDockerイメージをベースとすれば、Dockerfileは簡潔に書くことができます。
FROM rclone/rclone
COPY ./rclone.conf /config/rclone/rclone.conf
# バックアップ目的なので`sync`ではなく`copy`に
CMD ["copy", "r2:バケット名", "gcs:バケット名"]
コマンド/オプションについてはドキュメントを参考に調整してください。
3. Cloud Storageのバケットを用意
※ Google Cloudの設定はTerraformから行ったのでその記述と簡単な解説を載せておきます。
# R2からバックアップするためのCloud Storageバケット
resource "google_storage_bucket" "backup_bucket" {
name = "r2-backup"
location = "US-CENTRAL1"
storage_class = "ARCHIVE"
uniform_bucket_level_access = false # falseでないとエラーになったので
}
最もコストが低くなるようにロケーションはus-central1
に、ストレージのクラスはarchive
にしています。
4. Cloud Run jobsの実行用のサービスアカウントを作成
# ジョブ実行用のサービスアカウント
resource "google_service_account" "backup_job_service_account" {
project = var.project_id
account_id = "rclone-backup-service-account"
display_name = "バックアップジョブ実行用のサービスアカウント"
}
# サービスアカウントからCloud Storageにアクセスできるようにする
resource "google_project_iam_member" "rclone_backup_service_account_gcs_admin" {
project = var.project_id
role = "roles/storage.admin"
member = "serviceAccount:${google_service_account.backup_job_service_account.email}"
}
Cloud Run jobs実行用のサービスアカウントを作成したうえで、Cloud StorageへアクセスできるようにIAMロールを付与しておきます。
5. Cloud Buildからデプロイ
今回はGitHubのmainブランチへpushされたときにCloud Buildにより以下のデプロイが実行されるようにしました。
- Cloud Run jobs: rcloneを実行するジョブ
- Cloud Scheduler: ジョブを定期的に呼び出すためのもの
Cloud Buildを使わずにローカルからgcloud
コマンドで設定していくのであれば以下のページが参考になるかもしれません。
Cloud Buildのサービスアカウントにロールを付与する
事前にCloud Buildのサービスアカウントに必要なロールを付与しておく
Cloud Buildの実行前に、Cloud Buildのサービスアカウントに必要なロールを付与しておきます。デフォルトのサービスアカウントにロールを追加する場合は以下のようになると思います。
# Cloud BuildのサービスアカウントがArtifact Registryにアクセスできるように
resource "google_project_iam_member" "cloudbuild_artifact_registry_repo_admin" {
project = var.project_id
role = "roles/artifactregistry.repoAdmin"
member = "serviceAccount:${data.google_project.project.number}@cloudbuild.gserviceaccount.com"
}
# Cloud BuildのサービスアカウントがCloud Runをデプロイできるように
resource "google_project_iam_member" "cloudbuild_artifact_registry_repo_admin" {
project = var.project_id
role = "roles/run.admin"
member = "serviceAccount:${data.google_project.project.number}@cloudbuild.gserviceaccount.com"
}
# Cloud BuildのサービスアカウントがCloud Schedulerを管理できるように
resource "google_project_iam_member" "cloudbuild_scheduler_admin" {
project = var.project_id
role = "roles/cloudscheduler.admin"
member = "serviceAccount:${data.google_project.project.number}@cloudbuild.gserviceaccount.com"
}
# Cloud Build: GitHubにプッシュされたときにR2のバックアップジョブ/スケジューラーをデプロイする
resource "google_cloudbuild_trigger" "deploy_backup_job" {
name = "deploy-backup-job"
description = "mainブランチへのプッシュ時にバックアップジョブをデプロイ"
# GitHubとの連携設定
github {
owner = "user_or_org_name"
name = "repo_name"
push {
branch = "^main$"
invert_regex = false
}
}
# 以下のいずれかのファイルに変更があった場合のみトリガーする
included_files = ["rclone/Dockerfile", "rclone/rclone.conf"]
substitutions = {
# さっき作ったサービスアカウントを参照
"_SERVICE_ACCOUNT" = "${google_service_account.backup_job_service_account.email}"
# ビルドされたイメージの格納先(Artifact Resistry上のURL)
"_ARTIFACT_RESISTRY_IMAGE_TAG" = "us-central1-docker.pkg.dev/${var.project_id}/backup-job-images/backup-job:${"$"}{SHORT_SHA}"
}
build {
timeout = "1200s"
# Dockerfileをビルド
step {
id = "build"
name = "gcr.io/cloud-builders/docker"
args = [
"build",
"--tag=$_ARTIFACT_RESISTRY_IMAGE_TAG",
"--file=Dockerfile",
"."
]
}
# Artifact Registryにイメージをプッシュ
step {
id = "push"
name = "gcr.io/cloud-builders/docker"
args = [
"push",
"$_ARTIFACT_RESISTRY_IMAGE_TAG",
]
}
# Cloud Run jobsにデプロイ
step {
id = "deploy-backup-job"
name = "gcr.io/google.com/cloudsdktool/cloud-sdk:slim"
entrypoint = "gcloud"
args = [
"run",
"jobs",
"deploy",
"backup-job",
"--region=us-central1",
"--service-account=$_SERVICE_ACCOUNT",
"--image=$_ARTIFACT_RESISTRY_IMAGE_TAG",
"--task-timeout=30m",
]
}
# Cloud Schedulerでジョブを定期実行する設定
step {
id = "deploy-rclone-backup-scheduler"
name = "gcr.io/google.com/cloudsdktool/cloud-sdk:slim"
entrypoint = "gcloud"
args = [
"scheduler",
"jobs",
# deployコマンドがないため、createコマンドで作成し、作成後はupdateコマンドしないとエラーになる
# (そもそもterraformではgcloudで呼び出さない方が良いかもしれない)
"create",
"http",
"backup-job-scheduler",
"--location=us-central1",
"--uri=https://us-central1-run.googleapis.com/apis/run.googleapis.com/v1/namespaces/$PROJECT_ID/jobs/rclone-backup-job:run",
"--http-method=POST",
# 1日に1回、日本時間の午前4時に実行する
"--schedule=0 4 * * *",
"--time-zone=Asia/Tokyo",
# Cloud Run jobsを実行するためのサービスアカウントの指定
"--oauth-service-account-email=${data.google_project.project.number}-compute@developer.gserviceaccount.com"
]
}
}
}
これで設定は完了です。コンソールからCloud Schedulerを開き、手動でジョブを実行する等してバックアップが正常にできていることを確認しましょう。
Discussion
お金かけてもいいなら、これどうなんですかね笑
(もちろんこの記事の方法で十分だと思うのですが)
コスト気にせず、運営元が信頼できそうならこういうものを使うのもアリですね!
この記事を元に、自分なりにterraformを使って自動バックアップを構築できました。
ありがとうございます。