セキュリティサービス部 佐竹です。
今回は AWS DataSync をハブ的に使い、Amazon Quick のあるバージニアリージョンの S3 Bucket にデータを集約するための検証を行います。
- はじめに
- AWS 環境構成図で考える
- 実際に検証してみる
- KMS の CMK を作成する
- S3 Bucket をそれぞれ作成する
- クロスアカウント用の IAM Role と IAM ポリシーを作成する
- バケットポリシーを追加する
- キーポリシーを追加する
- AWS DataSync のロケーションを作成する
- DataSync でログ出力のためのリソースベースポリシーを設定する
- AWS DataSync で Task を作成する
- Redshift を VPC 内に構築する
- Redshift でクエリを実行し、データを S3 Bucket へ UNLOAD する
- DataSync で Task を実行する
- Bucket でファイルが Sync されていることを確認する
- まとめ
はじめに
Amazon Redshift に保存されているデータを、Amazon Quick Suite や Quick Sight に取り込んで利用したいシーンがあります。
そのために以下の条件をクリアしたい状況となりました。
- Redshift が東京リージョンに存在しており、Amazon Quick Suite がバージニアリージョンに存在しているためクロスリージョンが必要
- また、Redshift と Amazon Quick Suite が別の AWS アカウントのためクロスアカウントが必要
- Redshift に、BI ツール等からの負荷がかかる処理を流すことで、通常業務への影響が出てしまうことを避けたい
- 他のデータを S3 Bucket に格納し、合わせて Amazon Quick Suite で処理したい
- 将来的には AWS DataSync を運用することで、他のアカウントに存在している各データを集約するためのハブとしたい
そして、これらをクリアする構成を検討しました。以下にそれを記載します。
名称に関する補足
なお、Amazon Quick Suite は上記の通り、名称が Amazon Quick に既に改められていますが、一旦今回はわかりやすさを優先して本ブログでは Amazon Quick Suite として記載します。
AWS 環境構成図で考える
先の条件を満たす構成を以下に示します。

ポイント
- Source アカウントと、Destination アカウントそれぞれに S3 Bucket を作成します
- 都合、Source アカウントは KMS (Customer Managed Key) で暗号化します
- Source アカウントで Redshift から「UNLOAD」を行い、必要なデータを同アカウントの S3 Bucket に格納します
- その後 AWS DataSync でクロスアカウントとクロスリージョンを同時に行います
- 最後に Quick Suite に S3 Bucket からデータをロードします
直接 Redshift からクロスアカウント&クロスリージョンで S3 Bucket に UNLOAD するという構成も取れますが、一度ローカルに出力し、データの Sync 処理を DataSync に任せることで役割を分担し、安定させたいという狙いがあります。
なお、この Sync は現状1日に1回程度を想定しており、リアルタイムで Sync する想定ではありません。DataSync は Cron でスケジュールされるバッチ処理的な動作を可能としますが、それとの相性もあるでしょう。
実際に検証してみる
KMS の CMK を作成する

まず、上図までを作成します。
最初に、S3 Bucket を暗号化するための鍵を「Source アカウント」の「東京リージョン」で作成します。

Alias を datasync-key として作成しました。Symmetric な Single Region Key です。
S3 Bucket をそれぞれ作成する

次に、「Source アカウント」の「東京リージョン」で S3 Bucket を新規作成しつつ、「Default encryption」では「SSE-KMS」、「Choose from your AWS KMS keys」と設定し、先ほど作成した Alias datasync-key を選択して紐づけます。バケット名は「bucket-datasync-src-20260218」としました。

「Destination アカウント」に切り替え、今後は「バージニアリージョン」で S3 Bucket を新規に作成します。都合、「Destination アカウント」では「SSE-S3」で暗号化とします。バケット名は「bucket-datasync-dest-20260218」としました。

ここまでで、上図が完成しています。ここから、DataSync の設定に移ります。
クロスアカウント用の IAM Role と IAM ポリシーを作成する

