【全リージョン対応】CloudTrailのログをAthenaのPartition Projectionなテーブルで作る

2021.02.08

CloudTrailのログを分析するためのAthenaテーブルを作る機会がありましたので、AthenaのPartition Projectionという機能を用いてリージョンごと・時系列ごとでパーティションを分割するように設定してみました。

今回はPartition Projectionについてざっくりおさらいして、CloudTrailのPartition ProjectionのサンプルDDLをご紹介します。

これまで

CloudTrail画面から作成されるデフォルトのDDLを用いてAthenaでテーブルを作成して、 us-east-1 の結果を返すクエリを投げてみます。

デフォルトのDDL(クリックで展開)
CREATE EXTERNAL TABLE cloudtrail_logs (
    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 [バケット名] bucket'
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://[バケット名]/AWSLogs/[アカウントID]/CloudTrail/'
TBLPROPERTIES ('classification'='cloudtrail');
SELECT * FROM cloudtrail_logs WHERE awsregion = 'us-east-1' LIMIT 10;

これを流した結果がこちら。

実行時間は約1分、スキャンされたデータは778MBと、10件出力するだけで大量の余計なデータを含んで検索していることが分かります。

ここで、DDLを少々改善して、リージョン、年、月、日でパーティションを分割するよう設定してみましょう。

パーティションを切ったDDL(クリックで展開)
CREATE EXTERNAL TABLE cloudtrail_logs_partition (
    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 [バケット名] bucket'
PARTITIONED BY (region string, year string, month string, day 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://[バケット名]/AWSLogs/[アカウントID]/CloudTrail/'
TBLPROPERTIES ('classification'='cloudtrail');

この場合、下記のように ALTER TABLE ADD PARTITION を利用してパーティションをロードする事により、データをクエリできるようになります。

ALTER TABLE cloudtrail_logs
ADD PARTITION (region='us-east-1',year='2021',month='01',day='05') 
LOCATION 's3://${BucketName}/AWSLogs/${AccountId}/CloudTrail/us-east-1/2021/01/05/';

ただ、これだと必要なパーティションを手動で都度ロードする必要があり、非常に面倒ですよね。

PARTITIONED BYで指定しているパーティションをリージョンだけにする事も可能ですが、それでも毎度 ALTER TABLE ADD PARTITION を叩くのは面倒ですし、月・日単位でパーティションを切らないと無駄なコストも発生してしまいます。

Partition Projectionを使うとどうなるのか

Partition Projectionを使うと、 ALTER TABLE ADD PARTITION でパーティションをロードする事無く、指定した内容で自動でパーティションを切ってくれます。

DDLにPartition Projectionの設定を仕込んで、後はSELECTを流すだけでOKです。

設定内容

今回作成したDDLは下記となります。

CREATE EXTERNAL TABLE cloudtrail_logs_partition_projection (
    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 ${BucketName} 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://[バケット名]/AWSLogs/[アカウントID]/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://[バケット名]/AWSLogs/[アカウントID]/CloudTrail/${region}/${date}',
    'classification'='cloudtrail',
    'compressionType'='gzip',
    'typeOfData'='file',
    'classification'='cloudtrail'
);

ポイントはハイライトしている部分で、 PARTITION BY でregionとdateを指定して、下部でPartition Projectionの設定をしてあげます。

Projection は dateregion(enum) で行っており、 regionのenumは2021/02/08時点で利用可能なリージョン一覧を列挙しています。

こちらのテーブルでSELECTした結果、最適化できて、実行時間・スキャンデータが少なくなっていることを確認できます。

SELECT * FROM cloudtrail_logs_partition_projection WHERE region = 'us-east-1' LIMIT 10;

1日単位でパーティションを切るように指定しているため、リージョン&日付指定でのSELECTもサクサクです。

SELECT * FROM cloudtrail_logs_partition_projection WHERE region = 'us-east-1' AND date = '2021/01/01' LIMIT 10;

まとめ

AthenaのPartition Projectionを使うことで、CloudTrailのログでもリージョン別・日付別で ALTER TABLE ADD PARTITION を叩くこと無くパーティションを設定することができます。

公式ドキュメントではリージョンに対応したPartition Projectionの例が記載されていなかったため記事にしてみました。

誰かのお役に立てれば幸いです。

参考

https://dev.classmethod.jp/articles/20200627-amazon-athena-partition-projection/

https://docs.aws.amazon.com/ja_jp/athena/latest/ug/cloudtrail-logs.html