S3 クロスアカウントレプリケーション ・ 異常時の動作を試してみた

# はじめに
異なるアカウント間で、ファイル転送によるデータ連携ではなく、S3バケット間のレプリケーションを考える機会がありました。
単純にレプリケーションするだけなら問題なくできることを経験していたのですが、レプリケーションの失敗など異常時の動作がどうなるのだろうと思い、試してみることにしました。

# S3クロスアカウントレプリケーションとは
アカウントAのバケットに保存したファイルがアカウントBのバケットにレプリケーションできる機能です。
以下のようなイメージです。
![S3_001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925746/27c4510e-dacd-9a93-556c-9daa9fb2aabf.png)

https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/userguide/replication-walkthrough-2.html

どれくらいの遅延でレプリケーションできるのか？という点についてもマニュアルに記載がありました。
> S3 Replication Time Control (S3 RTC) では、データレプリケーションに関するコンプライアンス要件 (またはビジネス要件) への対応をサポートします。また、Amazon S3 レプリケーション時間を可視化します。S3 RTC は、Amazon S3 にアップロードしたほとんどのオブジェクトを数秒でレプリケートし、これらのオブジェクトの 99.99% を 15 分以内にレプリケートします。

十分に実用的ですね。

https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/userguide/replication-time-control.html

# 使ってみた
### レプリケーション元のアカウントA　バケットを作成
まずはレプリケーション元のアカウントAでバケットを作成します。
S3のレプリケーションを行うために、バケットのバージョニングを有効にする必要があるので、ご注意ください。
※デフォルトでは無効です。
![S3_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925746/a30c6355-2b05-d09b-540e-17abc54812aa.png)
![S3_02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925746/42e25005-4931-d55e-f4f4-cdb27a499d7d.png)

その他の設定はデフォルトで「バケットを作成」をクリックします。

### レプリケーション先のアカウントB　バケットを作成
続いてレプリケーション先のアカウントBでバケットを作成します。
レプリケーション元と同様にバケットのバージョニングを有効にすることを忘れないでください。
※以降の画面でもアカウントBの画面はダークモードでキャプチャしています

![S3_03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925746/e1cb17f6-26ae-69b9-ad95-e92421c66695.png)
![S3_04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925746/9db8fb05-acba-35d9-3b84-3ce11481f184.png)

### レプリケーション元のアカウントA　レプリケーション設定
次にレプリケーション元のアカウントAでレプリケーションの設定を行います。
「管理」タブにて「レプリケーションルールを作成」をクリック。
![S3_05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925746/762da9bc-3c7a-1c74-ca95-c1bbb5aa2761.png)

以下の設定を行います。
　・レプリケーションルールの設定
　　レプリケーションルール名：replication-test
  ・ソースバケット
　　ルールスコープを選択：バケット内のすべてのオブジェクトに適用
![S3_06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925746/03245d66-377c-120f-d9ea-e7be5b475faf.png)

　・送信先
 　　送信先：別のアカウントのバケットを指定する
 　　アカウントID：レプリケーション先のアカウントBのアカウントIDを設定
 　　バケット名：destination-bucket-202411
　・オブジェクト所有者を送信先バケット所有者に変更：チェックを入れる
　・IAMロール：新しいロールの作成　を選択
![S3_07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925746/372440c3-0476-724a-176c-6d96f33c093f.png)
 
 ・追加のレプリケーションオプション
 　レプリケーション時間のコントロール（RTC）：チェックをいれる
　その他はデフォルト設定で、最後に「保存」をクリック。
![S3_08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925746/9795e15f-a594-e575-55c8-2867aa9f3157.png)

 ルールを保存する際に、既存オブジェクトをレプリケートするか確認がありました。
 今回は、作ったばかりのバケットで空になっていますので、「いいえ、既存オブジェクトをレプリケートしません。」を選択しました。
![S3_09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925746/5bb4cd9c-8e70-1e2a-eb6a-97e20adf10e6.png)

