Glue Crawler のクローリングが完了したら Lambda function から QuickSight の SPICE を更新する

本記事では AWS Glue crawler のクロールが終わったら QuickSight の SPICE を Refresh する構成を作ります。 データ処理の自動化の参考になれば幸いです。
2024.04.22

目的

本記事では AWS Glue crawler のクロールが終わったらイベント駆動で QuickSight の SPICE を更新する構成を作ります。
AWS の公式ブログ "Event-driven refresh of SPICE datasets in Amazon QuickSight"[1]を参考にしています。

以前書いたこちらの記事と似ていますが、本記事ではクロールが終わったら QuickSight の SPICE を更新するので、より実践的な記事となっています。

全体を通して以下のような構成を作成します。

1~3 までは上記の記事と同じなので、そちらも参考にしていただければと思います。
また、ほとんど CDK で構築しますが、QuickSight は CLI とマネジメントコンソールで設定します。

ただし、マネジメントコンソールでも同じ構成を作り、クロールの実行後 SPICE を自動で更新することが可能です。

前提条件・知識

  • AWS アカウントを作成済みで、QuickSight のセットアップが完了している
  • (ざっくりですが)IAM Role, Policy, Cloud9 の環境を作成できる権限がある
  • 使いたいAWS アカウントのリージョンで cdk bootstrap コマンドを実行済み
  • IAM Role, Policy を作成する方法、aws cli の使い方

手順

まずは CDK を実行するための環境を Cloud9 で準備します。

Cloud9 の環境を立ち上げる

はじめにCloud9 の環境が使う EC2 インスタンスにアタッチするロールを作成します。
ロールには以下のポリシーを関連付けます。
⚠️ 最小権限ではありません。実際のプロジェクトで使うときは、最小権限を設定したほうが良いです。

  • AWSCloud9SSMInstanceProfile: Session Manager で Cloud9 の環境に接続するためです。
  • AmazonS3FullAccess: S3 にオブジェクトをアップロード、削除するためです。
  • 以下の Inline Policy:cdk のデプロイに必要な権限、Glue クローラーを開始、QuickSight のリソースを作成・削除、CloudFormation の Stack を Describe する権限です。
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "sts:AssumeRole"
            ],
            "Resource": [
                "arn:aws:iam::*:role/cdk-*"
            ]
        },
        {
            "Sid": "StartCrawler",
            "Effect": "Allow",
            "Action": [
                "glue:StartCrawler"
            ],
            "Resource": [
                "*"
            ]
        },
        {
            "Sid": "CfnDescribe",
            "Effect": "Allow",
            "Action": [
                "cloudformation:DescribeStacks"
            ],
            "Resource": [
                "*"
            ]
        },
        {
            "Sid": "QS",
            "Effect": "Allow",
            "Action": [
                "quicksight:*"
            ],
            "Resource": [
                "*"
            ]
        }
    ]
}

そして、Cloud9 の環境を作成し、EC2 インスタンスにロールをアタッチします。

ロールのアタッチは以下の記事を参考にしていただければと思います。

QuickSight 以外のリソースの作成