クロスアカウントアクセスには前提として IAM Role が必要です。そのための IAM Role を先に作成しておきます。
作業アカウントは「Destination アカウント」で、今回は名称として IAM Role CrossAccountRoleForDataSync を作成します。サービスとして「DataSync」を信頼した IAM Role とします。
なおこの IAM Role へアタッチする IAM ポリシーですが、今回少々要件が複雑です。
まずこの IAM Role がどこにアクセスする必要があるか、整理します。
- DataSync のロケーションとなっている S3 Bucket へのアクセス
- ソース S3 Bucketでは Read の権限が、デスティネーション S3 Bucket では Write の権限が必要
- ソース S3 Bucketで Read をするには、CMK で暗号化されているため、CMK を参照できる必要がある
以下がそれを兼ね備えた IAM ポリシー CrossAccountPolicyForDataSync です。
{ "Version": "2012-10-17", "Statement": [ { "Sid": "SourceBucketRead", "Effect": "Allow", "Action": [ "s3:GetBucketLocation", "s3:ListBucket", "s3:ListBucketMultipartUploads" ], "Resource": "arn:aws:s3:::bucket-datasync-src-20260218" }, { "Sid": "SourceObjectRead", "Effect": "Allow", "Action": [ "s3:GetObject", "s3:GetObjectTagging", "s3:GetObjectVersion", "s3:GetObjectVersionTagging" ], "Resource": "arn:aws:s3:::bucket-datasync-src-20260218/*" }, { "Sid": "DestBucketWrite", "Effect": "Allow", "Action": [ "s3:GetBucketLocation", "s3:ListBucket", "s3:ListBucketMultipartUploads" ], "Resource": "arn:aws:s3:::bucket-datasync-dest-20260218" }, { "Sid": "DestObjectWrite", "Effect": "Allow", "Action": [ "s3:PutObject", "s3:AbortMultipartUpload", "s3:DeleteObject", "s3:GetObject", "s3:GetObjectTagging", "s3:GetObjectVersion", "s3:GetObjectVersionTagging", "s3:ListMultipartUploadParts", "s3:PutObjectTagging" ], "Resource": "arn:aws:s3:::bucket-datasync-dest-20260218/*" }, { "Sid": "SourceKMSDecrypt", "Effect": "Allow", "Action": [ "kms:Decrypt", "kms:DescribeKey" ], "Resource": "arn:aws:kms:ap-northeast-1:SRCAWSACCOUNTID:key/45c3dbae-e23c-4aee-9928-db1231231231" } ] }
ちなみに、「GetObjectTagging」などのタグ系の操作へも許可が必要です。これがないと、DataSync の Task が失敗します。
なお、厳密に設計する場合は、DataSync のロケーションとなっている S3 Bucket へのアクセス権ごとに、IAM ポリシーも IAM Role も分離したほうが良いです。今回は検証目的のため、1つの IAM Role で処理します。
バケットポリシーを追加する
S3 Bucket のクロスアカウントでは、IAM Role へのアクセス許可を明示的に設定する必要があります。