レプリケーションルールが作成されました。
レプリケーションルールの構成設定欄に作成されたIAMロールのリンクがありますので、開いて確認します。
![S3_10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925746/56af328c-8af5-c662-f234-d24ab84083aa.png)

次のステップで使用しますので、IAMロールのARNをコピーしておきます。
![S3_12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925746/866e5428-0a59-c2b1-f753-cf0c3ec1025c.png)

ちなみに、自動で作成されたIAMロールの中身は以下となっていました。

```
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "s3:ListBucket",
                "s3:GetReplicationConfiguration",
                "s3:GetObjectVersionForReplication",
                "s3:GetObjectVersionAcl",
                "s3:GetObjectVersionTagging",
                "s3:GetObjectRetention",
                "s3:GetObjectLegalHold"
            ],
            "Effect": "Allow",
            "Resource": [
                "arn:aws:s3:::source-bucket-202411",
                "arn:aws:s3:::source-bucket-202411/*",
                "arn:aws:s3:::destination-bucket-202411",
                "arn:aws:s3:::destination-bucket-202411/*"
            ]
        },
        {
            "Action": [
                "s3:ReplicateObject",
                "s3:ReplicateDelete",
                "s3:ReplicateTags",
                "s3:ObjectOwnerOverrideToBucketOwner"
            ],
            "Effect": "Allow",
            "Resource": [
                "arn:aws:s3:::source-bucket-202411/*",
                "arn:aws:s3:::destination-bucket-202411/*"
            ]
        }
    ]
}
```

### レプリケーション先のアカウントB　バケットポリシーを設定
アカウントAからレプリケーション先のアカウントBのバケットにアクセスできるように、アカウントB側でバケットポリシー（先ほど作られたIAMロールによるアクセスを許可）を設定していきます。
バケットポリシーの「編集」をクリックします。
![S3_13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925746/9df45c89-a561-2335-a7c1-a97bf838bcb5.png)

ポリシーに設定を入れて、「変更の保存」をクリックして保存します。
![S3_14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925746/bdc30597-7c74-b42a-b0ca-228e5321e907.png)

設定したポリシーは以下の通りです。
```
{
	"Version": "2012-10-17",
	"Statement": [
		{
			"Sid": "1",
			"Effect": "Allow",
			"Principal": {
				"AWS": "arn:aws:iam::<アカウントAのアカウントID>:role/service-role/s3crr_role_for_source-bucket-202411"
			},
			"Action": [
				"s3:ReplicateDelete",
				"s3:ReplicateObject",
				"s3:ReplicateTags",
				"s3:ObjectOwnerOverrideToBucketOwner"
			],
			"Resource": "arn:aws:s3:::destination-bucket-202411/*"
		}
	]
}
```

試しにアカウントAのバケットにファイルを配置してみます。
![S3_15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925746/a7f1b0ce-d64f-177b-0d0b-48ffcbf7efb4.png)

しっかりアカウントBのバケットにファイルがレプリケーションされました。
![S3_16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925746/9ec85d30-7f9e-d030-810d-d10f9d524bc1.png)


### レプリケーション先のアカウントBでレプリケーションを検知する
異常系の動作の確認の前に少し寄り道をします。
レプリケーション先のアカウントではレプリケーションでファイルが作成されたことを契機として、後続の処理を動かすといったことがあると思います。
そこで、レプリケーション先のアカウントBでS3のイベント検知を実施し、一番簡単な後続処理であるSNSでメール通知が行えるか確認しておきます。

イメージとしては以下のような形です。
![S3_002.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925746/e24c4ac6-4e6d-3872-82ce-578ca76a8092.png)


アカウントBのSNSでトピックを作成します。
![S3_18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925746/ac6f50b7-407d-de04-c656-1c953e908197.png)

![S3_17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925746/a8d20097-0419-9f81-0fad-1fd8097a76a2.png)

トピックのアクセスポリシーを開いて、メソッドの選択で「アドバンスト」を選択します。
![S3_19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925746/5467c658-9832-f884-6bf0-301ad06000d7.png)

