Amazon Athenaを使ってみた

# はじめに

Athenaがよく分からなかったので使ってみました。
お金を溶かしたくありません。

# Amazon Athenaとは

Athenaとは、S3に保存したデータに対してクエリを叩くサービスです。

# 注意点

+ 何も考えずにクエリを叩くとお金が溶ける

# お金が溶けることへの対策

お試しで使用する際は大容量のS3を指定することは少ないと思うので、高額請求の心配はあまりないと思います。
しかし、何が起こるか分からないので、以下の対策を行います。

## ワークグループのデータの使用状況の制御を使用

クエリごとの最大バイトを制限して、予期せぬ高額請求を避けることができます。
100GBなら0.5USDです。間違えても1回のクエリで最大50円程度なので問題ありませんね。

![スクリーンショット 2021-03-04 19.34.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344711/79082585-37ce-6897-7303-3584b50f7f69.png)

## フルスキャンしない

Athenaの料金は、スキャンされたバイト数に対して課金され、1TB当たり5.00USDです。
むやにやたらにフルスキャンせず、必要に応じたSQL文を使用しましょう。

## パーティションを設定する

以下の構造のS3があるとして。

```
バケット
└── AWSLogs
    └──アカウントID
　　　　　     └──CloudTrail
　　　　　　　       └──リージョン名
　　　　　　　　　         ├──2020
　　　　　　　　　         |  └──1
　　　　　　　　　         |     └──1
              └──2021     
　　　　　　　　　  　          └──1
　　　　　　　　　 　　　             └──1
```

パーティションを設定し、WHERE句でパーティションを指定すると、2020以下のフォルダのみにクエリをかけることができます。
逆に2021以下のフォルダのみにすることも可能です。
また、2020/1以下のみにすることもできます。

## リージョンを気にかける

Athena自体というより、S3のデータ転送料の問題です。
東京のAthenaがバージニア北部のS3を、1TB分クエリしたと仮定します。

バージニア北部のS3→東京のデータ転送（アウト）は0.02USD/GB。
0.02*1000=20USDで2000円となります。
1TB当たりのAthenaのスキャン料である500円の4倍です。

計算してみると、意外と高額になることがわかりました。
高すぎて合っているか不安になりますが、多かれ少なかれ転送料がかかってしまうことは事実なので、クエリ先のS3のリージョンは気にかけた方がいいでしょう。

# Athenaを使ってみる

今回は、同一アカウント内のS3にあるCloudTrailログを指定して、Athenaを実行します。
当初はクロスアカウントで行う予定でしたが、難しかったので中断しました（後述します。）。

まず、テーブルの作成を行います。
CloudTrailイベント履歴から、Athenaテーブルを作成をクリック。

![スクリーンショット 2021-03-04 20.19.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344711/916f8e8d-a5bd-c086-75fe-022d7915b953.png)

指示通りに作成するとパーティションを設定しないため、ひとまずコピーだけしてAthenaに移動します。

![スクリーンショット 2021-03-04 20.20.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344711/9f8f26d1-9a95-efa1-1273-91af5c8840dd.png)

クエリエディタに貼り付けて、ちょっと修正＆追記します。
追記したのは`PARTITIONED BY`と`TBLPROPERTIES`です（Classmethod様から拝借しました。ありがとうございます！）。

https://dev.classmethod.jp/articles/cloudtrail-athena-partition-projection-table/

```
CREATE EXTERNAL TABLE cloudtrail_logs_aws_controltower_logs_xxxxxxxxxxx_us_east_1_xxxxxxxxx (
    eventVersion STRING,
    userIdentity STRUCT<
        type: STRING,
        principalId: STRING,
        arn: STRING,
        accountId: STRING,
        invokedBy: STRING,
        accessKeyId: STRING,
        userName: STRING,
        sessionContext: STRUCT<
            attributes: STRUCT<
                mfaAuthenticated: STRING,
                creationDate: STRING>,
            sessionIssuer: STRUCT<
                type: STRING,
                principalId: STRING,
                arn: STRING,
                accountId: STRING,
                userName: STRING>>>,
    eventTime STRING,
    eventSource STRING,
    eventName STRING,
    awsRegion STRING,
    sourceIpAddress STRING,
    userAgent STRING,
    errorCode STRING,
    errorMessage STRING,
    requestParameters STRING,
    responseElements STRING,
    additionalEventData STRING,
    requestId STRING,
    eventId STRING,
    resources ARRAY<STRUCT<
        arn: STRING,
        accountId: STRING,
        type: STRING>>,
    eventType STRING,
    apiVersion STRING,
    readOnly STRING,
    recipientAccountId STRING,
    serviceEventDetails STRING,
    sharedEventID STRING,
    vpcEndpointId STRING
)
COMMENT 'CloudTrail table for hoge bucket'
PARTITIONED BY (region string, date string)
ROW FORMAT SERDE 'com.amazon.emr.hive.serde.CloudTrailSerde'
STORED AS INPUTFORMAT 'com.amazon.emr.cloudtrail.CloudTrailInputFormat'
OUTPUTFORMAT 'org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat'
LOCATION 's3://aws-controltower-logs-xxxxxxxxx-us-east-1/xxxxxxxx/AWSLogs/xxxxxxxxxxx/CloudTrail/'
TBLPROPERTIES (
    'projection.enabled' = 'true',
    'projection.date.type' = 'date',
    'projection.date.range' = 'NOW-1YEARS,NOW',
    'projection.date.format' = 'yyyy/MM/dd',
    'projection.date.interval' = '1',
    'projection.date.interval.unit' = 'DAYS',
    'projection.region.type' = 'enum',
    'projection.region.values'='us-east-1,us-east-2,us-west-1,us-west-2,af-south-1,ap-east-1,ap-south-1,ap-northeast-2,ap-southeast-1,ap-southeast-2,ap-northeast-1,ca-central-1,eu-central-1,eu-west-1,eu-west-2,eu-south-1,eu-west-3,eu-north-1,me-south-1,sa-east-1',
    'storage.location.template' = 's3://aws-controltower-logs-xxxxxxxxxxxxx-us-east-1/xxxxxxxxxx/AWSLogs/xxxxxxxxxxxx/CloudTrail/${region}/${date}',
    'classification'='cloudtrail',
    'compressionType'='gzip',
    'typeOfData'='file',
    'classification'='cloudtrail'
);
```
これでクエリを実行すると、パーティション済みでテーブルが作成されます。
`projection`を利用したのでパーティションのロード処理は必要ありませんが、利用しない場合はロード処理のためのクエリを実行しなければなりません。