「Source アカウント」の「bucket-datasync-src-20260218」のバケットポリシーを編集し、以下の内容を追記します。
{ "Version": "2012-10-17", "Statement": [ { "Sid": "AllowDataSyncCrossAccountRead", "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::DESTAWSACCOUNTID:role/CrossAccountRoleForDataSync" }, "Action": [ "s3:GetBucketLocation", "s3:ListBucket", "s3:ListBucketMultipartUploads" ], "Resource": "arn:aws:s3:::bucket-datasync-src-20260218" }, { "Sid": "AllowDataSyncCrossAccountObjectRead", "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::DESTAWSACCOUNTID:role/CrossAccountRoleForDataSync" }, "Action": [ "s3:GetObject", "s3:GetObjectTagging", "s3:GetObjectVersion", "s3:GetObjectVersionTagging" ], "Resource": "arn:aws:s3:::bucket-datasync-src-20260218/*" } ] }
「GetObjectTagging」などのタグ系の操作へも許可が必要で、これがないと DataSync の Task が失敗するのはバケットポリシーでも同様です。
実際に DataSync でどのようなエラーが出るのか?
参考まで、実際に DataSync のタスクで発生したエラーのログを以下に記します。
[ERROR] S3 Exception: op=GetObjectTagging bucket-datasync-src-20260218/111111/, code=403, type=15, exception=AccessDenied, msg=User: arn:aws:sts::DESTAWSACCOUNTID:assumed-role/CrossAccountRoleForDataSync/AwsSync-loc-0123576e3b8e4f481 is not authorized to perform: s3:GetObjectTagging on resource: "arn:aws:s3:::bucket-datasync-src-20260218/111111/" because no resource-based policy allows the s3:GetObjectTagging action req-hdrs: content-type=application/xml, x-amz-api-version=2006-03-01 rsp-hdrs: content-type=application/xml, date=Fri, 20 Feb 2026 06:30:11 GMT, server=AmazonS3, transfer-encoding=chunked, x-amz-id-2=2y3kEPlyihum833ahNXR3D2SVWcco3uJcI58ntbrAsrHAmyP1yZgk2HmwpaTEH75YAOWKBLYAd2FeaUFk5+IRe/UggrDyoHO, x-amz-request-id=GH0S5E36Z67QF5W0
キーポリシーを追加する
「Source アカウント」の CMK datasync-key のキーポリシーに以下を追記します。
{ "Sid": "AllowDataSyncRoleToDecrypt", "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::DESTAWSACCOUNTID:role/CrossAccountRoleForDataSync" }, "Action": [ "kms:Decrypt", "kms:DescribeKey" ], "Resource": "*" },
これで、IAM Role がクロスアカウントすることが可能となりました。
AWS DataSync のロケーションを作成する
「Destination アカウント」で、AWS DataSync でロケーションを作成します。
補足ですが、今回「Destination アカウント」に DataSync を設定していますが、「Source アカウント」で設定しても構いません。ただ今回は、費用の観点で「Source アカウント」の費用ではなく、「Destination アカウント」の費用としてプロジェクト管理をしたいため、「Destination アカウント」に DataSync 設定をしています。
さて、実際の作業なのですが、AWS DataSync でクロスアカウントをするとき、またクロスリージョンをするときは、マネジメントコンソールから作業ができません。よって、AWS CLI で作業となります。
まずは、「loc-src」として、ソース ロケーションを作成します。DataSync ロケーションを作成するとき、リージョンがロケーションと一致している必要があるため、「loc-src」は東京リージョンになります。コマンドは以下の通りです。
aws datasync create-location-s3 --s3-bucket-arn arn:aws:s3:::bucket-datasync-src-20260218 --s3-config BucketAccessRoleArn="arn:aws:iam::DESTAWSACCOUNTID:role/CrossAccountRoleForDataSync" --region ap-northeast-1
次に、「loc-dest」として、デスティネーション ロケーションを作成します。「loc-dest」はバージニアリージョンになります。コマンドは以下の通りです。
aws datasync create-location-s3 --s3-bucket-arn arn:aws:s3:::bucket-datasync-dest-20260218 --s3-config BucketAccessRoleArn="arn:aws:iam::DESTAWSACCOUNTID:role/CrossAccountRoleForDataSync" --region us-east-1
マネジメントコンソールで作業をすると、リージョンを取り間違えることが多い気がします。AWS CLI であれば、明示的に --region で指定できるので、そういうミスも減らせます。

さてこれでやっと上図まで完了しました。
DataSync でログ出力のためのリソースベースポリシーを設定する

次の状態として、上図を目指します。
DataSync のトラブルシューティングには、ログが必要です。DataSync は CloudWatch Logs にその実行ログを出力できるのですが、マネジメントコンソールから設定しても正しく権限設定が完了しないようです。
Task を作成&実行する前に、この権限設定である「リソースベースポリシー」設定を完了させます。これが完了しないと、タスクが実行すらされずに権限不足でエラーになります。
まず作業として、以下の json (TrustDataSyncToLog.json)を作成します。
{ "Statement": [ { "Sid": "DataSyncLogsToCloudWatchLogs", "Effect": "Allow", "Action": [ "logs:PutLogEvents", "logs:CreateLogStream" ], "Principal": { "Service": "datasync.amazonaws.com" }, "Resource": "*" } ], "Version": "2012-10-17" }
この json ファイルを CloudShell 上にアップロードした後、以下のコマンドを実行します。
aws logs put-resource-policy --policy-name TrustDataSyncToLog --policy-document file://TrustDataSyncToLog.json --region ap-northeast-1
これでポリシー設定は完了です。
補足ですが、DataSync の各ロケーションに紐づく IAM Role とそのポリシーには、CloudWatch Logs に関連する権限を記載せずとも、ログ出力は問題なく完了します*1。
AWS DataSync で Task を作成する
「Destination アカウント」でロケーションを作成した後は、DataSync でタスクの作成に移ります。
今回タスク名として task-cross-account-and-region-s3-sync と名付けたタスクを新規に作成します。
こちらもマネジメントコンソールからは作業ができないので、先ほどと同様に AWS CLI で作業します。
aws datasync create-task \ --source-location-arn "arn:aws:datasync:ap-northeast-1:DESTAWSACCOUNTID:location/loc-0123576e3b8e4f481" \ --destination-location-arn "arn:aws:datasync:us-east-1:DESTAWSACCOUNTID:location/loc-00b2346709ddb3c4e" \ --name "task-cross-account-and-region-s3-sync" \ --region ap-northeast-1