以下の内容に修正して、S3からのPublishを許可しておきます。
「トピックの作成」をクリックして、トピックを作成します。
```
{
  "Version": "2008-10-17",
  "Id": "__default_policy_ID",
  "Statement": [
    {
      "Sid": "__default_statement_ID",
      "Effect": "Allow",
      "Principal": {
        "AWS": "*"
      },
      "Action": [
        "SNS:GetTopicAttributes",
        "SNS:SetTopicAttributes",
        "SNS:AddPermission",
        "SNS:RemovePermission",
        "SNS:DeleteTopic",
        "SNS:Subscribe",
        "SNS:ListSubscriptionsByTopic",
        "SNS:Publish"
      ],
      "Resource": "arn:aws:sns:ap-northeast-1:<アカウントBのアカウントID>:test-topic2",
      "Condition": {
        "StringEquals": {
          "AWS:SourceOwner": "<アカウントBのアカウントID>"
        }
      }
    },
    {
      "Sid": "additional_statement_ID",
      "Effect": "Allow",
      "Principal": {
        "Service": "s3.amazonaws.com"  
      },
      "Action": [
        "SNS:Publish"
      ],
      "Resource": "arn:aws:sns:ap-northeast-1:<アカウントBのアカウントID>:test-topic2",
      "Condition": {
        "ArnLike": { 
          "aws:SourceArn": "arn:aws:s3:::destination-bucket-202411"
        }
      }
    }
  ]
}
```

サブスクリプションも作成していきます。
「サブスクリプションの作成」をクリックします。
![S3_20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925746/31cae3ce-fa26-c76c-ffad-c697c36b99eb.png)

プロトコルは「Eメール」を選択し、エンドポイントに通知したいメールアドレスを設定します。
「サブスクリプションの作成」をクリックして、サブスクリプションを保存します。
![S3_21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925746/17e1448a-7384-d1ee-0c5d-df691f72eeaa.png)

エンドポイントに入力したメールアドレスへメールが届きます。
届いたメールで「Confirm subscription」をクリックしておきます。

続いて、レプリケーション先のアカウントBのS3バケットでイベント通知の設定を行います。
レプリケーション先バケットの「プロパティ」タブを選択します。
![S3_22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925746/c755e170-0c81-3d68-39fb-0f189e8b14e3.png)

「イベント通知を作成」をクリックします。
![S3_23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925746/0394a0e0-93dc-2650-7fef-f4b601652682.png)

イベント名を入力します。
![S3_24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925746/90ea1acb-2dff-5bcd-f138-b6a6fea2b216.png)

イベント通知のタイプについては、今回はシンプルに「すべてのオブジェクト作成イベント」を選択しました。
![S3_25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925746/c6a4a3aa-cec3-7993-12ce-46156b311a93.png)

送信先は「SNSトピック」を選択します。
SNSトピックを特定の項目については「SNSトピックから選択する」を選択します。
最後に先ほど作成したトピックを選択して、「変更の保存」をクリックします。
![S3_26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925746/3d1322eb-5aa3-e82a-451c-094e7e85f696.png)

こうして、ファイルがアップロードされた時に起動するイベント通知ができました。
![S3_27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925746/83e61f95-6cfa-b944-9e44-edd10cc05cda.png)

早速、レプリケーション元のアカウントAのS3バケットにファイルを配置してみます。
![S3_28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925746/82ba3098-d061-92af-3789-1d0618fa0e5f.png)
![S3_29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925746/3e86980d-ab58-4f73-40c0-ff4fc0c832e5.png)

レプリケーション先のアカウントBのバケットにもファイルがレプリケーションされており、無事にレプリケーション結果の通知も受けることができました。
![S3_30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925746/9c6a25c9-a85a-fc38-47f7-dea5d489f0f3.png)


# 異常時の動作について
正常系の稼働が確認できましたので、いよいよ異常系の動作確認に入っていきます。

