はじめに
Amazon QuickSight(注:QuickSightは、サービス全体としての名称はQuick Suiteにリブランドされましたが、本記事では機能名称を指す場合はQuickSightに表記を統一します)でダッシュボードを作成した後、それを別のAWSアカウントや別の環境に展開したいというニーズは意外と多いのではないでしょうか。例えば、開発環境で作成したダッシュボードを本番環境に展開する場合や、複数の顧客に同じダッシュボードを提供する場合などが考えられます。
今回は、AWS CLIのquicksight start-asset-bundle-export-jobコマンドとCloudFormationを使ってダッシュボードとその周辺のサービスをテンプレート化する方法と、その際に遭遇したいくつかのつまづきポイントについてご紹介します。
なお、今回サンプルとして、AWS公式のCloud Intelligence Dashboards (CID)のCUDOSダッシュボードをエクスポートして使用していますが、ここで紹介する手法はQuickSightダッシュボード全般に適用可能です。
アーキテクチャ
QuickSightのダッシュボードをテンプレート化して横展開するためには、ダッシュボード自体のテンプレート(QS形式のファイル)と、周辺サービス(Athenaワークスペース、ビューやGlue Tableなど)のテンプレート(CloudFormation等IaC)の二つの要素に分けて考える必要があります。イメージは次の図の通りです。

