LIVESENSE ENGINEER BLOG

リブセンスエンジニアの活動や注目していることを発信しています

バケット名を変更したいだけなのに、大量データ移行でDataSyncを使った話

これは Livesense Advent Calendar 2024 DAY 5 の記事です。

バケット名を変更したいけど無理

Q. S3のバケット名を変更する方法はないでしょうか?
A. ありません、新しいバケットを作成してデータを移行してください。

データ移行、AWS CLIでやる場合は以下のような感じですね。簡単に書きましたが、実際ファイルが大量にあるときはレートリミットなど考えることがたくさんありそうです。
今回は以下のようなコマンドを使わずにできるデータ移行について書きます。

aws s3 mb s3://${new bucket name}
aws s3 sync s3://${old bucket name} s3://${new bucket name}
aws s3 rb --force s3://${old bucket name}

大量データ移行

今回、バケット名を変更したいができないので、大量データが格納されたバケットAから新設したバケットBにデータを移行する必要が出てきました。
これに対しての方法は思いつくだけで以下のようなものがあります。

  • S3 レプリケーション
  • S3 バッチオペレーション
  • 上記のようなAWS CLI

ぼやきです。S3を触るたびに思うことなのですが、上記のように複数選択肢がある時点で、シンプルじゃないなあと思います。
シンプル・ストレージ・サービスといいますが、機能がたくさん追加されて、シンプルなのは遠い過去となりました。
ただ今回はDataSyncというサービスでシンプルにデータ移行できたので、その手順を書いていきます。

aws.amazon.com

ドキュメントを見る限りオンプレ→S3などの移行を想定しているように見えましたが、S3→S3もできます。
その場合、やることは以下の通りです。

  • 移行元(source location)を作成
  • 移行先(destination location)を作成
  • タスクを作成
  • 実行

シンプルなので、GUIから見れば初見でも大体できます。今回はAPIレベルでどんなことができるのかの調査を兼ねてTerraformで構築してみます。
また大量データ移行ということで、CloudTrailのイベントが格納されたバケットを移行することを想定しました。

Terraformで構築する場合

以下を構築します。

  • DataSyncが使用するIAMロール
  • ログ出力用のCloudWatch Logs
  • DataSync
    • ロケーション
    • タスク

IAMロール

ユースケースによって権限を絞ってください。サンプルとしてfull accessを付与しています。

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" "datasync" {
  name               = "s3-datasync"
  assume_role_policy = data.aws_iam_policy_document.sts_for_datasync.json
}

resource "aws_iam_role_policy_attachment" "datasync_s3" {
  role       = aws_iam_role.datasync.name
  policy_arn = "arn:aws:iam::aws:policy/AmazonS3FullAccess"
}

resource "aws_iam_role_policy_attachment" "datasync_logs" {
  role       = aws_iam_role.datasync.name
  policy_arn = "arn:aws:iam::aws:policy/CloudWatchLogsFullAccess"
}

CloudWatch Logs

後述しますが、大量データ転送時にエラーが発生することがあります。その際にログを出力するためのものです。

resource "aws_cloudwatch_log_group" "datasync_result" {
  name              = "/aws/datasync"
  retention_in_days = 30
  tags = {
    Name = "/aws/datasync"
  }
}

ロケーション

移行元と移行先両方用意します。以下は移行元のサンプルです。

resource "aws_datasync_location_s3" "datasync_source" {
  s3_bucket_arn = "arn:aws:s3:::sample_cloudtrail_hogehgoe"
  subdirectory  = "/AWSLogs/"

  s3_config {
    bucket_access_role_arn = aws_iam_role.datasync.arn
  }
}

タスク