異常系の動作として気になったのは以下の3点です。
１．レプリケーション元のアカウントAでレプリケーションが失敗したことを検知できるか
２．レプリケーションが失敗した後に、次の別ファイルがアップロードされたらどうなるか
３．レプリケーションの自動リトライが行われるか
オンプレ環境のファイル転送だと自動リトライ処理や手動再送を実装しているかと思います。AWS環境だとそのあたりが楽になっているといいなと思い、試してみることにしました。

### １．レプリケーション元のアカウントAでレプリケーションが失敗したことを検知できるか
検知を簡単に行うため、SNSで通知を受けられるようにします。
以下のようなイメージです。
![S3_003.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925746/43ed5565-9cc9-dc17-2e08-26ed058fa8ac.png)


レプリケーション元のアカウントAのSNS通知を受け取るため、先ほどのアカウントBと同様にアカウントAでもSNSトピック、サブスクリプションを作成しておきます。

続いてアカウントAのS3バケットでイベント通知の設定を行います。
レプリケーション元バケットのプロパティタブを選択し、「イベント通知を作成」をクリックします。
![S3_32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925746/bfd634e0-a5c8-eac2-5a53-5dbe9c2dfe63.png)

イベント名を設定します。
![S3_31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925746/ebef4ddb-01e0-db03-f9a7-a4d325df3aa7.png)

今回は、イベントタイプで「オブジェクトのレプリケートに失敗しました」にチェックを入れます。
![S3_33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925746/ee564be3-ea45-7c63-9a1c-2e8157d785dd.png)

送信先は「SNSトピック」を選択します。
SNSトピックを特定の項目については「SNSトピックから選択する」を選択します。
最後にアカウントBで作成したトピックを選択して、「変更の保存」をクリックします。
![S3_34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925746/8fe4c145-401c-1b4c-7fcc-0dc4322edf87.png)

イベント通知ができました。
![S3_35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925746/3e7ec81a-d88a-565e-fe64-9a1d858c5d79.png)

準備が整ったので、いよいよレプリケーションを失敗させます。
レプリケーション先のアカウントBのS3バケットでバケットポリシーを一時的に削除します。
![S3_36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925746/b1b4ea89-63bb-022a-4237-1b5e3112beb0.png)

![S3_37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925746/d80cc4f3-c6cd-a44a-3b9d-e685c96e89f3.png)
![S3_38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925746/2df5498c-9bbe-c8df-07f3-aa2301d5957a.png)


バケットポリシーが削除されたことで、アカウントAからアカウントBへのレプリケーションはできなくなりました。
この状態で、アカウントAのバケットにファイルをアップします。
![S3_39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925746/ce4bc352-3b5a-407a-06a3-835009124a87.png)

アカウントBのバケットにはファイルがレプリケーションされません。
![S3_41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925746/f4b5237c-a975-b3af-3e07-0cd977dc936f.png)


また、アカウントA側でレプリケーション失敗について、以下のような内容のSNS通知を受けることができました。
```
{"Records":[{"eventVersion":"2.2","eventSource":"aws:s3","awsRegion":"ap-northeast-1","eventTime":"2024-11-16T12:56:16.172Z","eventName":"Replication:OperationFailedReplication","userIdentity":{"principalId":"s3.amazonaws.com"},"requestParameters":{"sourceIPAddress":"s3.amazonaws.com"},"responseElements":{〜},"s3":{"s3SchemaVersion":"1.0","configurationId":"S3-error-event","bucket":{"name":"source-bucket-202411","ownerIdentity":{"principalId":"※***********"},"arn":"arn:aws:s3:::source-bucket-202411"},"object":{"key":"replication-error.pdf","size":12179011,〜}},"replicationEventData":{"replicationRuleId":"replication-test","destinationBucket":"arn:aws:s3:::destination-bucket-202411","s3Operation":"OBJECT_PUT","requestTime":"2024-11-16T12:55:17.749Z","failureReason":"DstPutObjectNotPermitted"}}]}
```

結論、レプリケーションの失敗を検知して次のアクションを起こすことはできることを確認しました。


