AWS DataSyncでS3から別AWSアカウントのEFSへデータ転送する

# はじめに
このドキュメントでは、アカウントAに作成されたS3にあるデータを
アカウントBにあるEFSへDataSyncを使ってデータ同期する方法をterraformのコードとともに記載します。

同一アカウント内での資料はたくさん見かけましたが
クロスアカウントでS3 to EFSな資料を見かけなかったので備忘録として残しておきます。

以下の4ステップです。
1. 転送元AWSアカウントでS3を作成する
2. 転送先AWSアカウントでEFSとDataSync用のRoleを作成する
3. 転送元AWSアカウントで作ったS3にクロスアカウントの設定を適用する
4. 転送先AWSアカウントでEFSのLocationおよびTaskを作成する

なお、ソースとなる環境がNFSやSMBのみな場合はDataSync Agentを使う必要があります。
今回はせっかくなのでエージェントレスな方式が取れないか、を検証した結果になります。

# モチベーション
以下のような状態でした。

* 社内環境で稼働しているNFSサーバをクラウドへ移行したい
  * 移行先としてEFSに目をつけた 
* ただし、NFSサーバにあるファイルは小さいかつ大量にある( 1MB以下のファイルが1000万ファイル以上、総容量は700GB程度 )
  * rsyncではTry&Errorするのに2週間以上かかりそう
* NFSサーバ上にあるファイルはたまたまバックアップ用AWS環境 S3にバックアップが取られていた
* AWS DataSyncを知る
    * どうやらS3 -> EFSはサポートされている
* バックアップ用AWS環境 S3 -> 移行検証用AWS環境のEFSへの移行ができるんじゃないか？
    * サポート構成ならエージェントレスなので、そこまで費用がかからないんじゃ？
    * VPC Peering やTGWなど、そういったネットワークを使わないで済みそう？
        * じゃあ試してみよう

という目論見です。


# 結論
* rsyncだと2週間〜1ヶ月はかかるようなものが12時間とかで終わってくれた
* 思ったよりterraformも(適用する順序はあるが)簡単だった。
* 札束で殴るのは正義


では、リブセンスアドベントカレンダー20日目としてやっていきましょう。

# 1.転送元AWSアカウントでS3を作成する
まず、転送元AWSアカウントでS3を作成してください。

```s3.tf
resource "aws_s3_bucket" "source_s3" {
  bucket = "source_s3"
  acl    = "private"
}
```

# 2.転送先AWSアカウントでEFSとDataSync用のRoleを作成する

```efs.tf
resource "aws_efs_file_system" "dest_efs" {
  encrypted              = true
  ### コードを短くするために1AZで作成
  availability_zone_name = "ap-northeast-1"
}

resource "aws_efs_mount_target" "dest_efs_a" {
  file_system_id = aws_efs_file_system.dest_efs.id
  subnet_id      = YOUR_SUBNET_ID
  security_groups = [
    aws_security_group.dest_efs.id
  ]
}

resource "aws_security_group" "dest_efs" {
  name        = "dest_efs_sg"
  description = "Security Group for DataSync Destination EFS"
  vpc_id      = YOUR_VPC_ID

  ingress {
    from_port = 2049
    to_port   = 2049
    protocol  = "tcp"
    self      = true
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = [0.0.0.0/0]
  }
}
```

```datasync_iam.tf
data "aws_iam_policy_document" "sts_for_datasync" {
  statement {
    effect  = "Allow"
    actions = ["sts:AssumeRole"]
    principals {
      type        = "Service"
      identifiers = ["datasync.amazonaws.com"]
    }
  }
}

resource "aws_iam_role" "dest_efs_datasync" {
  name               = "dest-efs-datasync-role"
  assume_role_policy = data.aws_iam_policy_document.sts_for_datasync.json
}

data "aws_iam_policy_document" "dest_efs_datasync" {
  statement {
    effect = "Allow"
    actions = [
      "s3:GetBucketLocation",
      "s3:GetBucketAcl",
      "s3:ListBucket",
      "s3:ListBucketMultipartUploads"
    ]
    resources = [
      "arn:aws:s3:::SOURCE_S3_NAME"
    ]
  }

  statement {
    effect = "Allow"
    actions = [
      "s3:AbortMultipartUpload",
      "s3:ListMultipartUploadParts",
      "s3:PutObjectTagging",
      "s3:GetObjectTagging",
      "s3:PutObject",
      "s3:GetObject",
      "s3:DeleteObject"
    ]
    resources = [
      "arn:aws:s3:::SOURCE_S3_NAME/*"
    ]
  }
}

resource "aws_iam_policy" "dest_efs_datasync" {
  name   = "dest-efs-datasync-policy"
  policy = data.aws_iam_policy_document.dest_efs_datasync.json
}

resource "aws_iam_role_policy_attachment" "dest_efs_datasync" {
  role       = aws_iam_role.dest_efs_datasync.name
  policy_arn = aws_iam_policy.dest_efs_datasync.arn

  depends_on = [
    aws_iam_role.dest_efs_datasync
  ]
}
```

