こしあん
2025-02-12

S3の双方向クロスリージョンレプリケーション+S3 RTCを試す


29{icon} {views}

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はそこまで大きくない。これぐらいのお金払ってもいいぐらいクリティカルなケースで有効だと思う。

参考

マネジメントコンソールでやった例はこちら

S3の双方向レプリケーションを試してみた

当然ながら、このブログでは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の中の人が運営しているサークル「じゅ~しぃ~すくりぷと」の本のご案内

技術書コーナー

北海道の駅巡りコーナー


Add a Comment

メールアドレスが公開されることはありません。 が付いている欄は必須項目です