テーブルが作成できたので、Athenaを実行してみます。

```
SELECT count(*) FROM cloudtrail_logs_aws_controltower_logs_xxxxxxxxxx_us_east_1_xxxxxxxx WHERE region = 'ap-northeast-1' AND date='2021/02/01';

実行時間: 0.95 秒, スキャンしたデータ: 125.05 KB
212
```

成功しました。
お金も0.00005円程度でしょうか。

日にちの範囲指定などもできるので、必要な情報だけを安く求められそうですね。

# Athenaでクロスアカウントを止めた理由

既存オブジェクトの所有権を変更するのが大変そうだったからです。

今回、マルチアカウントのCloudTrailログを集約しているS3に対してAthenaを使用しました。

Athenaでクエリを実行すると、以下のようにエラーがでます。

![スクリーンショット 2021-03-04 20.59.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344711/770ed593-28eb-f353-8861-114a0369f858.png)

アクセス拒否エラーを解決するには、2つの手順が必要です。

1つ目は、異なるアカウントにあるS3のバケットポリシーを以下のように変更することです。

https://docs.aws.amazon.com/ja_jp/athena/latest/ug/cross-account-permissions.html

```
{
   "Version": "2012-10-17",
   "Id": "MyPolicyID",
   "Statement": [
      {
          "Sid": "MyStatementSid",
          "Effect": "Allow",
          "Principal": {
             "AWS": "arn:aws:iam::123456789123:root"
          },
          "Action": [
             "s3:GetBucketLocation",
             "s3:GetObject",
             "s3:ListBucket",
             "s3:ListBucketMultipartUploads",
             "s3:ListMultipartUploadParts",
             "s3:AbortMultipartUpload",
             "s3:PutObject"
          ],
          "Resource": [
             "arn:aws:s3:::my-athena-data-bucket",
             "arn:aws:s3:::my-athena-data-bucket/*"
          ]
       }
    ]
 }
```

2つ目は、オブジェクト所有者の変更です。
オブジェクト所有者は、S3のバケット所有者ではなく、アップロードをした人が所有者になります。
今回の場合はCloudTrailが所有者になるため、バケット所有者でもないAthenaを実行するアカウントは、例えS3バケットポリシーを変更しても、オブジェクトへのアクセスが拒否されます。

所有者の変更は

+ コンソールかCLIなどでオブジェクトを一つずつ変更
+ Object Ownershipで変更

などの方法を見つけましたが、既存のオブジェクトの所有者を変更するには、一つずつ行うしかなさそうでした。

よって、Athenaの実行対象S3があるアカウント内のAthenaを使用した方が楽だと考え、クロスアカウントでの実行を中断しました。

ちなみにObject Ownershipはこれです。

![スクリーンショット 2021-03-04 22.51.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/344711/12cc3971-3823-d34c-7c3f-cc8694720a68.png)

「希望するバケット所有者」を設定すれば、新たにアップロードされるオブジェクトの所有者はバケット所有者になります。
ただし、この設定だけでなく、バケットポリシーの変更も必要です。

# さいごに

Athenaを使ってみて、Athenaへの恐怖が多少和らぎました。
分からなかったらやってみるのが一番ですね。
まだまだ分からないことばかりなので、これからもAthenaを積極的に使っていこうと思います。

# 参考

https://aws.amazon.com/jp/athena/pricing/

https://aws.amazon.com/jp/s3/pricing/

https://dev.classmethod.jp/articles/cloudtrail-athena-partition-projection-table/

https://blog.serverworks.co.jp/2020/11/25/114449

https://docs.aws.amazon.com/ja_jp/athena/latest/ug/cross-account-permissions.html

https://blog.jicoman.info/2019/01/using-athena-cloudtail-logs-anorher-aws-account/

https://qiita.com/kotatsu360/items/091c6def1b41eecb696b

https://dev.classmethod.jp/articles/amazon-s3-object-ownership-enables-bucket-owners-to-automatically-assume-ownership/

https://aws.amazon.com/jp/premiumsupport/knowledge-center/s3-require-object-ownership/

https://docs.aws.amazon.com/AmazonS3/latest/userguide/about-object-ownership.html