【AWS】S3のクロスアカウント＆クロスリージョンレプリケーションをすべてCLIで試してみた

S3のレプリケーション機能は元々知ってはいたものの、なかなか自分で実装する機会がありませんでした。
そこでAWSの公式サイトにチュートリアルを見つけたので試してみました。
公式では一部でGUIを使う手順ですが、せっかくなので全部CLIでやってみました。

[例 1: レプリケート元バケットとレプリケート先バケットが同じアカウントによって所有されている場合のレプリケーションを設定する]
(https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/dev/replication-walkthrough1.html)

#前提
レプリケーション元と先、それぞれのIAMユーザをCLIに登録する必要があります。
今回はレプリケーション元アカウントの方はprofile指定せずにデフォルトのもので登録しています。

##レプリケーション元バケット

まず、レプリケーション元のバケットを東京リージョンに作ります。

```ruby:レプリケーション元バケット
$ aws s3 mb s3://{source-bucket-name} --region ap-northeast-1
make_bucket: {source-bucket-name}
```

一応存在確認と、テストファイルのアップロードくらい試しておきます。

```ruby:テスト
$ aws s3 cp ./test.txt s3://{source-bucket-name}/test/
upload: ./test.txt to s3://{source-bucket-name}/test/test.txt

$ aws s3 ls s3://{source-bucket-name}/test/
2020-12-25 18:23:42          0 test.txt
```

続いて、バージョニングの有効化。
レプリケーションには必須要件となります。

```ruby:バージョニング
$ aws s3api put-bucket-versioning \
>   --bucket {source-bucket-name} \
>   --versioning-configuration Status=Enabled
$ 

$ aws s3api get-bucket-versioning --bucket {source-bucket-name}
{
    "Status": "Enabled"
}
$ 
```

##レプリケーション先バケット

続いてdestとなるバケットも同様に作成します。
こちらはオレゴンリージョンに作成します。

```ruby:レプリケーション先バケット
$ aws s3 mb s3://{dest-bucket-name} --region us-west-2 --profile {profile_name}

$ aws s3api put-bucket-versioning \
>   --bucket {dest-bucket-name} \
>   --versioning-configuration Status=Enabled \
>   --profile {profile_name}

$ 
$ 
$ 
$ aws s3api get-bucket-versioning --bucket {dest-bucket-name} --profile {profile_name}
{
    "Status": "Enabled"
}
$ 
```

##IAM関係

まずIAMロールの作成。

```ruby:IAMロール
$ vi ./S3-role-trust-policy.json
$ 
$ cat ./S3-role-trust-policy.json 
{
   "Version":"2012-10-17",
   "Statement":[
      {
         "Effect":"Allow",
         "Principal":{
            "Service":"s3.amazonaws.com"
         },
         "Action":"sts:AssumeRole"
      }
   ]
}
$ 

$   aws iam create-role \
>   --role-name replicationRole \
>   --assume-role-policy-document file://s3-role-trust-policy.json 
{
    "Role": {
        "Path": "/",
        "RoleName": "replicationRole",
        "RoleId": "AROAUWAUORV4ISXXUMSMG",
        "Arn": "arn:aws:iam::0123456789012:role/replicationRole",
        "CreateDate": "2020-12-25T09:36:43+00:00",
        "AssumeRolePolicyDocument": {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Effect": "Allow",
                    "Principal": {
                        "Service": "s3.amazonaws.com"
                    },
                    "Action": "sts:AssumeRole"
                }
            ]
        }
    }
}
$ 
```

続いて、IAMポリシーの作成。

```ruby:IAMポリシー
$ vi ./S3-role-permissions-policy.json
$ 
$ cat ./S3-role-permissions-policy.json 
{
   "Version":"2012-10-17",
   "Statement":[
      {
         "Effect":"Allow",
         "Action":[
            "s3:GetObjectVersionForReplication",
            "s3:GetObjectVersionAcl"
         ],
         "Resource":[
            "arn:aws:s3:::{source-bucket-name}/*"
         ]
      },
      {
         "Effect":"Allow",
         "Action":[
            "s3:ListBucket",
            "s3:GetReplicationConfiguration"
         ],
         "Resource":[
            "arn:aws:s3:::{source-bucket-name}"
         ]
      },
      {
         "Effect":"Allow",
         "Action":[
            "s3:ReplicateObject",
            "s3:ReplicateDelete",
            "s3:ReplicateTags",
            "s3:GetObjectVersionTagging"

         ],
         "Resource":"arn:aws:s3:::{dest-bucket-name}/*"
      }
   ]
}

$ 
$ aws iam put-role-policy \
> --role-name replicationRole \
> --policy-document file://s3-role-permissions-policy.json \
> --policy-name replicationRolePolicy
$ 
```