コマンドが正しく実行されれば、東京リージョンの DataSync において上画像の通りに「作成されたタスク」が確認できます。

補足ですが、デフォルト(上記の AWS CLI で作成されたタスク)では DataSync のログは出力されないため、作成されたタスクを Edit し、適宜 Logging の設定を追加してください。

ここまで完了しました。
Redshift を VPC 内に構築する

検証のため、「Source アカウント」に Private な VPC と Subnet を作成し、そこに Redshift Serverless を構築します。VPC には S3 Bucket へアクセスするための、VPC Endpoint を作成しておきます。この作業については長くなるので本ブログでは割愛します。
なお、Redshift には UNLOAD のためにも IAM Role が必要です。
まず、以下の IAM ポリシー RedshiftUnloadPolicy を作成します。S3 Bucket が暗号化されていることもあり、CMK へのアクセスも必要となります。なお、同 AWS アカウント内のアクセスのため、今回キーポリシーについては追記をしていません。
{ "Version": "2012-10-17", "Statement": [ { "Sid": "RedshiftUnloadS3Access", "Effect": "Allow", "Action": [ "s3:PutObject", "s3:GetObject", "s3:DeleteObject", "s3:GetBucketLocation", "s3:ListBucket" ], "Resource": [ "arn:aws:s3:::bucket-datasync-src-20260218", "arn:aws:s3:::bucket-datasync-src-20260218/*" ] }, { "Sid": "RedshiftUnloadKMSAccess", "Effect": "Allow", "Action": [ "kms:Decrypt", "kms:Encrypt", "kms:GenerateDataKey", "kms:DescribeKey" ], "Resource": "arn:aws:kms:ap-northeast-1:SRCAWSACCOUNTID:key/45c3dbae-e23c-4aee-9928-db1231231231" } ] }
これをアタッチした IAM Role RedshiftUnloadRole を作成します。なお、信頼ポリシーは以下の通りとしました。
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": [ "redshift.amazonaws.com", "redshift-serverless.amazonaws.com" ] }, "Action": "sts:AssumeRole" } ] }
Redshift でクエリを実行し、データを S3 Bucket へ UNLOAD する
さて、Redshift を作成しただけでは、データがないため UNLOAD の検証になりません。よって、以下の AWS 公式ドキュメントを参考に、オープンなデータを COPY で取り込みます。
このテストのためには、先の IAM ポリシーを書き換える必要がある点に留意してください。
{ "Version": "2012-10-17", "Statement": [ { "Sid": "RedshiftLoadAccess", "Effect": "Allow", "Action": [ "s3:GetObject", "s3:ListBucket", "s3:GetBucketLocation" ], "Resource": [ "arn:aws:s3:::redshift-downloads", "arn:aws:s3:::redshift-downloads/*" ] }, { "Sid": "RedshiftUnloadS3Access", "Effect": "Allow", "Action": [ "s3:PutObject", "s3:GetObject", "s3:DeleteObject", "s3:GetBucketLocation", "s3:ListBucket" ], "Resource": [ "arn:aws:s3:::bucket-datasync-src-20260218", "arn:aws:s3:::bucket-datasync-src-20260218/*" ] }, { "Sid": "RedshiftUnloadKMSAccess", "Effect": "Allow", "Action": [ "kms:Decrypt", "kms:Encrypt", "kms:GenerateDataKey", "kms:DescribeKey" ], "Resource": "arn:aws:kms:ap-northeast-1:SRCAWSACCOUNTID:key/45c3dbae-e23c-4aee-9928-db1231231231" } ] }
あとは、ドキュメントの通り、以下の SQL を順に実行してテーブル作成と COPY を終えます。
create table users( userid integer not null distkey sortkey, username char(8), firstname varchar(30), lastname varchar(30), city varchar(30), state char(2), email varchar(100), phone char(14), likesports boolean, liketheatre boolean, likeconcerts boolean, likejazz boolean, likeclassical boolean, likeopera boolean, likerock boolean, likevegas boolean, likebroadway boolean, likemusicals boolean); create table event( eventid integer not null distkey, venueid smallint not null, catid smallint not null, dateid smallint not null sortkey, eventname varchar(200), starttime timestamp); create table sales( salesid integer not null, listid integer not null distkey, sellerid integer not null, buyerid integer not null, eventid integer not null, dateid smallint not null sortkey, qtysold smallint not null, pricepaid decimal(8,2), commission decimal(8,2), saletime timestamp);
COPY users FROM 's3://redshift-downloads/tickit/allusers_pipe.txt' DELIMITER '|' TIMEFORMAT 'YYYY-MM-DD HH:MI:SS' IGNOREHEADER 1 REGION 'us-east-1' IAM_ROLE default; COPY event FROM 's3://redshift-downloads/tickit/allevents_pipe.txt' DELIMITER '|' TIMEFORMAT 'YYYY-MM-DD HH:MI:SS' IGNOREHEADER 1 REGION 'us-east-1' IAM_ROLE default; COPY sales FROM 's3://redshift-downloads/tickit/sales_tab.txt' DELIMITER '\t' TIMEFORMAT 'MM/DD/YYYY HH:MI:SS' IGNOREHEADER 1 REGION 'us-east-1' IAM_ROLE default;
これらのコマンドは AWS 公式のドキュメントそのままです。
ここまで来たら最後は UNLOAD のコマンドを実行すれば、S3 Bucket にデータが出力されます。
UNLOAD (' SELECT firstname, lastname, total_quantity FROM ( SELECT buyerid, sum(qtysold) total_quantity FROM sales GROUP BY buyerid ORDER BY total_quantity desc LIMIT 10 ) Q, users WHERE Q.buyerid = userid ORDER BY Q.total_quantity desc ') TO 's3://bucket-datasync-src-20260218/unload/top_buyers_' IAM_ROLE 'arn:aws:iam::SRCAWSACCOUNTID:role/RedshiftUnloadRole' KMS_KEY_ID '45c3dbae-e23c-4aee-9928-db1231231231' FORMAT AS PARQUET -- Parquet形式を指定 ENCRYPTED -- 指定したKMSキーで暗号化 CLEANPATH -- ALLOWOVERWRITEより安全に既存ファイルをクリーンアップ MANIFEST; -- QuickSight用のマニフェストファイルを自動生成