### ２．レプリケーションが失敗した後に、次の別ファイルがアップロードされたらどうなる？
続いて、レプリケーションが失敗した上記の状態からレプリケーション先のバケットのアクセスポリシーを再度付与して復旧させます。
![S3_40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925746/3cb08d29-0145-d213-36a3-e5037c0c089c.png)
失敗したファイル```replication-error.pdf```がある状態で、同じバケットに新しいファイル```replication-error2.pdf```がアップロードされたらどうなるでしょうか。

レプリケーションに失敗したファイルがある状態で、アカウントAのバケットに、新しいファイル```replication-error2.pdf```をアップロードしてみます。
![S3_46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925746/296b066a-9e30-1332-7e74-f28afe21a294.png)

結果、追加でアップロードされた```replication-error2.pdf```だけがレプリケーションされました。
権限不足でレプリケーションに失敗した```replication-error.pdf```はレプリケーションされないままでした。
ファイルごとのステータスをちゃんと見て、レプリケーションの判断をしているということですね。
![S3_47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925746/323ae100-2bf8-1198-6b63-52f7b24bc272.png)


### ３．レプリケーションの自動リトライが行われるか
さて、レプリケーションが失敗したファイル```replication-error.pdf```は、バケットポリシーで権限が付与された後、どうなったでしょうか。

15分以上待ちましたが、アカウントBのバケットに```replication-error.pdf```はレプリケーションされませんでした。
![S3_48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925746/3ca44871-7e57-fc80-ea12-406cce75131e.png)


マニュアルを読み進めてみたところ、以下の記載がありました。

https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/userguide/replication-status.html#replication-status-overview

>オブジェクトをアップロードした後で、オブジェクトのレプリケーションに失敗した場合、レプリケーションを再試行できません。オブジェクトを再度アップロードするか、S3 バッチレプリケーションを使用して失敗したオブジェクトをレプリケートする必要があります。

さらに、以下の記載もありました。

>レプリケーションロールの許可、AWS Key Management Service (AWS KMS) 許可、またはバケットの許可がないなどの問題がある場合、オブジェクトは FAILED ステータスに移行します。バケットやリージョンが使用できないなどの一時的な障害が発生した場合、レプリケーションのステータスは FAILED にはならず、PENDING のままになります。リソースがオンラインに戻ると、Amazon S3 はこれらのオブジェクトのレプリケーションを再開します。

何らかの障害で失敗した場合は```PENDING```ステータスになって、自動でレプリケーションが再開されるようですので、再レプリケーションは基本的に実行されそうですね。
一方で、ステータスを意識してリトライやアラートをあげる等、もう少し考えるところはありそうです。

ちなみに、レプリケーションに失敗したファイル```replication-error.pdf```のステータスを確認してみました。
権限不足でレプリケーションに失敗した```replication-error.pdf```は、たしかにレプリケーションステータスが、```FAILED```になっていました。
![S3_42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925746/b2bd5d94-175b-38e3-ff96-bb1f21816b74.png)

マニュアル通りですね。（最初から読めばよかった。。）

再度手動でファイルをアップロードしました。
![S3_43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925746/d899bd20-d6a5-72ca-a7a3-48175cd108db.png)
![S3_44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925746/1c778236-9b38-f901-d5eb-407a98eaa427.png)

最終的に、アカウントBへファイルがレプリケーションされました。
![S3_45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925746/125df83d-1f5d-955c-2835-f98eb80a57ba.png)

# まとめ
S3のクロスアカウントレプリケーションは、アカウント間において非同期でファイルを連携する処理に十分使えることが分かりました。
レプリケーション後の処理も非同期で実装でき、レプリケーション先のアカウントでイベント検知もできますので、SQSやEventBridge等を連携して後続処理が実装できますので、非常に便利だと思います。
レプリケーション失敗時の動作などを意識しながら活用していきたいと思います。

以上、今回はS3 クロスアカウントレプリケーション をお試ししてみました。
どなたかの参考になれば幸いです。