put-role-policyは返り値無いので、一応きちんと当たってるか確かめましょう。

```ruby:ポリシーのアタッチチェック
$ aws iam list-role-policies \
> --role-name replicationRole 
{
    "PolicyNames": [
        "replicationRolePolicy"
    ]
}
$ 
```

よさげ。

##レプリケーション元バケット設定

レプリケーション元バケットにレプリケーション設定を入れます。

```ruby:レプリケーション設定
$ vi ./replication.json
$ 
$ cat ./replication.json 
{
  "Role": "replicationRole",
  "Rules": [
    {
      "Status": "Enabled",
      "Priority": 1,
      "DeleteMarkerReplication": { "Status": "Disabled" },
      "Filter" : { "Prefix": "Tax"},
      "Destination": {
        "Bucket": "arn:aws:s3:::{dest-bucket-name}"
      }
    }
  ]
}
$ 
$ aws s3api put-bucket-replication \
> --replication-configuration file://replication.json \
> --bucket {source-bucket-name}

An error occurred (InvalidArgument) when calling the PutBucketReplication operation: Invalid Role specified in replication configuration
```

あれ、エラー…
公式ドキュメントを見直してみると、Role部分はARNって書いてありますね。
ARNを確認します。

```ruby:ロールARN
$ aws iam get-role --role-name replicationRole
{
    "Role": {
        "Path": "/",
        "RoleName": "replicationRole",
        "RoleId": "AROAUWAUORV4ISXXUMSMG",
        "Arn": "arn:aws:iam::0123456789012:role/replicationRole",
        "CreateDate": "2020-12-25T09:36:43+00:00",
        "AssumeRolePolicyDocument": {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Effect": "Allow",
                    "Principal": {
                        "Service": "s3.amazonaws.com"
                    },
                    "Action": "sts:AssumeRole"
                }
            ]
        },
        "MaxSessionDuration": 3600,
        "RoleLastUsed": {}
    }
}
```

jsonファイルを修正して、再度実行。

```ruby:
$ vi ./replication.json 
$ 
$ cat  ./replication.json 
{
  "Role": "arn:aws:iam::0123456789012:role/replicationRole",
  "Rules": [
    {
      "Status": "Enabled",
      "Priority": 1,
      "DeleteMarkerReplication": { "Status": "Disabled" },
      "Filter" : { "Prefix": "Tax"},
      "Destination": {
        "Bucket": "arn:aws:s3:::{dest-bucket-name}"
      }
    }
  ]
}
$ 
$ aws s3api put-bucket-replication \
> --replication-configuration file://replication.json \
> --bucket {source-bucket-name}
$ 
$ aws s3api get-bucket-replication \
> --bucket {source-bucket-name}
{
    "ReplicationConfiguration": {
        "Role": "arn:aws:iam::0123456789012:role/replicationRole",
        "Rules": [
            {
                "ID": "NTI5NzA3NWItZjdmMy00ZTgzLTlkMDgtODI2NWE1ZTVlYmUy",
                "Priority": 1,
                "Filter": {
                    "Prefix": "Tax"
                },
                "Status": "Enabled",
                "Destination": {
                    "Bucket": "arn:aws:s3:::{dest-bucket-name}"
                },
                "DeleteMarkerReplication": {
                    "Status": "Disabled"
                }
            }
        ]
    }
}
$ 
```

うまく行きました。

##レプリケーション先バケットポリシー

ここからは以下チュートリアルの内容です。
GUIを使う手順になっていますがせっかくなのでCLIでやってみます。

[例 2: レプリケート元バケットとレプリケート先バケットが異なるアカウントによって所有されている場合のレプリケーションを設定する]
(https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/dev/replication-walkthrough-2.html)

最初はデフォルトなのでバケットポリシーが設定されていない状態。

```ruby:
$ aws s3api get-bucket-policy \
>   --bucket {dest-bucket-name} \
>   --profile {profile_name}

An error occurred (NoSuchBucketPolicy) when calling the GetBucketPolicy operation: The bucket policy does not exist
$ 
```

ローカルにjsonを書いて、バケットポリシーを設定します。