プレフィックスで区切った通りにファイルが出力されていることがわかります。
DataSync で Task を実行する
ここまでの検証で、ソースバケットに Redshift のデータが出力されています。
この後、「Destination アカウント」で AWS DataSync のタスクを開始すれば、データが「Source アカウント」から「Destination アカウント」に同期されます。

なお「Keep deleted files」にチェックを入れていると、Sync 先(bucket-datasync-dest-20260218)のバケットに、削除済のオブジェクトが残ることになります。削除までを同期したい場合は、コンソールから上記チェックを外します。

「Start」から DataSync でタスクを実行し、少し待ちます。

数分後、History で無事に処理が完了したことを確認します。
Bucket でファイルが Sync されていることを確認する

「Destination アカウント」の S3 Bucket「bucket-datasync-dest-20260218」において、先ほど UNLOAD したファイル「top_buyers_000.parquet」がプレフィックスの通りに同期されていることを確認します。
これで、検証は完了です。
なお、Redshift の Query Editor V2 では、「Scheduled queries」として定期的に UNLOAD が実行可能です。
そして、DataSync も Cron によるスケジュール実行が可能であるため、毎日1度 Redshift から必要なデータを Quick Suite 側へと連携するということが可能となります。

まとめ
Amazon Redshift に保存されているデータを、Amazon Quick Suite や Quick Sight に取り込んで利用したいシーンにおいて、AWS DataSync を利用する方法を今回検証しました。

今回、Amazon Quick Suite へのデータ取り込みまでは検証していませんが、一旦 S3 Bucket にデータが来てしまえば後は容易です。
色々とハマりどころがあり、特に権限設計では思った以上に複雑な点がありました。こういうのはやはり、実際にやってみないとわからないことも多いですね。
では、またお会いしましょう。
*1:コンソールから手動でタスクを Start するときは、ログインしているユーザの権限でタスクが動作します
佐竹 陽一 (Yoichi Satake) エンジニアブログの記事一覧はコチラ
セキュリティサービス部所属。AWS資格全冠。2010年1月からAWSを業務利用してきています。主な表彰歴 2021-2022 AWS Ambassadors/2020-2025 Japan AWS Top Engineers/2020-2025 All Certifications Engineers。AWSのコスト削減やマルチアカウント管理と運用を得意としています。