AWS Athena:跨账户写入 CTAS 查询结果



我在账户 A中有大型历史数据集。此数据集采用 csv 格式,并按year/month/day/hour/进行分区。我的目标是将这些数据转换为镶木地板,并具有额外的规范化步骤和额外的分区级别,例如year/month/day/hour/product/,并将其写回账户 A 的同一存储桶processed/"目录"下。所以"目录"树看起来像

S3_bucket_Account_A
dataset
|
├── raw
│   ├── year=2017
|   │   ├── month=01
|   |   │   ├── day=01
|   │   |   |   ├── hour=00
|   │   |   |   └── hour=01
|                                 
├── processed
│   ├── year=2017
|   │   ├── month=01
|   |   │   ├── day=01
|   |   |   │   ├── hour=00
|   |   │   |   |   ├── product=A
|   |   │   |   |   └── product=B
|   |   |   │   ├── hour=01
|   |   │   |   |   ├── product=A
|   |   │   |   |   └── product=B

为此,我使用 boto3 API 向 Athena 发送 CTAS 查询语句。我知道CTAS查询的局限性,例如,在同一查询中最多可以写入100个分区,CTAS查询结果的位置必须为空/唯一。因此,我当时处理一个原始分区,并且考虑到这些限制,正在动态生成CTAS查询的内容。

由于我使用账户 B来执行这些 CTAS 查询,但这些查询的结果应写入账户 A 拥有的S3 存储桶中。我已获得以下权限,这些权限在账户 A 的存储桶策略级别指定。

{
"Effect": "Allow",
"Principal": {
"AWS": "__ARN_OF_ACCOUNT_B__"
},
"Action": [
"s3:*"
],
"Resource": [
"arn:aws:s3:::dataset",
"arn:aws:s3:::dataset/*"
]
}

问题是账户 A(存储桶拥有者)无权访问由于账户 B 的 Athena 执行的 CTAS 查询而写入的文件

据我了解,账户 A 可以选择为我创建 IAM 角色,然后我会像账户 A 一样执行此任务。但不幸的是,这个选项是不可能的。

我已经找到了如何转移S3对象的所有权/更改ACL的方法。一种方法是在账户 B 的 S3 存储桶中输出 CTAS 查询结果,然后将这些文件复制到账户 A 的存储桶(原始源)

aws s3 cp s3://source_awsexamplebucket/ s3://destination_awsexamplebucket/ --acl bucket-owner-full-control --recursive

另一种方法是递归更新 acl 类似(原始来源)

aws s3 ls s3://bucket/path/ --recursive | awk '{cmd="aws s3api put-object-acl --acl bucket-owner-full-control --bucket bucket --key "$4; system(cmd)}'

但这两个选项将需要额外的GET和对 S3 的PUT请求,从而为AWS支付更多的钱。但更重要的是,在 CTAS 查询成功后,我使用创建的表中的分区更新账户 A的 AWS Glue 表(目标表)。这样,账户 A中的 IAM 用户可以立即开始查询转换后的数据。以下是我如何更新destination_table的一般想法

response = glue_client.get_partitions(
CatalogId="__ACCOUNT_B_ID__",
DatabaseName="some_database_in_account_B",
TableName="ctas_table"
)
for partition in response["Partitions"]:
for key in ["DatabaseName", "TableName", "CreationTime"]:
partition.pop(key)

glue_client.batch_create_partition(
CatalogId="__ACCOUNT_A_ID__",
DatabaseName="some_database_in_account_A",
TableName="destination_table",
PartitionInputList=response["Partitions"]
)

我以这种方式而不是MSCK REPAIR TABLE destination_table这样做,因为后者出于某种原因需要很长时间。如您所见,如果我选择使用aws s3 cp,当我复制有关分区的元信息时,我也需要考虑这一点

所以我真正的问题是如何在另一个账户执行的 CTAS 查询中向存储桶的拥有者授予完全控制权?

2019-06-25更新:

刚刚找到类似的帖子,但似乎他们使用 IAM 角色,这不是我的情况的选择

更新 2019-06-27

我发现:1)无法在CTAS查询中更改ACL。相反,S3 对象可以复制到具有新所有权的自身上(感谢 John Rotenstein 和 Theo 的评论)。

更新 2019-06-30

只是回顾一下。我从account B运行 CTAS 查询,但结果保存在account A拥有的存储桶中。这是 CTAS 查询"标头"的样子:

CREATE TABLE some_database_in_account_B.ctas_table
WITH (
format = 'PARQUET',
external_location = 's3://__destination_bucket_in_Account_A__/__CTAS_prefix__/',
partitioned_by = ARRAY['year', 'month', 'day', 'hour', 'product']
) AS (
...
...
)

由于我使用boto3提交 CTAS 查询,并且我知道__destination_bucket_in_Account_A____CTAS_prefix__,因此我可以直接在成功执行 CTAS 查询后在同一 python 脚本中更改它们的 ACL,而不是使用aws cp复制文件。

s3_resource = aws_session.resource('s3')
destination_bucket = s3_resource.Bucket(name="__destination_bucket_in_Account_A__")
for obj in destination_bucket.objects.filter(Prefix="__CTAS_prefix__"):
object_acl = s3_resource.ObjectAcl(destination_bucket.name, obj.key)
object_acl.put(
ACL='bucket-owner-full-control'
)

请注意,由于我需要提交一些超出 AWS Athena 限制的 CTAS 查询,因此我已经实现了自动提交新查询并执行一些其他操作的逻辑,例如更新目标 Glue 表和日志记录。因此,包含这些代码行非常简单。

目前,唯一能做到这一点的方法是在账户 A 中使用具有允许账户 B 代入该角色的信任策略的 IAM 角色。你提到这对你的案子是不可能的,这是不幸的。目前无法通过任何其他方式的原因是 Athena 不会使用"存储桶拥有者完全控制"选项写入文件,因此账户 A 永远不会完全拥有由账户 B 中的角色发起的操作创建的任何文件。

由于您在目标存储桶中被授予的策略允许所有内容,因此您可以做的一件事是在 CTAS 操作完成后运行一个任务,该任务列出创建的对象,并使用"存储桶拥有者完全控制"ACL 选项将每个对象复制到自身(相同的源密钥和目标密钥)。像这样复制对象是更改 S3 对象的存储和 ACL 属性的常用方法。正如您所说,这将产生额外费用,但与 CTAS 费用以及与未来对数据进行查询相关的费用相比,这些费用微不足道。

真正的缺点是必须在CTAS操作之后编写一些东西来运行,并对其进行协调。我建议查看Step Functions来做到这一点,您可以制作相当不错的工作流程来自动化Athena,并且运行成本非常低。我的应用程序或多或少完全符合您正在尝试执行的操作,这些应用程序使用 Step Functions、Lambda 和 Athena 并且花费几分钱(不过,我使用 IAM 角色进行跨账户工作)。

我建议您执行复制。

"额外的 GET 和 PUT 请求"将是次要的:

  • GET 为每 1,000 个请求 0.0004 USD
  • PUT 为每 1,000 个请求 0.005 USD

或者,您从账户 B 运行aws s3 cp --recursive命令,通过所有权变更将文件复制到自身(是的!)(它还需要另一项更改,例如将元数据设置为接受为复制命令)。这类似于您向put-object-acl提出的建议。

最新更新