```ruby:ロールARN
$ vi ./dest_bucket_policy.json
$ 
$ cat ./dest_bucket_policy.json
{
   "Version":"2008-10-17",
   "Id":"",
   "Statement":[
      {
         "Sid":"1",
         "Effect":"Allow",
         "Principal":{
            "AWS":"arn:aws:iam::123456789012:role/replicationRole"
         },
         "Action":["s3:ReplicateObject", "s3:ReplicateDelete"],
         "Resource":"arn:aws:s3:::{dest-bucket-name}/*"
      },
      {
         "Sid":"2",
         "Effect":"Allow",
         "Principal":{
            "AWS":"arn:aws:iam::123456789012:role/replicationRole"
         },
         "Action":["s3:GetBucketVersioning", "s3:PutBucketVersioning"],
         "Resource":"arn:aws:s3:::{dest-bucket-name}"
      }
   ]
}
$ 

$ aws s3api put-bucket-policy \
> --bucket {dest-bucket-name} \
> --policy file://dest_bucket_policy.json \
> --profile {profile_name}
$ 
$ 
$ aws s3api get-bucket-policy \
>    --bucket {dest-bucket-name} \
>    --profile {profile_name}
{
    "Policy": "{\"Version\":\"2008-10-17\",\"Id\":\"\",\"Statement\":[{\"Sid\":\"1\",\"Effect\":\"Allow\",\"Principal\":{\"AWS\":\"arn:aws:iam::123456789012:role/replicationRole\"},\"Action\":[\"s3:ReplicateObject\",\"s3:ReplicateDelete\"],\"Resource\":\"arn:aws:s3:::{dest-bucket-name}/*\"},{\"Sid\":\"2\",\"Effect\":\"Allow\",\"Principal\":{\"AWS\":\"arn:aws:iam::123456789012:role/replicationRole\"},\"Action\":[\"s3:GetBucketVersioning\",\"s3:PutBucketVersioning\"],\"Resource\":\"arn:aws:s3:::{dest-bucket-name}\"}]}"
}
```

よし、これでOKのはず。
でしたが…

##レプリケートされない

待てども待てどもレプリケートされない。
確か非同期だったよな？と思って調べてみると、15分くらいはかかるらしい。