手順の詳細
ここからは、以下の表の流れに沿って具体的な手順を説明します。
| # | ステップ名 | 説明 |
|---|---|---|
| ① | ダッシュボードのエクスポート |
aws quicksight start-asset-bundle-export-jobコマンドを利用して、既存のQuickSightダッシュボードを.qs形式(ZIP)でエクスポートします。エクスポートされたファイルには、Dashboard、DataSet、DataSource、RefreshScheduleの情報が含まれます。 |
| ② | 手動修正作業 | エクスポートされた.qsファイルを解凍し、各JSONファイルを編集します。 具体的には、ARNやAccount IDの置換、重複した設定値の削除などを行います。 |
| ③ | ターゲット環境へのデプロイ | 必要に応じてCloudFormationで周辺リソースをデプロイした後、aws quicksight start-asset-bundle-import-jobコマンドで.qsファイルをインポートします。 |
1. ダッシュボードのエクスポート
テンプレート化したいダッシュボードはもう構築済の前提で、テンプレートとしてエクスポートするための手順を説明します。
エクスポートコマンド
CloudShellなどから下記を実行します。SOURCE_ACCOUNT_IDはテンプレート化したいダッシュボードがあるAWSのアカウントID、SOURCE_REGIONは対象のダッシュボードがあるリージョン、DASHBOARD_IDはQuickSightのダッシュボードを開いた際のURLの末尾のIDをそれぞれ指定してください。
# 環境変数の設定
SOURCE_ACCOUNT_ID="XXXXXXXXXXXX"
SOURCE_REGION="ap-northeast-1"
DASHBOARD_ID="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
JOB_ID="export-job-$(date +%Y%m%d%H%M%S)"
# QuickSightダッシュボードのエクスポート
aws quicksight start-asset-bundle-export-job \
--aws-account-id ${SOURCE_ACCOUNT_ID} \
--asset-bundle-export-job-id ${JOB_ID} \
--resource-arns \
"arn:aws:quicksight:${SOURCE_REGION}:${SOURCE_ACCOUNT_ID}:dashboard/${DASHBOARD_ID}" \
--export-format QUICKSIGHT_JSON \
--include-all-dependencies
エクスポート状態の確認
エクスポートジョブは非同期で実行されるので、しばらくしたらステータスを確認します。
aws quicksight describe-asset-bundle-export-job \
--aws-account-id ${SOURCE_ACCOUNT_ID} \
--asset-bundle-export-job-id ${JOB_ID} \
--query 'JobStatus' \
--output text
ジョブステータス:
-
QUEUED: キューに入っている -
IN_PROGRESS: 実行中 -
SUCCESSFUL: 完了(ダウンロード可能) -
FAILED: 失敗
エクスポート結果のダウンロード
# ダウンロードURLを取得してダウンロード
DOWNLOAD_URL=$(aws quicksight describe-asset-bundle-export-job \
--aws-account-id ${SOURCE_ACCOUNT_ID} \
--asset-bundle-export-job-id ${JOB_ID} \
--query 'DownloadUrl' \
--output text)
# .qsファイルとしてダウンロード
curl -o assetbundle-job-${JOB_ID}.qs "${DOWNLOAD_URL}"
echo "ダウンロード完了: assetbundle-job-${JOB_ID}.qs"
2. 手動修正作業
1.でダッシュボードをエクスポートすると、ローカルファイルに.qsファイルがあるはずです。
実はこのqsファイルは実態がzipファイルとなっており、zipとして解凍することで各種QuickSightリソースのテンプレートが出てきます。
対象のファイルはボリュームが大きく、CloudShell上での編集は辛かったので私はローカルにダウンロードしてきてマシン上のIDEで修正をかけました。
解凍
拡張子を.zipに変更し、解凍します。
修正
ダウンロードして解凍した各ファイルには、アカウントIDなどのアカウント固有の情報が入っています。
各ファイルを開き、インポート先の情報に書き換えます。
ここでの注意点は後ほど「つまづきポイント」にて詳細に説明します。
再圧縮
zipとして圧縮して、拡張子を再度.qsに変更します。
ターゲット環境上のS3へのアップロード
修正した.qsファイルを、ターゲット環境上の任意のS3バケット上にアップロードします。
3. ターゲット環境へのデプロイ
CloudFormationによる周辺リソースのデプロイ
周辺リソース(Glue, Athena)に関しては、CloudFormationのテンプレートを作成し、デプロイします。
※ テンプレートの中身については後ほどポイントを絞って説明します。
# 周辺リソースのデプロイ
aws cloudformation create-stack \
--stack-name quicksight-datasources \
--template-body file://datasource-resources.yaml \
--parameters \
ParameterKey=DataBucketName,ParameterValue=my-data-bucket \
ParameterKey=DatabaseName,ParameterValue=my_database \
--capabilities CAPABILITY_NAMED_IAM
# スタック作成完了待機
aws cloudformation wait stack-create-complete \
--stack-name quicksight-datasources
QuickSightへのインポート
QuickSightへのインポートでは、まずインポートするための定義ファイルを作成する必要があります。
アップロードした.qsファイルのURIや、ターゲットアカウント上のQuick SuiteユーザーのARN、各Quick Suiteリソースのファイル名となっているIDを入力し、定義ファイルを作成します。
{
"AssetBundleImportSource": {
"S3Uri": "s3://<アップロードした.qsファイルのS3URI>"
},
"OverridePermissions": {
"DataSources": [
{
"DataSourceIds": [
"<ダウンロードした.qsファイルを解凍した際のDataSourceフォルダ内のファイル名>"
],
"Permissions": {
"Principals": [
"arn:aws:quicksight:ap-northeast-1:yyyyyyyyyyyy:user/<権限を付与したいquicksightユーザー>"
],
"Actions": [
"quicksight:PassDataSource",
"quicksight:DescribeDataSourcePermissions",
"quicksight:UpdateDataSource",
"quicksight:UpdateDataSourcePermissions",
"quicksight:DescribeDataSource",
"quicksight:DeleteDataSource"
]
}
}
],
"DataSets": [
{
"DataSetIds": [
"<ダウンロードした.qsファイルを解凍した際のDataSetフォルダ内のファイル名>"
],
"Permissions": {
"Principals": [
"arn:aws:quicksight:ap-northeast-1:yyyyyyyyyyyy:user/<権限を付与したいquicksightユーザー>"
],
"Actions": [
"quicksight:DeleteDataSet",
"quicksight:UpdateDataSetPermissions",
"quicksight:PutDataSetRefreshProperties",
"quicksight:CreateRefreshSchedule",
"quicksight:CancelIngestion",
"quicksight:UpdateRefreshSchedule",
"quicksight:DeleteRefreshSchedule",
"quicksight:ListRefreshSchedules",
"quicksight:DescribeDataSetRefreshProperties",
"quicksight:DescribeDataSet",
"quicksight:PassDataSet",
"quicksight:CreateIngestion",
"quicksight:DescribeRefreshSchedule",
"quicksight:ListIngestions",
"quicksight:DescribeDataSetPermissions",
"quicksight:UpdateDataSet",
"quicksight:DeleteDataSetRefreshProperties",
"quicksight:DescribeIngestion"
]
}
}
],
"Dashboards": [
{
"DashboardIds": [
"<ダウンロードした.qsファイルを解凍した際のDashboardフォルダ内のファイル名>"
],
"Permissions": {
"Principals": [
"arn:aws:quicksight:ap-northeast-1:yyyyyyyyyyyy:user/<権限を付与したいquicksightユーザー>"
],
"Actions": [
"quicksight:DescribeDashboard",
"quicksight:ListDashboardVersions",
"quicksight:UpdateDashboardPermissions",
"quicksight:QueryDashboard",
"quicksight:UpdateDashboard",
"quicksight:DeleteDashboard",
"quicksight:DescribeDashboardPermissions",
"quicksight:UpdateDashboardPublishedVersion"
]
}
}
]
}
}
インポートの実行
import.jsonが準備できたら、以下のコマンドでインポートします。
# 環境変数の設定
DEST_AWS_ACCOUNTID="123456789012"
REGION="ap-northeast-1"
# Asset Bundleのインポート
aws quicksight start-asset-bundle-import-job \
--aws-account-id ${DEST_AWS_ACCOUNTID} \
--asset-bundle-import-job-id job-1 \
--region ${REGION} \
--cli-input-json file://./import.json
# インポート状況の確認
aws quicksight describe-asset-bundle-import-job \
--aws-account-id ${DEST_AWS_ACCOUNTID} \
--asset-bundle-import-job-id job-1 \
--region ${REGION}
.qsファイルの構造
エクスポートされた.qsファイルを解凍すると、以下のようなディレクトリ構造になります。
├── dashboard/
│ └── <dashboard-id>.json # ダッシュボード定義
├── dataset/
│ ├── <dataset-id-1>.json # データセット1
│ ├── <dataset-id-2>.json # データセット2
│ └── <dataset-id-3>.json # データセット3
├── datasource/
│ └── <datasource-name>.json # データソース定義
└── refreshSchedule/
└── (リフレッシュスケジュール設定があれば格納される)
各フォルダには、それぞれのリソースのJSON定義が格納されています。
これらのJSONファイルを適切に修正することで、別のAWSアカウントにインポートできるようになります。
具体例(CID CUDOSダッシュボードの場合):
- データソース:
CID-Athena.json(Athena接続) - データセット:
hourly_view.json,resource_view.json,summary_view.json - ダッシュボード: CUDOSダッシュボードの定義
つまづきポイント
ここからは、実際にこのテンプレート化を進めるにあたってつまづきやすいポイントを紹介します。
つまづきポイント1: DataSourceの修正が必要
問題点
エクスポートされたDataSourceの定義ファイル(例: datasource/<datasource-name>.json)には、エクスポート元のAccount IDやリージョンが含まれたARNが記載されています。
以下は、AthenaをDataSourceとして使用している場合の例です。
{
"resourceType": "datasource",
"dataSourceId": "CID-Athena",
"name": "CID-Athena",
"type": "ATHENA",
"dataSourceParameters": {
"athenaParameters": {
"workGroup": "CID",
"roleArn": "arn:aws:iam::xxxxxxxxxxxx:role/CidQuickSightDataSourceRole"
}
},
"sslProperties": { "disableSsl": false }
}
このまま別のAWSアカウントにインポートしようとすると、存在しないIAM Roleを参照してしまいエラーになります。
他のDataSourceタイプでも同様:
- RDS/Redshift: エンドポイント、VPC設定、認証情報のARN
- S3: バケット名、Manifestファイルのパス
- その他のデータソース: それぞれ固有のARNや設定値
解決方法
roleArnの値を、ターゲットアカウントのAccount IDに置換する必要があります。
また、同時にターゲットアカウント上で必要な権限が振られた同名のIAM Roleを用意しておく必要がある点にも注意してください。
{
"resourceType": "datasource",
"dataSourceId": "CID-Athena",
"name": "CID-Athena",
"type": "ATHENA",
"dataSourceParameters": {
"athenaParameters": {
"workGroup": "CID",
"roleArn": "arn:aws:iam::<TargetAccountID>:role/CidQuickSightDataSourceRole"
}
},
"sslProperties": { "disableSsl": false }
}
つまづきポイント2: DataSetのARN参照を修正
問題点
DataSetの定義ファイル(例: dataset/<dataset-id>.json)には、DataSourceへの参照がARN形式で記載されています。
以下は、Athena ViewをソースとするDataSetの例です。
{
"resourceType": "dataset",
"dataSetId": "78f4cc36-c46c-431b-91f0-c72e147d9bcb",
"name": "hourly_view",
"physicalTableMap": {
"e0c1051e-daef-4661-9554-42989b38effa": {
"relationalTable": {
"dataSourceArn": "arn:aws:quicksight:ap-northeast-1:xxxxxxxxxxxx:datasource/CID-Athena",
"schema": "cid_cur",
"name": "hourly_view",
...
}
}
}
}
このARNもエクスポート元のAccount IDとリージョンを含んでいるため、修正が必要です。
解決方法
Account IDとリージョンの部分を、ターゲット環境の値に置換します。
一括置換を行う場合は、以下のような処理を行います。
# エクスポート元のAccount IDとリージョン
OLD_ACCOUNT_ID="xxxxxxxxxxxx"
OLD_REGION="ap-northeast-1"
# ターゲット環境のAccount IDとリージョン
NEW_ACCOUNT_ID="123456789012"
NEW_REGION="us-east-1"
# datasetフォルダ内のすべてのJSONファイルを一括置換
cd assetbundle-job-cudos/dataset
for file in *.json; do
sed -i "s/${OLD_ACCOUNT_ID}/${NEW_ACCOUNT_ID}/g" "$file"
sed -i "s/${OLD_REGION}/${NEW_REGION}/g" "$file"
done
つまづきポイント3: Dashboardの重複したフィールドの削除
問題点
これが最も厄介な問題でした。
私の環境では、aws quicksight start-asset-bundle-export-jobコマンドを何度も実行していると、Dashboardの定義ファイル(dashboard/f24fd27a-7a3b-4161-8f11-79d5eb2a5742.json)内のselectFields句やselectedColumns配列が増殖してしまうという事象が発生しました。
具体的には、以下のようなコードが重複して記載されてしまいました。
"visuals": [
{
"tableVisual": {
"chartConfiguration": {
"fieldWells": {
"tableAggregatedFieldWells": {
"values": [
{
"numericalMeasureField": {
"fieldId": "unblended_cost.1.1234567890123",
...
}
},
{
"numericalMeasureField": {
"fieldId": "unblended_cost.1.1234567890123",
...
}
},
{
"numericalMeasureField": {
"fieldId": "unblended_cost.1.1234567890123",
...
}
}
]
}
}
}
}
}
]
この重複により、ダッシュボードのインポート時にエラーが発生したり、正常にインポートできても表示がおかしくなったりしました。
解決方法
残念ながら、この問題については手作業で修正するしかありませんでした。
重複しているfieldIdを持つオブジェクトを、テキストエディタで検索して1つだけ残すように削除します。
最近ではGitHub Copilotなど生成AIツールがあるので、重複削除してくれとお願いするとよいかもしれません。
根本原因
この増殖問題の根本原因はわかっていないのですが、私の環境ではエクスポートを何度も繰り返しているとこの事象が発生しやすいという印象を持ちました。
対策としては、エクスポートは必要最小限の回数に留めることになるのかなと思います。
また、エクスポート前にダッシュボードのクローンを作成し、クローンをエクスポートするという方法も有効かもしれません。
つまづきポイント4: 周辺リソースのデプロイ(例: CloudFormationでのAthena Viewのbase64エンコード)
問題点
QuickSightダッシュボードが依存する周辺リソース(DataSourceやデータベース等)を、ターゲット環境に事前にデプロイする必要があります。
ここでは、Athena(Glue)のViewテーブルをCloudFormationでデプロイする際の具体例を紹介しますが、他のデータソースを使用する場合も同様に事前準備が必要です。
Athena Viewの場合
Viewテーブルには、Presto形式のSQL文をbase64エンコードした文字列を設定する必要があるのですが、この作業が意外と面倒です。
SummaryViewTable:
Type: AWS::Glue::Table
Properties:
CatalogId: !Ref AWS::AccountId
DatabaseName: !Ref CidDatabase
TableInput:
Name: summary_view
TableType: VIRTUAL_VIEW
Parameters:
presto_view: "true"
comment: Presto View
ViewOriginalText: "/* Presto View: eyJvcmlnaW5hbFNxbCI6... */"
ViewExpandedText: !Sub 'SELECT ...'
ViewOriginalTextの値は、以下のようなJSON構造をbase64エンコードしたものです。
{
"originalSql": "SELECT ...",
"catalog": "awsdatacatalog",
"schema": "cid_cur",
"columns": [...]
}
解決方法
PrestoのView定義をJSONで書いておいて、それをbase64エンコードします。
PrestoのView定義例
{
"originalSql": "SELECT year, month, billing_period, payer_account_id, linked_account_id FROM cid_cur.cur WHERE bill_billing_period_start_date >= date_trunc('month', current_timestamp) - INTERVAL '7' MONTH GROUP BY 1, 2, 3, 4, 5, 6, 7, 8",
"catalog": "awsdatacatalog",
"schema": "cid_cur",
"columns": [
{"name": "year", "type": "varchar"},
{"name": "month", "type": "varchar"},
{"name": "billing_period", "type": "timestamp"},
{"name": "payer_account_id", "type": "varchar"},
{"name": "linked_account_id", "type": "varchar"},
{"name": "service", "type": "varchar"},
{"name": "product_family", "type": "varchar"},
{"name": "usage_type", "type": "varchar"},
{"name": "unblended_cost", "type": "double"},
{"name": "amortized_cost", "type": "double"}
]
}
Bashでbase64エンコード
# Bash
base64 -w 0 view.json
まとめ
AWS CLIのstart-asset-bundle-export-jobとstart-asset-bundle-import-job、そして周辺サービスをテンプレート化して利用することで、QuickSightダッシュボードの複数環境への展開が可能になりました。
ただし、実際にやってみると様々なつまづきポイントがあることが分かりました。
特に、以下の3点については事前に知っておくことで、作業時間を大幅に短縮できるかと思います。
-
DataSourceとDataSetのARN修正が必須
- Account IDとリージョンの置換を忘れずに
- 環境変数やスクリプトで自動化する
-
DashboardのselectFields/selectedColumnsの増殖問題
- エクスポート回数を最小限に抑える
-
周辺リソースの事前準備
- DataSourceに応じた周辺リソースをテンプレート化しておく
- Athena Viewの場合はbase64エンコードが必要
これらのポイントを押さえておけば、複数環境へのデプロイが必要な時に少しスムーズになるかもしれません。
本記事ではところどころ具体例としてCID CUDOSダッシュボード(Athenaベース)を使用しましたが、同様の手法は他のダッシュボードにも適用可能です。
ぜひ参考にしていただければ幸いです。