resource "aws_datasync_task" "sample" {
  name                     = "task-sample"
  source_location_arn      = aws_datasync_location_s3.datasync_source.arn
  destination_location_arn = aws_datasync_location_s3.datasync_destination.arn
  cloudwatch_log_group_arn = aws_cloudwatch_log_group.datasync_result.arn

  # GUIで構築した場合の設定を明示的に書いている
  options {
    verify_mode                    = "ONLY_FILES_TRANSFERRED" # データとメタデータが転送された後にデータの整合性検証するかどうか
    overwrite_mode                 = "ALWAYS" # コピー先のファイルを上書きするか保持するかどうか、デフォルトでは上書きする
    atime                          = "BEST_EFFORT" # メタデータ、ファイルが最後にアクセスされた時刻を保持するか
    mtime                          = "PRESERVE" # メタデータ、ファイルが最後に変更された時刻を保持するか
    uid                            = "NONE" # LinuxにおけるユーザIDの保持をするかどうか
    gid                            = "NONE"# LinuxにおけるグループIDの保持をするかどうか
    preserve_deleted_files         = "PRESERVE" # 移行元で削除されたファイル移行先から削除するかどうか
    preserve_devices               = "NONE" # デバイスのメタデータを保持するかどうか
    posix_permissions              = "NONE" # ファイルのパーミッションを保持するかどうか
    bytes_per_second               = -1 # 帯域制限、オンプレミスからの移行時に使う雰囲気
    task_queueing                  = "ENABLED" # タスクを実行する前にキューに入れるかどうか
    log_level                      = "BASIC" # logsに記録する内容の詳細度、TRANSFERにすると詳細になる
    transfer_mode                  = "CHANGED" # 移行元と移行先で異なるデータ/メタデータのみを転送するかどうか
    security_descriptor_copy_flags = "NONE" # smb関係のオプション、オンプレミスからの移行時に使う雰囲気
    object_tags                    = "PRESERVE" # オブジェクトのタグを保持するかどうか
  }
}

ハマりポイント

課金に気をつけよう

DataSyncはコピーされたデータのGBあたりに料金がかかり、さらにget/put/listなどのS3のAPIを使うたびに料金がかかります。
大量データだと課金が大変なことになりますので、事前に試算しておくといいですね。
元となるバケットのデータ量を調べるには以下のコマンドを使います(このコマンドもバケットによっては結構なlistオペレーションなので注意が必要です)。

$ aws s3api list-objects --bucket sample_cloudtrail_hogehgoe --prefix AWSLogs/666666666666/CloudTrail/ --output json --query "[sum(Contents[].Size)]"

バケットの設定は引き継がれない

GUIから設定を行う時に注意書きが出てきますが、オブジェクトのコピーのみが対象です。バケットポリシーやバージョニングなどのバケットそのものの設定は引き継がれません。

AWSのコンソール、丸くなりましたね

大量にファイルがある場合2500万ファイルまで

タスクあたりのファイル、オブジェクトまたはディレクトリの最大数は2500万です。超過すると以下のエラーが出て止まります。

[ERROR] Failed to allocate destination file /tairyo_file_6666666666 : Cannot allocate memory

docs.aws.amazon.com

2500万ファイルを超える場合はfilterを使う

filterを使用し、複数のタスクを作成する必要があります。以下のようにprefixが分かれてた場合は以下のように対応する必要があります。

sample_cloudtrail_hogehgoe/2017/大量のデータ
~略~
sample_cloudtrail_hogehgoe/2024/大量のデータ
  • Terraform
locals {
  years = ["2017", "2018", "2019", "2020", "2021", "2022", "2023", "2024"]
}

resource "aws_datasync_task" "datasync_tasks" {
  for_each                 = toset(local.years)
  # 略

  includes {
    filter_type = "SIMPLE_PATTERN"
    value       = "/${each.key}*"
  }
}

使ってみた感想

今回のような1回限りのデータ移行以外でも、定期的にデータをsyncするときにも使えると思いました(変更されたデータのみsyncするオプションがあります)。
具体的にはオンプレミスからAWSへの移行時にハイブリッド構成を取る場合に任意のタイミングで簡単にsyncできるかな?と思いました。このように使い所が色々ありそうですね。
今回はDataSyncについての紹介とTerraformでの構築方法を紹介しましたが、参考になれば幸いです。