[Amazon S3 でオブジェクトのレプリケーションにかかる時間はどれくらいですか?]
(https://aws.amazon.com/jp/premiumsupport/knowledge-center/s3-object-replication-time/)

>ほとんどのオブジェクトは 15 分以内にレプリケートされますが、レプリケーションには数時間以上かかることがあります。
>レプリケーションは非同期プロセスで、オブジェクトは最終的にレプリケートされます。

ふむふむ、やっぱり。
では待ってみましょう。

##やっぱりレプリケートされない

15分待ってもレプリケートされない。
これは何かが間違っているに違いない。

まずはオブジェクトがレプリケートされる状況なのかをチェックする手段を探します。
ジャストな記事がありました。

[バケット間にレプリケーションを設定しましたが、新しいオブジェクトがレプリケートされません。この問題を解決するには、どうすれば良いですか?]
(https://aws.amazon.com/jp/premiumsupport/knowledge-center/s3-troubleshoot-replication/)

>レプリケートされていないオブジェクトのレプリケーションステータスを確認します。オブジェクトにレプリケーションステータスメタデータがない場合は、プレフィックスまたはタグフィルタを修正しなければならない可能性があります。

レプリケーションステータスのメタデータを確認する必要があるとのこと。
コマンドを叩いてみます。

```ruby:ロールARN
$ aws s3api head-object --bucket {source-bucket-name} --key test.txt 
{
    "AcceptRanges": "bytes",
    "LastModified": "2020-12-25T10:05:04+00:00",
    "ContentLength": 0,
    "ETag": "\"c5db3aac373050fe886c49b37a8cfdb6\"",
    "VersionId": "wG.VKaeCzNj8.ezCmCfV9ZnctlrWuXjr",
    "ContentType": "text/plain",
    "Metadata": {}
}
$
```

**メタデータ、無いですね！！**
どうやらそもそもレプリケートされてないらしい。
原因調査開始。

##フィルターの設定ミス

自分のログをもう一度見返した所、以下のjsonが気になりました。

```ruby:replication.json
$ cat ./replication.json 
{
  "Role": "replicationRole",
  "Rules": [
    {
      "Status": "Enabled",
      "Priority": 1,
      "DeleteMarkerReplication": { "Status": "Disabled" },
      "Filter" : { "Prefix": "Tax"},
      "Destination": {
        "Bucket": "arn:aws:s3:::{dest-bucket-name}"
      }
    }
  ]
}
$ 
```

Taxって何だ？Filterってことは固有名詞っぽいし…
一応公式のドキュメントを確認します。

[オプション：フィルターの指定]
(https://docs.aws.amazon.com/AmazonS3/latest/dev/replication-add-config.html#replication-config-optional-filter)

>ルールが適用されるオブジェクトのサブセットを選択するには、オプションのフィルターを追加します。オブジェクトキープレフィックス、オブジェクトタグ、または両方の組み合わせでフィルタリングできます。

なるほど。
つまり、現在はTaxプレフィックス配下しかレプリケートされない設定になっている様子。
testプレフィックス配下になるようレプリケーションルールを更新します。

```ruby:レプリケーションルール更新
$ vi ./replication.json 
$ 
$ cat ./replication.json 
{
  "Role": "arn:aws:iam::123456789012:role/replicationRole",
  "Rules": [
    {
      "Status": "Enabled",
      "Priority": 1,
      "DeleteMarkerReplication": { "Status": "Disabled" },
      "Filter" : { "Prefix": "test"},
      "Destination": {
        "Bucket": "arn:aws:s3:::{dest-bucket-name}"
      }
    }
  ]
}
$ aws s3api put-bucket-replication \
> --replication-configuration file://replication.json \
> --bucket {source-bucket-name}
$ aws s3api get-bucket-replication \
> --bucket {source-bucket-name}
{
    "ReplicationConfiguration": {
        "Role": "arn:aws:iam::123456789012:role/replicationRole",
        "Rules": [
            {
                "ID": "N2Q0MWJkOTItNzAyYi00OWVlLTkxYTMtOTcwNjdhMjljNTk2",
                "Priority": 1,
                "Filter": {
                    "Prefix": "test"
                },
                "Status": "Enabled",
                "Destination": {
                    "Bucket": "arn:aws:s3:::{dest-bucket-name}"
                },
                "DeleteMarkerReplication": {
                    "Status": "Disabled"
                }
            }
        ]
    }
}
$ 
```

それでもって、testプレフィックス配下にテストファイルを作成し、メタデータ確認。

```ruby:
$ aws s3api head-object --bucket {source-bucket-name} --key test/test.txt 
{
    "AcceptRanges": "bytes",
    "LastModified": "2020-12-25T09:23:42+00:00",
    "ContentLength": 0,
    "ETag": "\"d41d8cd98f00b204e9800998ecf8427e\"",
    "VersionId": "null",
    "ContentType": "text/plain",
    "Metadata": {}
}
$ 
```

**…空じゃん**
どういうこと？設定は間違えてないはずなのに…

##解決！！

数分悩んでふと気づきました。
これ、容量ゼロだとレプリケートされないのでは…？

テストだからとtouchしただけのファイルを上げているので、容量はゼロ。

試しになにか書いてみます。

```ruby:
$ vi ./test.txt 
$ cat ./test.txt 
Replication is succeeded!!
$ 
```
レプリケーション元バケットへアップロード。

```ruby:
$ aws s3 cp ./test.txt s3://{source-bucket-name}/test/
upload: ./test.txt to s3://{source-bucket-name}/test/test.txt 
```

メタデータを確認してみると…

```ruby:
$ aws s3api head-object --bucket {source-bucket-name} --key test/test.txt 
{
    "AcceptRanges": "bytes",
    "LastModified": "2020-12-25T10:35:39+00:00",
    "ContentLength": 27,
    "ETag": "\"c5db3aac373050fe886c49b37a8cfdb6\"",
    "VersionId": "Ne7uyQ_Y2qivKEhJhyqnz9Kajt3EwsZg",
    "ContentType": "text/plain",
    "Metadata": {},
    "ReplicationStatus": "PENDING"
}
```

PENDING！来た！
ここまでくればレプリケーションが終わるのを待つだけです。
そして数分後…

```ruby:
$ aws s3api head-object --bucket {source-bucket-name} --key test/test.txt 
{
    "AcceptRanges": "bytes",
    "LastModified": "2020-12-25T10:35:39+00:00",
    "ContentLength": 27,
    "ETag": "\"c5db3aac373050fe886c49b37a8cfdb6\"",
    "VersionId": "Ne7uyQ_Y2qivKEhJhyqnz9Kajt3EwsZg",
    "ContentType": "text/plain",
    "Metadata": {},
    "ReplicationStatus": "COMPLETED"
}
$ 
$ aws s3 ls s3://{dest-bucket-name}/test/ --profile {profile_name}
2020-12-25 19:35:39         27 test.txt
$
```

無事オブジェクトのレプリケーションが出来ました！

##まとめ

しょうもない所で散々つまづきましたが、無事オブジェクトをクロスアカウント&クロスリージョンでレプリケートすることが出来ました。
どなたかのお役に立てば幸いです。