# 3.転送元AWSアカウントで作ったS3にクロスアカウントの設定を適用する
`arn:aws:iam::DESTINATION_AWS_ACCOUNT_ID:root` という記載がしてありますが
後続のDataSync用のLocationを作るユーザが決め打ちの場合はユーザを決めておくと良いでしょう。
複数人で作る場合はアカウント委任してしまったほうが楽だと思います。
※ 試せて無いのでもしかしたらアカウントごと委任しないとだめかも知れません。

また、後述作業のDataSync Locationを作成する場合、`s3:DeleteObject` が無いと作成に失敗します。
「勝手に消されてほしくないし、権限絞っておくか」と削った結果、後続のLocation作成時にエラーが吐かれました。
どうやらDataSyncが自身で何かしらのファイル？を追加・削除するために必要な権限のようですね。


```s3_policy.tf
resource "aws_s3_bucket_policy" "source_s3_policy" {
  bucket = aws_s3_bucket.source_s3.id

  policy = jsonencode(
    {
      "Version" : "2012-10-17",
      "Statement" : [
        {
          "Sid" : "",
          "Effect" : "Allow",
          "Principal" : {
            "AWS" : [
              "arn:aws:iam::DESTINATION_AWS_ACCOUNT_ID:role/dest-efs-datasync-role-role",
              "arn:aws:iam::DESTINATION_AWS_ACCOUNT_ID:root"
            ]
          },
          "Action" : [
            "s3:GetBucketLocation",
            "s3:ListBucket",
            "s3:ListBucketMultipartUploads",
            "s3:AbortMultipartUpload",
            "s3:GetObject",
            "s3:DeleteObject",
            "s3:ListMultipartUploadParts",
            "s3:PutObject",
            "s3:GetObjectTagging",
            "s3:PutObjectTagging"
          ],
          "Resource" : [
            "arn:aws:s3:::SOURCE_S3_NAME",
            "arn:aws:s3:::SOURCE_S3_NAME/*"
          ]
        }
      ]
    }
  )
}
```


# 4.転送先AWSアカウントでEFSのLocationおよびTaskを作成する

```datasync.tf
# Source location
resource "aws_datasync_location_s3" "source_datasync_location" {
  s3_bucket_arn = "arn:aws:s3:::SOURCE_S3_NAME"
  subdirectory  = "/"

  s3_config {
    bucket_access_role_arn = aws_iam_role.dest_efs_datasync.arn
  }
}

# Destination location
resource "aws_datasync_location_efs" "destination_datasync_location" {
  efs_file_system_arn = aws_efs_mount_target.dest_efs_a.file_system_arn

  ec2_config {
    security_group_arns = [aws_security_group.dest_efs.arn]
    subnet_arn          = YOUR_SUBNET_ARN
  }
}

resource "aws_datasync_task" "datasync" {
  destination_location_arn = aws_datasync_location_s3.source_datasync_location.arn
  name                     = "efs-datasync-from-s3-task"
  source_location_arn      = aws_datasync_location_efs.source_datasync_location.arn

  # Source Locationと同じファイル構造にしたければ以下のように記載
  options {
    preserve_deleted_files = "REMOVE"
  }

  # スケジュールタスクとして設定したければ以下のように記載
  schedule {
    schedule_expression = "cron(0 18 ? * * *)"
  }
}
```

# おわりに
クロスアカウント設定の都合上、移行元・移行先AWSでterraformを実行する必要がありますが
これでS3 to EFSへのDataSync Taskが作成できます。

サポートされている形式ならAWSのバックボーンネットワークを利用してくれるので通信コストなどを気にせずに済みます。
また、rsyncでは時間がかかってしまうような場合でも、DataSyncを使えばとても速く同期が完了します。
費用さえ許せば、rsyncを使うよりも良いソリューションになる気がします。

現状、EFSに同期されたファイルのタイムスタンプや所有権などがちょっとおかしいですが、そちらは分かり次第追記します。

# 参考文献
https://qiita.com/calorie/items/8c5f81bd4597246f481f
https://aws.amazon.com/jp/blogs/news/webinar-bb-awsdatasync-2021/