Cloud9 の環境に接続して、以下のコマンドを実行します。
私が作成した本ブログ用のリポジトリ(https://github.com/yuta-cmth/blog-glue-trigger-refresh-qs-spice)の clone と、必要なパッケージをインストールします。

git clone https://github.com/yuta-cmth/blog-glue-trigger-refresh-qs-spice.git
cd blog-glue-trigger-refresh-qs-spice
npm i

続いて、CDK で使う環境変数の宣言後、cdk コマンドを使ってリソースを作成します。

# QuickSight の dataset id の宣言とリソース作成
export QS_DATA_SET_ID='blog-glue-trigger-refresh-qs-spice-dataset'
cdk deploy --require-approval never

コマンド実行後、ターミナルで以下のような出力があれば成功です。

✨ Total time: 133.24s

このあと使うため、CloudFormation の Outputs の必要な値をシェル変数に入れます。
以下のコマンドで S3 バケット名、Glue クローラーの名前、Athena の Work Group 名を取得します。

bucket_name=$(aws cloudformation describe-stacks --stack-name BlogGlueTriggerStack --output text --query 'Stacks[0].Outputs[?OutputKey==`BlogGlueCrawlerBucketName`].OutputValue')
crawler_name=$(aws cloudformation describe-stacks --stack-name BlogGlueTriggerStack --output text --query 'Stacks[0].Outputs[?OutputKey==`BlogGlueCrawlerName`].OutputValue')
athena_work_group_name=$(aws cloudformation describe-stacks --stack-name BlogGlueTriggerStack --output text --query 'Stacks[0].Outputs[?OutputKey==`BlogGlueAthenaWorkGroupName`].OutputValue')

また、S3 にファイルを1つアップロードし、Glue の Crawler を実行しておきます。

# Upload s3 object and run crawler.
aws s3 cp ./s3_test_data/data/test1 "s3://${bucket_name}/data/test1"
aws glue start-crawler --name "${crawler_name}"

アップロードしたファイル s3_test_data/data/test1 の中身は以下の通りで、後で QuickSight で表示します。

{"timestamp": "2024-01-01 00:00:00", "metric1": 1, "dimension1": "foo"}
{"timestamp": "2024-01-01 00:00:00", "metric1": 3, "dimension1": "bar"}
{"timestamp": "2024-01-01 00:01:00", "metric1": 1, "dimension1": "foo"}
{"timestamp": "2024-01-01 00:01:00", "metric1": 1, "dimension1": "bar"}

QuickSight のリソースを作成

ここからは CLI とマネジメントコンソールの両方を使って操作していきます。

まずは、CDK で作成した S3 へのアクセス権限を QuickSight に与えます。
マネジメントコンソール画面の右上の "Manage QuickSight" をクリックし、Security & permissions をクリックします。

⚠️ QuickSight の設定によって、Security & permissions を確認するために別のリージョンに移動する必要があります。
私の環境の場合、バージニア北部リージョンに切り替えてから、"Manage QuickSight" をクリックしました。

manage_quick_sight

QuickSight access to AWS services の Manage ボタンをクリックします。

CDK で作成した S3 バケット名へのアクセス許可をします。

これをしておくことで、QuickSight が Athena を使って S3 にクエリを発行できます。

続いて、Cloud9 の環境で以下のコマンドを実行し、AWS アカウントの ID を取得します。

export AAI=$(aws sts get-caller-identity --query "Account" --output text)

次に、マネジメントコンソールの QuickSight で自分のユーザー名を取得します。
以下の画像の通り、 QuickSight の画面に入って右上の部分にユーザー名があるのでコピーしておきます。

blog_glue_trigger_refresh_qs_spice_qs_username

続いて以下のコマンドでシェル変数に QuickSight のユーザー名を入れます。

# このコマンドを入力後、コピーしておいたユーザー名をペーストし、エンターを押します。
read -s QUICKSIGHT_USERNAME; export QUICKSIGHT_USERNAME

ここから CLI で一気に QuickSight のリソースを作成していきます。
今回は CLI で Dataset を作成しますが、マネジメントコンソールで作っても Lambda から SPICE を更新することができます。
data source, dataset, analysis, dashboard の4つのリソースを作りますが、いずれも以下の流れで作成します。

  1. jsonnet ファイルをコンパイルして QuickSight リソースを定義する JSON を生成する
  2. aws quicksight create-<リソース> --cli-input-json <生成した JSON> コマンドで QuickSight のリソースを作成する

JSON は AWS の workshop studio "QuickSight Workshops > Admin Workshop > Content Porting"[2] を参考にして作りました。

まずは data source を作ります。
jsonnet によって、リポジトリの qs_jsonnet/create-data-source.jsonnet を JSON に変換します。

jsonnet では変数を使うことができるので、環境によって異なるような値(AWS アカウントIDなど)は変数に入れます。
ただし、jsonnet は環境変数を読むことができないので、--ext-str という引数を使って値を渡します。

docker run \
	-v $(pwd):/app \
	-w /app \
	bitnami/jsonnet:latest \
	--ext-str "AWS_ACCOUNT_ID=$AAI" \
	--ext-str "QUICKSIGHT_USERNAME=$QUICKSIGHT_USERNAME" \
	--ext-str "ATHENA_WORKGROUP=$athena_work_group_name" \
	qs_jsonnet/create-data-source.jsonnet > jsonnet.out/create-data-source.json
aws quicksight create-data-source --cli-input-json file://jsonnet.out/create-data-source.json

同じ要領で dataset, analysis, dashboard を作成します。

# Create a Dataset
docker run \
	-v $(pwd):/app \
	-w /app \
	bitnami/jsonnet:latest \
	--ext-str "AWS_ACCOUNT_ID=$AAI" \
	--ext-str "QUICKSIGHT_USERNAME=$QUICKSIGHT_USERNAME" \
	--ext-str "DATA_SET_ID=$QS_DATA_SET_ID" \
	qs_jsonnet/create-data-set.jsonnet > jsonnet.out/create-data-set.json
aws quicksight create-data-set --cli-input-json file://jsonnet.out/create-data-set.json

# Create an analysis
docker run \
	-v $(pwd):/app \
	-w /app \
	bitnami/jsonnet:latest \
	--ext-str "AWS_ACCOUNT_ID=$AAI" \
	--ext-str "QUICKSIGHT_USERNAME=$QUICKSIGHT_USERNAME" \
	qs_jsonnet/create-analysis.jsonnet > jsonnet.out/create-analysis.json
aws quicksight create-analysis --cli-input-json file://jsonnet.out/create-analysis.json

# Create a dashboard.
docker run \
	-v $(pwd):/app \
	-w /app \
	bitnami/jsonnet:latest \
	--ext-str "AWS_ACCOUNT_ID=$AAI" \
	--ext-str "QUICKSIGHT_USERNAME=$QUICKSIGHT_USERNAME" \
	qs_jsonnet/create-dashboard.jsonnet > jsonnet.out/create-dashboard.json
aws quicksight create-dashboard --cli-input-json file://jsonnet.out/create-dashboard.json

ここまでうまくいっているか、マネジメントコンソールの QuickSight 画面で確認します。

Datasets には blog-glue-trigger-refresh-qs-spice が作成されました。

Analyses にも同じ名前の analysis が作成されました。

Dashboards にも同じ名前の dashboard が作成されました。
現在の dashboard はこのような感じです。

これで必要なリソースが立ち上がりました。

Glue クローラーを実行して、QuickSight の SPICE が更新されることを検証する

動作検証をしていきます。
Cloud9 の環境で以下のコマンドを実行します。
追加の S3 オブジェクトを作成し、Glue クローラーを実行します。

aws s3 cp ./s3_test_data/data/test2 "s3://${bucket_name}/data/test2"
aws glue start-crawler --name "${crawler_name}"

クロールが完了すると、EventBridge によって Lambda function https://github.com/yuta-cmth/blog-glue-trigger-refresh-qs-spice/blob/main/codes/lambda/blog_glue_crawler_success_handler/main.py が実行されます。
この Lambda function の中で↓の通り QuickSight の SDK を使い SPICE を更新しています。

response = quicksight.create_ingestion(AwsAccountId=account_id, DataSetId=data_set_id,
                                                   IngestionId=ingestion_id)

続いて、マネジメントコンソールで QuickSight > Datasets > blog-glue-trigger-refresh-qs-spice > Refresh を開きます。 Glue クローラーの実行が 1分ほどかかるので、このまま待機します。
クロールが完了すると、↓のように QuickSight で Refresh が開始します。

Refresh が完了したら、Dashboard を見ましょう。
SPICE が更新されたので、Dashboard に表示されるデータも以下のように更新されました。

リソースの削除

CDK で立ち上げた部分とそうでない部分を以下のコマンドで一つずつリソースを削除します。

# Delete all resources in QuickSight
aws quicksight delete-dashboard --aws-account-id $AAI --dashboard-id blog-glue-trigger-refresh-qs-spice-dashboard
aws quicksight delete-analysis --aws-account-id $AAI --analysis-id blog-glue-trigger-refresh-qs-spice-analysis
aws quicksight delete-data-set --aws-account-id $AAI --data-set-id blog-glue-trigger-refresh-qs-spice-dataset
aws quicksight delete-data-source --aws-account-id $AAI --data-source-id blog-glue-trigger-refresh-qs-spice-data-source

# Emptying the bucket. Without it, cdk destroy will fail.
aws s3 rm "s3://${bucket_name}" --recursive
cdk destroy --force

最後に

本記事では、AWS Glue Crawler のクロール完了後に QuickSight の SPICE を更新する構成を解説しました。
次はこれを使ってどう運用するのかを考えていきたいですね。

参考

[1] Event-driven refresh of SPICE datasets in Amazon QuickSight. (2020, October 12). Amazon Web Services. https://aws.amazon.com/blogs/big-data/event-driven-refresh-of-spice-datasets-in-amazon-quicksight/
[2] Amazon QuickSight. (n.d.). All Aspects [QuickSight Workshops]. Retrieved April 19, 2024, from https://catalog.us-east-1.prod.workshops.aws/workshops/cd8ebba2-2ef8-431a-8f72-ca7f6761713d/en-US/admin-workshop/7-content-porting