S3の双方向クロスリージョンレプリケーション+S3 RTCを試す
Posted On 2025-02-12
S3のクロスリージョンレプリケーションにS3 Replication Time Controlを組み合わせることで、15分以内の非同期複製がほぼ保証される高速なレプリケーションを実現できる。Terraformを用いた設定例では、バージョニングやIAMロールなどの要点を押さえるだけで簡単に双方向のレプリケーションが構築可能だ。
目次
はじめに
- S3バケットを異なるリージョンにレプリケートできるクロスリージョンレプリケーション。
- 普通のレプリケーションだと一方向だが、双方向のレプリケーションにしてどちらのバケットにアップロードされてもレプリケートされるようにする
- レプリケーションは非同期処理でデフォルトだといつくるかはわからない。そこで15分以内に複製できるSLAが99.99% のレプリケーションS3 Replication Time Control(S3 RTC)を試してみる。日本語だと「レプリケーション時間制御」と訳されることもある。
- 簡単にいうと、爆速で双方向のバケットに複製できるハイスペックS3ということ
料金
- レプリケーション自体は通常のS3料金でできる。ストレージ料金が2倍かかるだけ
- ただクロスリージョンになったときに、リージョン間の通信料が発生する。東京からはus-east-1もそうだが、ほぼ全て0.09USD/GB(参考)
- S3 RTCを入れる場合は、追加でUSD 0.015/GBかかる(参考)
正直、S3標準の保管量がUSD 0.023/GBであるのに比べたら、リージョン間の転送量が一番大きい気がする。S3 RTCはそこまで大きくない。これぐらいのお金払ってもいいぐらいクリティカルなケースで有効だと思う。
参考
マネジメントコンソールでやった例はこちら
当然ながら、このブログではTerraformをGPTで書かせたいためTerraformで書く。
ポイント
- どちらもS3のバージョニングを有効化しておく
- レプリケーションの設定時に削除マーカーのレプリケーションを無効化する(これTerraformだと初期ではONになってる)
- 双方向レプリケーションを実現するには、レプリケーションをsource→dest、dest→sourceの2つ張ればいい。再帰ループは起こらないようにAWS側でよしなにしてくれる。
- IAMポリシー周りがちょっと複雑になるが、やってることはそこまでむずくない(下記参照)
data "aws_iam_policy_document" "s3_replication_policy_source" {
# バケット1(ソース)からオブジェクト取得用の権限
statement {
actions = [
"s3:GetObjectVersionForReplication",
"s3:GetObjectVersionAcl",
"s3:GetObjectVersionTagging",
"s3:ListBucket",
"s3:ListBucketVersions"
]
resources = [
aws_s3_bucket.source_bucket.arn,
"${aws_s3_bucket.source_bucket.arn}/*"
]
}
# バケット2(ターゲット)へ書き込み用の権限
statement {
actions = [
"s3:ReplicateObject",
"s3:ReplicateDelete",
"s3:ReplicateTags",
"s3:PutObject"
]
resources = [
aws_s3_bucket.destination_bucket.arn,
"${aws_s3_bucket.destination_bucket.arn}/*"
]
}
}
試してみた
末尾のTerraformを実行してみる。
- 東京リージョンのバケット:
example-source-bucket-85139852
- バージニアリージョンのバケット:
example-destination-bucket-85139852
東京リージョンのバケットにdummy_file_1.txt
をアップロード
同様に、バージニアリージョンのバケットにdummy_file_2.txt
をアップロード
1~2分すると、バージニアリージョンのバケットに東京でアップロードしたファイルがくる。更新日時のメタデータはアップロード時(東京でアップロードした時刻)をそのまま持ってくるようだ。
同じタイミングで、東京にもバージニアでアップロードしたファイルがきている。
15分以内のSLAというのは本当なようだ。
Terraformのコード
# デフォルトプロバイダー: ap-northeast-1(バケット1用)
provider "aws" {
region = "ap-northeast-1"
}
# 追加プロバイダー: us-east-1(バケット2用)
provider "aws" {
alias = "us"
region = "us-east-1"
}
variable "s3_source_bucket_name" {
type = string
description = "レプリケーション元のバケット1の名前"
}
variable "s3_destination_bucket_name" {
type = string
description = "レプリケーション先のバケット2の名前"
}
#############################
# 1. S3 バケット作成 & バージョニング有効化
#############################
# バケット1: ap-northeast-1 (Tokyo)
resource "aws_s3_bucket" "source_bucket" {
provider = aws
bucket = var.s3_source_bucket_name # ユニークな名前に変更してください
tags = {
Name = "example-source-bucket"
}
}
# バケット2: us-east-1
resource "aws_s3_bucket" "destination_bucket" {
provider = aws.us
bucket = var.s3_destination_bucket_name # ユニークな名前に変更してください
tags = {
Name = "example-destination-bucket"
}
}
# バケット1 のバージョニング有効化
resource "aws_s3_bucket_versioning" "source_versioning" {
provider = aws
bucket = aws_s3_bucket.source_bucket.id
versioning_configuration {
status = "Enabled"
}
}
# バケット2 のバージョニング有効化
resource "aws_s3_bucket_versioning" "destination_versioning" {
provider = aws.us
bucket = aws_s3_bucket.destination_bucket.id
versioning_configuration {
status = "Enabled"
}
}
#############################
# 2. IAM ロール・ポリシーの作成(レプリケーション用)
#############################
# 2-1. バケット1(ap-northeast-1)→ バケット2(us-east-1)用 IAM ロール
data "aws_iam_policy_document" "s3_replication_assume_role_source" {
statement {
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = ["s3.amazonaws.com"]
}
}
}
resource "aws_iam_role" "replication_role_source" {
name = "replication-role-source"
assume_role_policy = data.aws_iam_policy_document.s3_replication_assume_role_source.json
}
data "aws_iam_policy_document" "s3_replication_policy_source" {
# バケット1(ソース)からオブジェクト取得用の権限
statement {
actions = [
"s3:GetObjectVersionForReplication",
"s3:GetObjectVersionAcl",
"s3:GetObjectVersionTagging",
"s3:ListBucket",
"s3:ListBucketVersions"
]
resources = [
aws_s3_bucket.source_bucket.arn,
"${aws_s3_bucket.source_bucket.arn}/*"
]
}
# バケット2(ターゲット)へ書き込み用の権限
statement {
actions = [
"s3:ReplicateObject",
"s3:ReplicateDelete",
"s3:ReplicateTags",
"s3:PutObject"
]
resources = [
aws_s3_bucket.destination_bucket.arn,
"${aws_s3_bucket.destination_bucket.arn}/*"
]
}
}
resource "aws_iam_role_policy" "replication_role_source_policy" {
name = "replication-role-source-policy"
role = aws_iam_role.replication_role_source.id
policy = data.aws_iam_policy_document.s3_replication_policy_source.json
}
# 2-2. バケット2(us-east-1)→ バケット1(ap-northeast-1)用 IAM ロール
data "aws_iam_policy_document" "s3_replication_assume_role_destination" {
statement {
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = ["s3.amazonaws.com"]
}
}
}
resource "aws_iam_role" "replication_role_destination" {
name = "replication-role-destination"
assume_role_policy = data.aws_iam_policy_document.s3_replication_assume_role_destination.json
}
data "aws_iam_policy_document" "s3_replication_policy_destination" {
# バケット2(ソース)からオブジェクト取得用の権限
statement {
actions = [
"s3:GetObjectVersionForReplication",
"s3:GetObjectVersionAcl",
"s3:GetObjectVersionTagging",
"s3:ListBucket",
"s3:ListBucketVersions"
]
resources = [
aws_s3_bucket.destination_bucket.arn,
"${aws_s3_bucket.destination_bucket.arn}/*"
]
}
# バケット1(ターゲット)へ書き込み用の権限
statement {
actions = [
"s3:ReplicateObject",
"s3:ReplicateDelete",
"s3:ReplicateTags",
"s3:PutObject"
]
resources = [
aws_s3_bucket.source_bucket.arn,
"${aws_s3_bucket.source_bucket.arn}/*"
]
}
}
resource "aws_iam_role_policy" "replication_role_destination_policy" {
name = "replication-role-destination-policy"
role = aws_iam_role.replication_role_destination.id
policy = data.aws_iam_policy_document.s3_replication_policy_destination.json
}
#############################
# 3. S3 レプリケーション設定(RTC 併用)
#############################
# 3-1. バケット1(ap-northeast-1)からバケット2(us-east-1)へのレプリケーション設定
resource "aws_s3_bucket_replication_configuration" "source_to_destination" {
provider = aws
bucket = aws_s3_bucket.source_bucket.id
role = aws_iam_role.replication_role_source.arn
rule {
id = "source-to-destination"
status = "Enabled"
filter {
prefix = "" # バケット内の全オブジェクトが対象
}
destination {
bucket = aws_s3_bucket.destination_bucket.arn
storage_class = "STANDARD"
replication_time {
status = "Enabled"
time {
minutes = 15
}
}
metrics {
status = "Enabled"
event_threshold {
minutes = 15
}
}
}
delete_marker_replication {
status = "Disabled"
}
}
depends_on = [
aws_s3_bucket_versioning.source_versioning,
aws_s3_bucket_versioning.destination_versioning
]
}
# 3-2. バケット2(us-east-1)からバケット1(ap-northeast-1)へのレプリケーション設定
resource "aws_s3_bucket_replication_configuration" "destination_to_source" {
provider = aws.us
bucket = aws_s3_bucket.destination_bucket.id
role = aws_iam_role.replication_role_destination.arn
rule {
id = "destination-to-source"
status = "Enabled"
filter {
prefix = ""
}
destination {
bucket = aws_s3_bucket.source_bucket.arn
storage_class = "STANDARD"
replication_time {
status = "Enabled"
time {
minutes = 15
}
}
metrics {
status = "Enabled"
event_threshold {
minutes = 15
}
}
}
delete_marker_replication {
status = "Disabled"
}
}
depends_on = [
aws_s3_bucket_versioning.source_versioning,
aws_s3_bucket_versioning.destination_versioning
]
}
所感
- これはなかなか便利でわかりやすい
- 同一リージョンのレプリケーション(特にアカウントまたいだ場合)はよく使いそうな気がする。S3 RTCを使ったとしても時間課金がないのが良い
Shikoan's ML Blogの中の人が運営しているサークル「じゅ~しぃ~すくりぷと」の本のご案内
技術書コーナー
北海道の駅巡りコーナー