データ分析基盤を構築しよう
このハンズオンでは、Amazon RDSのデータをGlue・Athena・QuickSightを使って分析・可視化するデータ分析基盤を実際にハンズオン形式で手を動かしながら体験します。
- テスト用データベース(VPC・RDS・踏み台サーバ)の構築
- AWS Glueによるデータの抽出・変換・ロード(ETL)
- Amazon Athenaによるデータの分析
- Amazon QuickSightによるデータの可視化
1. 事前準備
この講座のハンズオンでは、以下のツールやアカウントが必要です。まだ準備できていない場合は、リンク先の手順に沿って準備をお願いします。
2. ハンズオンの概要
このハンズオンでは、Amazon RDSにあるデータをAWS Glueで抽出・変換し、Amazon S3にParquet形式で格納する、データ分析基盤としてもっともオーソドックスな方法の一つを扱います。
RDSに直接SQLを発行することで分析は可能ですが、特に本番環境においてそれは適切ではありません。分析用のSQLは複雑になるケースも多く、データベースに負荷をかけ、本番システムに影響を与える可能性があります。また、通常はSELECTのみ許可する設定にしますが、設定ミスによって不用意なアップデートが行われるリスクも存在します。
このような理由から、本番のRDSを直接参照せず、分析に特化した環境を別途構築する必要があります。
このハンズオンでは、まずCloudFormationを使ってVPC・RDS・踏み台サーバを構築し、テストデータを登録します。次に、AWS Glueを使ってRDSからデータを抽出し、Parquet形式でS3に保存します。続いて、AthenaでS3上のデータに対してSQLクエリを実行し、最後にQuickSightを使って分析結果をグラフや表で可視化します。
今回構築するデータ分析基盤の全体構成を示します。
graph LR
A["Amazon RDS<br>(データソース)"] -->|"データ抽出"| B["AWS Glue<br>(ETL処理)"]
B -->|"Parquet形式で保存"| C["Amazon S3<br>(データレイク)"]
C -->|"SQLクエリ"| D["Amazon Athena<br>(データ分析)"]
D -->|"可視化"| E["Amazon QuickSight<br>(BI)"]
style A fill:#e3f2fd,stroke:#1565c0
style B fill:#fff3e0,stroke:#e65100
style C fill:#e8f5e9,stroke:#2e7d32
style D fill:#f3e5f5,stroke:#6a1b9a
style E fill:#fce4ec,stroke:#c62828
3. テスト用データベースの作成
このセクションでは、分析対象のデータが格納されるRDSインスタンス、およびRDSを配置するためのVPC関連のリソースとして、下記内容を作成していきます。
| リソース | 名前 | 説明 |
|---|---|---|
| VPC | my-vpc | 10.0.0.0/21のアドレス範囲を持つ仮想ネットワーク |
| サブネット | db-subnet-01, db-subnet-02, api-subnet | DB用サブネット2つ(10.0.0.0/24, 10.0.1.0/24)と踏み台サーバ用サブネット(10.0.2.0/24) |
| インターネットゲートウェイ | my-internet-gateway | 踏み台サーバへのインターネットアクセス用 |
| ルートテーブル | api-route-table | api-subnetからインターネットへのルーティング |
| セキュリティグループ | db-sg, jump-sg | RDS用とEC2(踏み台サーバ)用 |
| DBサブネットグループ | my-subnet-group | RDS用のサブネットグループ |
| RDSインスタンス | testdb | MySQLデータベース |
| EC2インスタンス | JumpServer | 踏み台サーバ |
これらのリソースはデータ分析の本質ではないため、今回は説明を省略します。
また、リソースの作成についても簡略化するため、CloudFormationテンプレートにより、リソースを一括で作成します。
| 📝 CloudFormationとは |
|---|
| CloudFormationは、AWSのリソースをテンプレート(YAML/JSON)で定義し、自動的に作成・管理できるサービスです。手動でリソースを一つずつ作成する代わりに、テンプレートをアップロードするだけで必要なリソースをまとめて構築できます。 IaC( Infrastructure as Code)ツールとしてはTerraformも広く使われていますが、Terraformは事前にインストールが必要です。一方、CloudFormationはAWSマネジメントコンソールからそのまま実行できるため、今回のハンズオンではCloudFormationを使用します。なお、このハンズオンではCloudFormationテンプレートの内容を理解する必要はありません。テンプレートをそのままアップロードして環境を構築してください。 |
また、環境構築が完了した後、踏み台サーバにログインし、MySQLクライアントのインストールとテストデータの登録を手動で行います。
3.1 CloudFormationによる環境構築
CloudFormationのダッシュボードを開き、「スタックの作成」>「新しいリソースを使用(標準)」をクリックします。

その後、画面に表示されるスタックの作成をクリックします。

スタックの作成の設定を行っていきます。下記表に従い、設定を行ってください。設定後、次へをクリックしてください。
| 設定項目 | 値 | 設定の基準 |
|---|---|---|
| テンプレートの準備 | 既存のテンプレートを選択 | 事前に作成したテンプレートファイルを使用するため |
| テンプレートソース | テンプレートファイルのアップロード | ローカルのテンプレートファイルを直接アップロードするため |
| テンプレートファイルのアップロード | 以下のYAMLテンプレートを保存したファイル | ハンズオン環境を構築するテンプレートを指定するため |
アップロードするのは下記ファイルです。こちらをtemplate.ymlのような任意の名前でローカル環境に保存し、それをアップロードしてください。
AWSTemplateFormatVersion: '2010-09-09'
Description: Data Analysis Hands-on Environment
Parameters:
DBMasterPassword:
Type: String
NoEcho: true
Description: Password for RDS master user (admin)
MinLength: 8
LatestAmiId:
Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
Default: /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-6.1-x86_64
Resources:
# ==================== VPC ====================
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.0.0.0/21
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: Name
Value: my-vpc
# ==================== Subnets ====================
DBSubnet01:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.0.0/24
AvailabilityZone: ap-northeast-1a
Tags:
- Key: Name
Value: db-subnet-01
DBSubnet02:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.1.0/24
AvailabilityZone: ap-northeast-1c
Tags:
- Key: Name
Value: db-subnet-02
APISubnet:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
CidrBlock: 10.0.2.0/24
AvailabilityZone: ap-northeast-1a
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: api-subnet
# ==================== Internet Gateway ====================
InternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: my-internet-gateway
VPCGatewayAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref VPC
InternetGatewayId: !Ref InternetGateway
# ==================== Route Table ====================
PublicRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: api-route-table
PublicRoute:
Type: AWS::EC2::Route
DependsOn: VPCGatewayAttachment
Properties:
RouteTableId: !Ref PublicRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
APISubnetRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref APISubnet
RouteTableId: !Ref PublicRouteTable
# ==================== Security Groups ====================
DBSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security group for RDS
GroupName: db-sg
VpcId: !Ref VPC
Tags:
- Key: Name
Value: db-sg
JumpSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security group for Jump Server
GroupName: jump-sg
VpcId: !Ref VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: 0.0.0.0/0
Tags:
- Key: Name
Value: jump-sg
DBFromJumpIngress:
Type: AWS::EC2::SecurityGroupIngress
Properties:
GroupId: !Ref DBSecurityGroup
IpProtocol: tcp
FromPort: 3306
ToPort: 3306
SourceSecurityGroupId: !Ref JumpSecurityGroup
# ==================== DB Subnet Group ====================
DBSubnetGroup:
Type: AWS::RDS::DBSubnetGroup
Properties:
DBSubnetGroupName: my-subnet-group
DBSubnetGroupDescription: my subnet group
SubnetIds:
- !Ref DBSubnet01
- !Ref DBSubnet02
# ==================== RDS ====================
RDSInstance:
Type: AWS::RDS::DBInstance
Properties:
DBInstanceIdentifier: testdb
Engine: mysql
DBInstanceClass: db.t3.micro
AllocatedStorage: 20
MasterUsername: admin
MasterUserPassword: !Ref DBMasterPassword
DBSubnetGroupName: !Ref DBSubnetGroup
VPCSecurityGroups:
- !Ref DBSecurityGroup
PubliclyAccessible: false
AvailabilityZone: ap-northeast-1a
# ==================== EC2 (Jump Server) ====================
JumpServer:
Type: AWS::EC2::Instance
Properties:
InstanceType: t3.micro
ImageId: !Ref LatestAmiId
SubnetId: !Ref APISubnet
SecurityGroupIds:
- !Ref JumpSecurityGroup
Tags:
- Key: Name
Value: JumpServer
Outputs:
RDSEndpoint:
Description: RDS Endpoint Address
Value: !GetAtt RDSInstance.Endpoint.Address
JumpServerInstanceId:
Description: Jump Server Instance ID
Value: !Ref JumpServer
VPCId:
Description: VPC ID
Value: !Ref VPC

「次へ」をクリックし、スタックの詳細を設定します。
| 設定項目 | 値 | 設定の基準 |
|---|---|---|
| スタック名 | data-analysis-handson | ハンズオン用のスタックであることを識別するため |
| DBMasterPassword | 任意のパスワード | RDSへの接続パスワード。後ほど使用するため控えておくこと |
| LatestAmiId | デフォルト | Amazon Linux 2023の最新AMIを自動取得するため |

「次へ」をクリックし、スタックオプションはデフォルトのまま「次へ」をクリックします。

確認画面で内容を確認し、「送信」をクリックします。

そうするとCloudFormationのスタックが作成されます。ステータスがCREATE_IN_PROGRESSとなり、作成中のステータスであることがわかります。スタックの作成には10〜15分ほどかかります。

ステータスがCREATE_COMPLETEになれば、リソースの作成が完了です。

3.2 RDSへの接続先を確認
今回作成されたリソースの中から、データ分析を行う上で必要となる項目を確認しておきます。
スタックの詳細画面で「出力」タブをクリックしてください。以下の値が表示されます。
| キー | 説明 | 用途 |
|---|---|---|
| RDSEndpoint | RDSインスタンスのエンドポイントアドレス | テストデータの登録やGlue Connectionの作成時に使用 |

また、RDSのコンソールから、testdbの詳細画面でも確認ができます。

また、RDSへの接続には以下の情報も必要です。これらはCloudFormationのスタック作成時に設定した値です。
| 項目 | 値 | 確認方法 |
|---|---|---|
| ユーザ名 | admin | CloudFormationテンプレートで固定値として設定済み |
| パスワード | スタック作成時に指定したDBMasterPassword | CloudFormationの画面には表示されないため、設定時に控えた値を使用 |
| ポート | 3306 | MySQLのデフォルトポート |
これらの値は後のステップで繰り返し使用するため、テキストエディタなどに控えておいてください。
3.3 データの登録
続いて、作成したRDSインスタンスに、テストデータを登録していきます。RDSにアクセスするための踏み台サーバとなるEC2インスタンスを作成しているので、これを使ってRDSインスタンスに接続し、データを登録していきます。
EC2インスタンスにログイン
まずはEC2インスタンスから、先程CloudFormationで作成したJumpServerをチェックし、接続をクリックします。

その後、デフォルトの状態で構いませんので、接続をクリックします。

以下のように接続完了の画面が表示されれば、無事にEC2インスタンスにログインできています。

| 📝 EC2 Instance Connectとは |
|---|
| EC2 Instance Connectとは、AWSマネジメントコンソールのブラウザ上から直接EC2インスタンスにSSH接続できる機能です。ローカルPCにSSHクライアントをインストールしたり、秘密鍵ファイルを管理したりする必要がなく、ブラウザだけで簡単にサーバへアクセスできます。Amazon Linux 2023やUbuntuなど、対応するAMIで利用可能です。 |
3.4 MySQLクライアントのインストール
ログインができたら、MySQLクライアントをインストールします。以下のコマンドによりMySQLクライアントをインストールします。
sudo dnf install -y mariadb105
3.5 MySQLへの接続
インストールが終わったら、RDSに接続します。以下のコマンドで接続が可能です。RDSのエンドポイントは、先程メモした、CloudFormationで作成されたRDSのエンドポイントに置き換えます。
mysql -h RDSのエンドポイント -u admin -p
パスワードの入力を求められるので、こちらもCloudFormation実行時にパラメータとして指定した、RDSのパスワードを入力します。
MariaDB [(none)]>のような待受状態になれば、無事にRDSインスタンスに接続できています。
テストデータの登録
SQLを実行し、テストデータを登録していきます。
まずは、datalake_testというデータベースを作成します。
CREATE DATABASE datalake_test;
続いて、usersというテーブルを作ります。
CREATE TABLE datalake_test.users (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
age INT,
gender INT
);
さらに、usersに10件程度のレコードを追加します。
INSERT INTO datalake_test.users (name, age, gender) VALUES
('Yamada Taro', 28, 1),
('Suzuki Hanako', 25, 2),
('Sato Kenji', 34, 1),
('Tanaka Yuki', 22, 2),
('Kobayashi Shota', 31, 1),
('Watanabe Aiko', 29, 2),
('Ito Naoki', 40, 1),
('Nakamura Mika', 27, 2),
('Kato Kenta', 36, 1),
('Yamamoto Rina', 33, 2);
最後に、無事にデータが登録されているかを、SELECTにより確認します。
SELECT * FROM datalake_test.users;
以下のように10件のレコードが表示されれば、ここまでは成功です。
+----+-----------------+------+--------+
| id | name | age | gender |
+----+-----------------+------+--------+
| 1 | Yamada Taro | 28 | 1 |
| 2 | Suzuki Hanako | 25 | 2 |
| 3 | Sato Kenji | 34 | 1 |
| 4 | Tanaka Yuki | 22 | 2 |
| 5 | Kobayashi Shota | 31 | 1 |
| 6 | Watanabe Aiko | 29 | 2 |
| 7 | Ito Naoki | 40 | 1 |
| 8 | Nakamura Mika | 27 | 2 |
| 9 | Kato Kenta | 36 | 1 |
| 10 | Yamamoto Rina | 33 | 2 |
+----+-----------------+------+--------+
10 rows in set (0.000 sec)
4. データの抽出と格納
このセクションでは、テスト用のRDSからデータを抽出し、S3に格納するまでの一連のETL処理を行います。具体的には、データを格納するS3バケット、Glueに権限を与えるIAMロール、Glue用のサブネット、S3に接続するためのエンドポイントなどの下準備を行った後、Glueの設定を行いETL処理を実行します。
4.1 S3バケットの作成
まずは、データを格納するためのS3バケットを事前に作成していきます。
バケットの作成
S3のダッシュボードから、バケットの作成をクリックします。

バケット名は任意ですが、今回はglue-data-<アカウントID>とします。<アカウントID>の部分はご自身のAWSアカウントIDに置き換えてください。これは、S3バケットは全世界で一意になる必要があるためです。

それ以外の項目は今回デフォルトで構いませんので、バケットを作成をクリックします。

無事にS3バケットが作成されればここまでの操作は完了です。

フォルダの作成
続いて、データを格納するためのフォルダを作成します。
先ほど作成したバケットをクリックします。

次に、フォルダの作成をクリックします。

フォルダ名にrawdataと入力し、「フォルダの作成」をクリックします。

無事にフォルダが作成されたら、続けてrawdataフォルダの中に入り、さらにusersフォルダを作成します。同じ手順で「フォルダの作成」をクリックし、フォルダ名にusersと入力して作成してください。
最終的に、以下のようなフォルダ構成になっていれば完了です。
s3://glue-data-(識別子)/
└── rawdata/
└── users/

| 💡 ポイント |
|---|
rawdataは「生データ(未加工データ)」を意味します。データ分析基盤では、データソースから抽出したデータをそのままの形で保存する領域としてrawdataフォルダを設けるのが一般的です。生データを加工せずに保持しておくことで、後から別の変換ロジックを適用したり、加工ミスがあった場合に元データから再処理したりすることが可能になります。rawdataの下にはテーブルごとのフォルダ(users、ordersなど)を作成し、データを分けて管理します。実務ではrawdataの他に、加工済みデータを格納するprocessedや、分析用に整形したデータを格納するcuratedなどのフォルダを用途ごとに分けて管理します。 |
4.2 IAMの作成
続いて、Glueに割り当てるためのIAMロールを作成します。GlueはETLジョブの実行時に、割り当てられたIAMロールの権限を使ってAWSリソースを操作します。今回はGlueがRDSからデータを読み取り、S3に書き込む必要があるため、これらのアクセスを許可するIAMポリシーを作成し、それをIAMロールに紐づけます。
IAMポリシーの作成
まずは、IAMロールに割り当てるためのIAMポリシーを作成していきます。
IAMのダッシュボードを開き、左側のメニューから「ポリシー」を選択し、「ポリシーの作成」をクリックします。

アクセス許可の指定では「JSON」を選択し、以下のJSONを貼り付けて「次へ」をクリックします。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"glue:*",
"rds:DescribeDBInstances",
"ec2:*",
"s3:PutObject",
"s3:PutObjectAcl",
"s3:GetObject",
"s3:ListBucket",
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "*"
}
]
}
このポリシーの内容を簡単に表にまとめています。
| アクション | 許可する内容 | 必要な理由 |
|---|---|---|
glue:* |
Glueの全操作 | ジョブの実行やデータカタログの操作を行うため |
rds:DescribeDBInstances |
RDSインスタンス情報の参照 | 接続先RDSのエンドポイントなどの情報を取得するため |
ec2:* |
EC2関連リソースの全操作 | GlueをVPC内で実行する際にENI(ネットワークインターフェース)を作成・管理するため |
s3:PutObject / s3:PutObjectAcl |
S3へのオブジェクトの書き込み | 抽出・変換したデータをS3に保存するため |
s3:GetObject / s3:ListBucket |
S3からのオブジェクトの読み取り・一覧取得 | S3上のデータやETLスクリプトを読み取るため |
logs:CreateLogGroup / CreateLogStream / PutLogEvents |
CloudWatch Logsへのログ出力 | Glueジョブの実行ログを記録・確認できるようにするため |

ポリシー名にglue-test-policyと入力し、「ポリシーの作成」をクリックします。

IAMポリシーが作成されれば、ここまでの操作は完了です。

IAMロールの作成
続いて、Glueに設定するためのIAMロールを作成します。
IAMのダッシュボードにて、左側のメニューから「ロール」を選択し、「ロールを作成」をクリックします。

信頼されたエンティティタイプにはAWSのサービスを選択し、次へをクリックします。

許可ポリシーでは、先程作成したglue-test-policyを選択し、次へをクリックします。

ロール名にはglue-test-roleを入力し、ロールの作成をクリックします。

IAMロールが作成されれば、ここまでの操作は完了です。

4.3 Glue用サブネットの作成
続いて、Glueを配置するためのサブネットを作成します。今回はRDSからのデータ取得を行うため、GlueをVPC内のサブネットに配置する必要があります。
| 💡ポイント |
|---|
| Glueは、データソースがVPC内にある場合(RDS、Redshift、ElastiCacheなど)にのみサブネットへの配置が必要です。VPC内のリソースはインターネットから直接アクセスできないため、Glue自身も同じVPC内に配置して通信する必要があります。一方、S3やDynamoDBなどパブリックなエンドポイントを持つサービスのみを扱う場合は、サブネットへの配置は不要です。 |
VPCのダッシュボードを開き、左側のメニューから「サブネット」をクリックし、「サブネットを作成」をクリックします。

下記の内容を設定してください。
| 設定項目 | 値 | 設定の基準 |
|---|---|---|
| VPC | my-vpc | RDSが配置されているVPCを指定するため |
| サブネット名 | glue-subnet | Glue用のサブネットであることを識別するため |
| アベイラビリティーゾーン | ap-northeast-1a | RDSと同じAZに配置するため |
| IPv4サブネットCIDRブロック | 10.0.3.0/24 | 他のサブネットと重複しない範囲を指定するため |
入力が完了したらサブネットを作成をクリックします。

サブネットが作成されれば、ここまでの操作は完了です。

4.4 Glue用セキュリティグループの作成
続いて、Glueに設定するためのセキュリティグループを作成します。GlueをVPC内のサブネットに配置する場合、セキュリティグループの割り当てが必須です。このセキュリティグループによって、Glueがどのリソースと通信できるかが制御されます。
セキュリティグループの作成
Glue用のセキュリティグループを作成します。
VPCのダッシュボードを開き、左側のメニューから「セキュリティグループ」を選択し、「セキュリティグループを作成」をクリックします。

下記の内容を設定してください。
| 設定項目 | 値 | 設定の基準 |
|---|---|---|
| セキュリティグループ名 | glue-sg | Glue用であることを識別するため |
| VPC | my-vpc | 作成したVPC内のリソースに適用するため |
なお、インバウンドルールは今回少し特殊な設定を行うため、一度セキュリティグループを作った後に再設定する必要があります。
入力が完了したら「セキュリティグループを作成」をクリックします。

セキュリティグループが作成されれば、ここまでの操作は完了です。

インバウンドルールの編集
続いて、インバウンドルールの設定を行います。
インバウンドルールの編集をクリックします。

まずは設定を行いながら、その設定の説明を行います。
ルールを追加をクリックし、タイプからすべてのトラフィックをクリックします。
| 💡 ポイント |
|---|
Glueジョブは内部的にApache Sparkで動作しており、複数のワーカーノードがタスクの分散・シャッフル・集約などさまざまな通信を行います。これらの通信は動的にポートが割り当てられるため、特定のポートだけを許可することができません。そのため、同一セキュリティグループ内に限定した上で「すべてのトラフィック」を許可しています。ソースに自分自身のセキュリティグループ(glue-sg)を指定することで、外部からのアクセスは遮断しつつ、ワーカー間の通信のみを許可する安全な設定になっています。 |
続いてソースを設定します。ここでは自分自身のセキュリティグループ(glue-sg)を選択します。
| 💡 ポイント |
|---|
ソースに自分自身のセキュリティグループを指定する設定を自己参照(セルフリファレンス) と呼びます。これは「同じセキュリティグループに属するリソース同士の通信を許可する」という意味です。Glueジョブは複数のワーカーノードが並列で起動しますが、すべてのワーカーが同じglue-sgに属しているため、この自己参照ルールによってワーカー同士が互いに通信できるようになります。 |
入力が完了したらルールを保存をクリックします。

無事にインバウンドルールの更新が完了したら、ここまでの操作は成功です。

4.5 RDSのセキュリティグループを更新
続いて、RDSのセキュリティグループを更新します。GlueからRDSに接続してデータを取得するためには、RDSのセキュリティグループに対してGlueのセキュリティグループからの通信を許可する必要があります。
VPCのダッシュボードにて、左側のメニューから「セキュリティグループ」を選択し、db-sgのセキュリティグループIDのリンクをクリックします。なお、このdb-sgは、先程CloudFormationで作ったセキュリティグループです。

続いて、インバウンドルールを編集をクリックします。

ルールを追加をクリックし、タイプにMySQL/Aurora、ソースにglue-sgを指定します。これにより、RDSに対して、Glueからデータの参照を行うことができるようになります。
入力が完了したらルールを保存をクリックします。

無事にインバウンドルールの更新が完了したら、ここまでの操作は成功です。

4.6 S3エンドポイントの作成
続いて、S3エンドポイントを作成します。GlueはPrivate Subnetに配置するため、インターネット経由でS3にアクセスすることはできません。そのため、VPCエンドポイント(Gateway型)を作成し、GlueからS3へアクセスできるようにします。
VPCのダッシュボードを開き、左側のメニューから「エンドポイント」をクリックし、「エンドポイントを作成」をクリックします。

下記の内容を設定してください。
| 設定項目 | 値 | 設定の基準 |
|---|---|---|
| 名前タグ | glue-endpoint | エンドポイントを識別するため |
| サービスカテゴリ | AWSのサービス | AWS提供のサービスに接続するため |
| サービス | com.amazonaws.ap-northeast-1.s3(Gateway) | S3にGateway型で接続するため |
| VPC | my-vpc | 作成したVPC内で使用するため |
| ルートテーブル | glue-subnetが使用しているルートテーブル(VPCのメインルートテーブル) |
Glueサブネットからの通信がS3エンドポイントを経由するようにするため |
| ポリシー | フルアクセス | S3への読み書きを許可するため |
| 💡 ポイント |
|---|
glue-subnetにはカスタムルートテーブルを明示的に関連付けていないため、VPCのメインルートテーブルが使用されています。ルートテーブルの一覧でapi-route-tableではなく、名前が設定されていない方のルートテーブル(VPCのメインルートテーブル)を選択してください。誤ったルートテーブルを選択すると、GlueからS3へのテスト接続時に「Could not find S3 endpoint or NAT gateway...」というエラーが発生します。 |
入力が完了したらエンドポイントを作成をクリックします。

VPCエンドポイントが作成されれば、ここまでの操作は完了です。

4.7 Data Connectionの作成
続いて、GlueのConnectionを作成します。Connectionは、GlueからRDSなどの外部データベースに接続するための設定です。接続先のエンドポイントやポート、認証情報、利用するVPCのサブネットやセキュリティグループなどを指定します。
Connectionの作成
Glueのダッシュボードを開き、左側のメニューからData connectionsをクリックし、Create connectionをクリックします。

「Choose data source」でjdbcを選択し、Nextをクリックします。

「Configure connection」に下記の内容を設定してください。
| 設定項目 | 値 | 設定の基準 |
|---|---|---|
| JDBC URL | jdbc:mysql://(RDSのエンドポイント):3306/datalake_test | RDSのdatalake_testデータベースに接続するため |
| Credential type | Username and password | ユーザ名とパスワードで認証するため |
| Username | admin | RDS作成時に指定したマスターユーザ名 |
| Password | (RDSのパスワード) | RDS作成時に指定したパスワード |
| VPC | my-vpc | RDSが配置されているVPCを指定するため |
| Subnet | glue-subnet | Glue用に作成したサブネットを指定するため |
| Security groups | glue-sg | Glue用に作成したセキュリティグループを指定するため |

nameはglue-connectionと入力し、次へをクリックします。

確認画面が表示されるため、問題なければcreate connectionをクリックします。

接続確認
続いて、作成したglue-connectionの動作確認を行っていきます。
glue-connectionをチェックし、ActionからTest Connectionを選択します。

ここで、Glueの実行で使用するIAM Roleの選択が求められます。ここでは、先程作成したglue-test-roleを選択し、Confirmをクリックします。

しばらく経過すると、以下のような接続成功の表示がされればOKです。

4.8 ETLジョブの作成と実行
それではいよいよ、GlueによるETL処理を作成していきます。RDSからデータを抽出(Extract)し、Parquet形式に変換(Transform)したうえで、S3に格納(Load)する一連の処理を設定します。
ETLジョブの作成
まずはETLジョブを作成する画面を開きます。
Glueのダッシュボードにて、左側のメニューからVirtual ETLを選択し、表示されるメニューからVirtual ETLを選択します。

入力(ソース)を設定
続いて、入力(ソース)を指定していきます。今回は、CloudFormationで作成したRDSインスタンスを使用します。
Add Nodeのボタン(+で表記されていると思います)を選択し、MySQLを選択します。ここで、MySQLにはSourceとTargetの二つが該当するので、間違いなくSourceの方を選んでください。Targetの方は、出力で使用するものなので、設定が異なります。

左側にプロパティメニューが表示されるので、下記の内容を設定してください。
| 設定項目 | 値 | 設定の基準 |
|---|---|---|
| JDBC source | JDBC connection details | JDBC接続を使用するため |
| Connection name | glue-connection | 先ほど作成したConnectionを使用するため |
| Table name | users | テストデータを登録したテーブルを指定するため |
| IAM Role | glue-test-role | Glueに権限を付与するため |

数分経過すると、テーブルの値がプレビューされます。

出力先(ターゲット)を設定
続いて、出力先となるS3バケットを指定していきます。
Add nodesからS3を選択します。このときも、SOURCEとTARGETで二つのS3があるため、TARGETの方を選択してください。

左側にプロパティメニューが表示されるので、各項目を設定していきます。
まず、Node parentsにMySQLを選択します。これにより、先ほど設定した入力ノード(RDSのusersテーブル)からデータを受け取る構成になります。
次に、FormatでParquetを選択します。Parquetは列指向のファイル形式で、圧縮効率が高く分析処理に適しているため、データレイクへの格納形式として広く使われています。
最後に、S3 Target Locationを設定します。ここで、先ほど作成したrawdata/usersフォルダを指定してください。パスはs3://glue-data-(識別子)/rawdata/users/の形式になります。このフォルダにRDSから抽出・変換されたデータがParquet形式で保存されます。
| 💡 ポイント |
|---|
S3 Target Locationでは、必ずrawdata/users/フォルダを指定してください。rawdata/の下にテーブルごとのフォルダを分けておくことで、後からテーブルが増えた場合もクローラがrawdata/配下を一括スキャンするだけで、テーブルごとに自動でデータカタログを作成してくれます。 |
入力が終わったら、ジョブの名前にtest-jobと入力したうえで、Saveをクリックして変更内容を保存します。

無事にジョブが作成されれば、ここまでは成功です。

ETLの実行
続いて、実際にETL処理を実行していきます。
まず、Runsのタブをクリックし、そこに表示されるRunをクリックします。

実行が開始されると、Run StatusがRunningとなります。実行完了までしばらく待ちます。

実行が完了したら、Run statusがSucceededとなります。

最後に、先程作ったS3バケットのrawdata/usersフォルダを開くと、parquet拡張子のデータがいくつか作成されていることが確認できます。

ここまでで、Glueを用いてRDSからデータを抽出し、S3にParquet形式で格納する処理が完了しました。
5. データの分析
このセクションでは、S3バケットに保存されたParquet形式のデータをAthenaから参照できるようにします。Athena上でテーブルを作成し、S3上のデータに対してSQLクエリを実行できるようにするまでの手順を説明します。
5.1 データカタログの設定(クローラの作成と実行)
Athenaでテーブルを作成する方法はいくつかありますが、今回はGlueクローラを利用する方法で進めます。Glueクローラは、S3などのデータソースを自動的にスキャンし、データのスキーマ(カラム名やデータ型)を検出して、Glueデータカタログにテーブル定義を自動作成する機能です。
| 📝 Glueクローラとは |
|---|
| Glueクローラは、指定したデータソース(S3、RDSなど)を自動的にスキャンし、データの構造(スキーマ)を検出してGlueデータカタログにテーブル定義を作成・更新する機能です。手動でカラム名やデータ型を定義する必要がなく、データの形式を自動で判別してくれるため、大量のテーブルを扱う場合やスキーマの変更が頻繁な場合に特に有用です。 |
クローラの作成
Glueのダッシュボードを開き、左側のメニューから「Crawlers」を選択し、「Create crawler」をクリックします。

クローラ名にtest-crawlerと入力し、「Next」をクリックします。

データソースの設定
「Add a data source」をクリックし、下記の内容を設定してください。

| 設定項目 | 値 | 設定の基準 |
|---|---|---|
| Data source | S3 | S3上のデータを読み取るため |
| S3 path | s3://glue-data-(識別子)/rawdata/users/ | ETL処理で出力したS3パスを指定するため |
入力が終わったら、Add an S3 data sourceをクリックしてください。

入力が問題なければ、Nextをクリックしてください。

IAMロールの設定
Crawlerの実行で利用するIAMロールを設定します。今回は、事前に作成しているglue-test-roleを使用します。
選択が終わったらNextをクリックします。

出力先の設定
続いて、対象となるデータベースをクリックします。今回は、Athenaで最初から作られているdefaultデータベースを利用します。
Crawler scheduleについて、時刻起動など設定できますが、今回は手動で実行するOn demandを選択します。
入力が終わったらNextをクリックします。

確認画面が表示されるので、問題なければCreate crawlerをクリックします。

クローラの実行

作成されたCrawlerにて、Run crawlerボタンをクリックし、Crawlerを実行します。

数分経過すると、クローラの実行が完了し、StatusがCompletedになります。

テーブルの確認
左側のメニューから「Data Catalog」>「Tables」を開き、usersテーブルが自動作成されていることを確認します。テーブルをクリックすると、クローラが自動検出したスキーマ(カラム名・データ型)を確認できます。

5.2 Athenaでデータを参照
Athenaのダッシュボードを開きます。テーブル一覧の中に、先ほど作成したusersテーブルが表示されています。

右側の三点メニューから「テーブルをプレビュー」をクリックします。テーブルに保存されているデータが画面上に表示されることを確認できます。

以上で、Athenaを利用してS3に保存されたParquet形式のデータを参照する設定は完了です。
6. データの可視化
このセクションでは、Athenaで取得したデータをQuickSightで可視化します。QuickSightは、AWSが提供するBIツールであり、クラウド上に保存されたデータをもとにダッシュボードやレポートを簡単に作成できるサービスです。
詳細はQuickSightの料金ページをご確認ください。料金が気になる場合は、このセクションはスキップしても問題ありません。
6.1 QuickSightの登録
サインアップ
QuickSightのダッシュボードを開き、「QUICKSIGHTにサインアップ」をクリックします。

下記の内容を設定してください。
| 設定項目 | 値 | 設定の基準 |
|---|---|---|
| メールアドレス | 任意のメールアドレス | QuickSightの通知先として使用するため |
| 認証方法 | デフォルト | 標準の認証方式を使用するため |
| QuickSightリージョン | Asia Pacific (Tokyo) | 東京リージョンで利用するため |
| QuickSightアカウント名 | test-quicksight | アカウントを識別するため |

続いて、アクセス権限の設定を行います。IAMロールについてはデフォルトを選択し、自動検出を許可するサービスとしてIAM、Amazon S3、Amazon Athenaを有効にします。

QuickSightがアクセスするS3バケットを選択します。先ほどGlueからのデータを保存したglue-data-(識別子)を指定します。

注意: 「ピクセルパーフェクトレポートを追加」は必ずチェックを外してください。チェックを外さないと500ドル程度の料金が発生するため、注意が必要です。
すべての入力が完了したら「完了」をクリックします。

しばらく経過するとアカウントが作成されるので、「QUICKSIGHTに移動」をクリックします。

新しい分析を作成
QuickSightではAthenaなどのデータソースを利用できます。ここではAthenaをデータソースとして設定し、データの可視化を行えるようにします。
左側のメニューから「分析」をクリックします。

「新しいデータセット」をクリックします。

データソースとして「Athena」を選択します。

データソース名にquicksight-testと入力し、「データソースを作成」をクリックします。

下記の内容を設定してください。
| 設定項目 | 値 | 設定の基準 |
|---|---|---|
| カタログ | AwsDataCatalog | Glueデータカタログを参照するため |
| データベース | default | テーブルを登録したデータベースを指定するため |
| テーブル | users | 分析対象のテーブルを指定するため |
選択が完了したら「選択」をクリックします。

「データクエリを直接実行」を選択し、「Visualize」をクリックします。

| 💡 ポイント |
|---|
| 「データクエリを直接実行」は、可視化するたびにAthenaへクエリを投げて結果を取得する方式です。「迅速な分析のためにSPICEインポート」を選ぶと、データがQuickSight内の高速ストレージに取り込まれ、パフォーマンスが向上します。 |
6.2 データの分析
分析一覧の中にusers analysisという分析が表示されます。こちらをクリックしてください。

データセットからフィールドを選択することで、円グラフや棒グラフ、テーブルなど任意の可視化を作成できます。以下の画像はgender項目を使って円グラフを作成した例です。
ぜひ、いろいろな分析を試してみましょう。

7. 不要リソースの削除
ハンズオンが完了したら、不要なコストが発生しないよう、作成したリソースを削除してください。CloudFormationで作成したリソース以外を先に削除し、最後にCloudFormationスタックを削除します。
- QuickSight — アカウントの削除(QuickSightの管理画面から「アカウントの削除」を実行)
- Glueジョブ — 作成したETLジョブ(test-job)を削除
- Glueクローラ — test-crawlerを削除
- Glue Connection — glue-connectionを削除
- Glueデータカタログ — usersテーブルを削除
- S3バケット — glue-data-(識別子)バケットを空にしてから削除
- VPCエンドポイント — glue-endpointを削除
- Glue用リソース — glue-subnetとglue-sgを削除
- IAMロール・ポリシー — glue-test-roleとglue-test-policyを削除
- CloudFormationスタック — data-analysis-handsonスタックを削除(VPC、RDS、EC2などが一括で削除されます)
注意: 特にQuickSightとRDSは月額料金が発生するため、忘れずに削除してください。CloudFormationスタックの削除前に、スタック外で作成したリソース(手順1〜9)を必ず先に削除してください。
8. まとめ
このハンズオンでは、AWS Glue・Athena・QuickSightを使ったデータ分析基盤の構築を体験しました。
- CloudFormationでVPC・RDS・EC2の環境をテンプレートから一括構築した
- 踏み台サーバからRDSに接続し、SQLでテストデータを登録した
- IAMロール・セキュリティグループでGlueに必要な権限とネットワーク設定を行った
- AWS GlueのVisual ETLでRDSからデータを抽出し、Parquet形式でS3に格納した
- GlueクローラでS3上のデータを自動スキャンし、データカタログにテーブル定義を自動作成した
- Amazon AthenaでS3上のParquetデータにSQLクエリを実行した
- Amazon QuickSightでAthenaのデータを円グラフや表で可視化した
9. (参考)Terraformで構築する場合
このハンズオンで構築した環境は、Terraformを使ってコードで管理することもできます。以下に、VPC・RDS・EC2を含むすべてのリソースをTerraformで構築するサンプルコードを示します。
注意: QuickSightのセットアップとテストデータの登録(MySQLクライアントのインストール・SQLによるデータ投入)は、Terraformの範囲外のため手動で行う必要があります。
9.1 Terraformコード
以下のコードをmain.tfとして保存してください。
# ==============================================================
# Provider(プロバイダ設定)
# Terraformが使用するAWSプロバイダのバージョンとリージョンを指定
# ==============================================================
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.0"
}
}
}
provider "aws" {
region = "ap-northeast-1" # 東京リージョン
}
# ==============================================================
# Variables(入力変数)
# terraform.tfvars または実行時に値を指定する
# ==============================================================
# RDSのマスターパスワード(sensitive = true でログに表示されない)
variable "db_master_password" {
type = string
sensitive = true
description = "RDS master user password"
}
# S3バケット名の末尾に付けるAWSアカウントID(バケット名はグローバルで一意にする必要があるため)
variable "s3_bucket_suffix" {
type = string
description = "AWS account ID for S3 bucket suffix (e.g. 123456789012)"
}
# ==============================================================
# VPC(仮想ネットワーク)
# すべてのリソースを配置するネットワーク基盤
# ==============================================================
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/21"
enable_dns_support = true # VPC内でDNS解決を有効化
enable_dns_hostnames = true # DNSホスト名の自動割り当てを有効化
tags = { Name = "my-vpc" }
}
# ==============================================================
# Subnets(サブネット)
# 用途ごとにネットワークを分割する
# ==============================================================
# RDS用サブネット1(AZ-a)- RDSのマルチAZ対応のため2つ作成
resource "aws_subnet" "db_01" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.0.0/24"
availability_zone = "ap-northeast-1a"
tags = { Name = "db-subnet-01" }
}
# RDS用サブネット2(AZ-c)- DBサブネットグループには2つ以上のAZが必要
resource "aws_subnet" "db_02" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.1.0/24"
availability_zone = "ap-northeast-1c"
tags = { Name = "db-subnet-02" }
}
# 踏み台サーバ(EC2)用のパブリックサブネット
resource "aws_subnet" "api" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.2.0/24"
availability_zone = "ap-northeast-1a"
map_public_ip_on_launch = true # パブリックIPを自動付与(EC2 Instance Connect用)
tags = { Name = "api-subnet" }
}
# Glue用のプライベートサブネット(RDSと同じAZに配置)
resource "aws_subnet" "glue" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.3.0/24"
availability_zone = "ap-northeast-1a"
tags = { Name = "glue-subnet" }
}
# ==============================================================
# Internet Gateway / Route Table(インターネット接続設定)
# 踏み台サーバからインターネットへの通信経路を確保する
# ==============================================================
# インターネットゲートウェイ(VPCとインターネットの接続口)
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = { Name = "my-internet-gateway" }
}
# パブリックルートテーブル(0.0.0.0/0 → IGWへルーティング)
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.main.id
}
tags = { Name = "api-route-table" }
}
# api-subnetにパブリックルートテーブルを関連付け
resource "aws_route_table_association" "api" {
subnet_id = aws_subnet.api.id
route_table_id = aws_route_table.public.id
}
# ==============================================================
# Security Groups(セキュリティグループ)
# リソースごとに通信を制御するファイアウォール設定
# ==============================================================
# --- RDS用セキュリティグループ ---
# インバウンドルールは別途 aws_security_group_rule で追加
resource "aws_security_group" "db" {
name = "db-sg"
description = "Security group for RDS"
vpc_id = aws_vpc.main.id
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = { Name = "db-sg" }
}
# --- 踏み台サーバ用セキュリティグループ ---
# SSH(ポート22)を許可し、EC2 Instance Connectでのログインを可能にする
resource "aws_security_group" "jump" {
name = "jump-sg"
description = "Security group for Jump Server"
vpc_id = aws_vpc.main.id
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = { Name = "jump-sg" }
}
# --- Glue用セキュリティグループ ---
resource "aws_security_group" "glue" {
name = "glue-sg"
description = "Security group for Glue"
vpc_id = aws_vpc.main.id
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = { Name = "glue-sg" }
}
# Glueの自己参照ルール: ワーカーノード間の通信を許可
# Glue(Spark)は複数ワーカーが並列動作するため、同一SG内の全通信を許可する
resource "aws_security_group_rule" "glue_self" {
type = "ingress"
from_port = 0
to_port = 0
protocol = "-1"
source_security_group_id = aws_security_group.glue.id
security_group_id = aws_security_group.glue.id
}
# RDSへの接続許可: 踏み台サーバ → RDS(MySQL 3306番ポート)
resource "aws_security_group_rule" "db_from_jump" {
type = "ingress"
from_port = 3306
to_port = 3306
protocol = "tcp"
source_security_group_id = aws_security_group.jump.id
security_group_id = aws_security_group.db.id
}
# RDSへの接続許可: Glue → RDS(MySQL 3306番ポート)
resource "aws_security_group_rule" "db_from_glue" {
type = "ingress"
from_port = 3306
to_port = 3306
protocol = "tcp"
source_security_group_id = aws_security_group.glue.id
security_group_id = aws_security_group.db.id
}
# ==============================================================
# RDS(データベース)
# 分析対象のテストデータを格納するMySQLインスタンス
# ==============================================================
# DBサブネットグループ(RDSの配置先サブネットを指定)
resource "aws_db_subnet_group" "main" {
name = "my-subnet-group"
subnet_ids = [aws_subnet.db_01.id, aws_subnet.db_02.id]
tags = { Name = "my-subnet-group" }
}
# RDSインスタンス本体
resource "aws_db_instance" "main" {
identifier = "testdb"
engine = "mysql"
instance_class = "db.t3.micro"
allocated_storage = 20 # ストレージ容量(GB)
username = "admin"
password = var.db_master_password
db_subnet_group_name = aws_db_subnet_group.main.name
vpc_security_group_ids = [aws_security_group.db.id]
publicly_accessible = false # プライベートサブネットに配置
availability_zone = "ap-northeast-1a"
skip_final_snapshot = true # 削除時にスナップショットを作成しない(ハンズオン用)
}
# ==============================================================
# EC2(踏み台サーバ)
# RDSにログインしてテストデータを登録するために使用
# ==============================================================
# SSM Parameter Storeから最新のAmazon Linux 2023のAMI IDを取得
data "aws_ssm_parameter" "ami" {
name = "/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-6.1-x86_64"
}
resource "aws_instance" "jump" {
ami = data.aws_ssm_parameter.ami.value
instance_type = "t3.micro"
subnet_id = aws_subnet.api.id
vpc_security_group_ids = [aws_security_group.jump.id]
tags = { Name = "JumpServer" }
}
# ==============================================================
# S3 Bucket(データレイク用ストレージ)
# GlueのETL処理で抽出・変換したデータの保存先
# ==============================================================
# Glueの出力先バケット
resource "aws_s3_bucket" "glue_data" {
bucket = "glue-data-${var.s3_bucket_suffix}"
}
# rawdata/users/ フォルダ(プレフィックス)を作成
# ETL処理の出力先として使用する
resource "aws_s3_object" "rawdata_folder" {
bucket = aws_s3_bucket.glue_data.id
key = "rawdata/users/"
}
# ==============================================================
# IAM(Glue用の権限設定)
# GlueがRDS・S3・CloudWatch Logsなどにアクセスするための権限
# ==============================================================
# Glue用のIAMポリシー
resource "aws_iam_policy" "glue" {
name = "glue-test-policy"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"glue:*", # Glueの全操作(ジョブ実行、データカタログ操作)
"rds:DescribeDBInstances", # RDSの接続情報を参照
"ec2:*", # VPC内でENI(ネットワークインターフェース)を管理
"s3:PutObject", # S3にデータを書き込み
"s3:PutObjectAcl", # S3オブジェクトのACLを設定
"s3:GetObject", # S3からデータを読み取り
"s3:ListBucket", # S3バケットの一覧取得
"logs:CreateLogGroup", # CloudWatch Logsにロググループを作成
"logs:CreateLogStream", # CloudWatch Logsにログストリームを作成
"logs:PutLogEvents" # CloudWatch Logsにログを出力
]
Resource = "*"
}
]
})
}
# Glue用のIAMロール(信頼ポリシーでGlueサービスからの引き受けを許可)
resource "aws_iam_role" "glue" {
name = "glue-test-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Principal = { Service = "glue.amazonaws.com" }
Action = "sts:AssumeRole"
}
]
})
}
# IAMポリシーをIAMロールにアタッチ
resource "aws_iam_role_policy_attachment" "glue" {
role = aws_iam_role.glue.name
policy_arn = aws_iam_policy.glue.arn
}
# ==============================================================
# VPC Endpoint(S3 Gatewayエンドポイント)
# Glueはプライベートサブネットに配置されるため、
# VPCエンドポイント経由でS3にアクセスする必要がある
# ==============================================================
resource "aws_vpc_endpoint" "s3" {
vpc_id = aws_vpc.main.id
service_name = "com.amazonaws.ap-northeast-1.s3"
vpc_endpoint_type = "Gateway"
# glue-subnetが使用するルートテーブル(VPCのメインルートテーブル)を指定
route_table_ids = [aws_vpc.main.main_route_table_id]
tags = { Name = "glue-endpoint" }
}
# ==============================================================
# Glue Connection(データベース接続設定)
# GlueからRDSにJDBC接続するための設定
# ==============================================================
resource "aws_glue_connection" "main" {
name = "glue-connection"
connection_type = "JDBC"
connection_properties = {
# RDSのエンドポイントはaws_db_instance.main.addressで自動参照
JDBC_CONNECTION_URL = "jdbc:mysql://${aws_db_instance.main.address}:3306/datalake_test"
USERNAME = "admin"
PASSWORD = var.db_master_password
}
# Glueを配置するネットワーク設定
physical_connection_requirements {
availability_zone = "ap-northeast-1a"
security_group_id_list = [aws_security_group.glue.id]
subnet_id = aws_subnet.glue.id
}
}
# ==============================================================
# Glue Crawler(クローラ)
# S3上のParquetデータを自動スキャンし、データカタログにテーブル定義を作成
# ==============================================================
resource "aws_glue_crawler" "users" {
name = "test-crawler"
role = aws_iam_role.glue.arn
database_name = "default"
# クローラのスキャン対象(ETLジョブの出力先S3パス)
s3_target {
path = "s3://${aws_s3_bucket.glue_data.id}/rawdata/users/"
}
}
# ==============================================================
# Glue ETL Job(ETLジョブ)
# RDSからデータを抽出し、Parquet形式に変換してS3に格納する
# ※ ハンズオンではVisual ETLで作成するが、Terraformではスクリプトで定義する
# ==============================================================
# ETLスクリプト(PySpark)をS3にアップロード
resource "aws_s3_object" "etl_script" {
bucket = aws_s3_bucket.glue_data.id
key = "scripts/etl_job.py"
content = <<-PYTHON
import sys
from awsglue.transforms import *
from awsglue.utils import getResolvedOptions
from pyspark.context import SparkContext
from awsglue.context import GlueContext
from awsglue.job import Job
# ジョブ引数の取得(ジョブ名とS3出力先パス)
args = getResolvedOptions(sys.argv, ['JOB_NAME', 's3_target_path'])
# Spark / Glueコンテキストの初期化
sc = SparkContext()
glueContext = GlueContext(sc)
spark = glueContext.spark_session
job = Job(glueContext)
job.init(args['JOB_NAME'], args)
# RDSからデータを抽出(Glue Connectionを使用してJDBC接続)
datasource = glueContext.create_dynamic_frame.from_options(
connection_type="mysql",
connection_options={
"useConnectionProperties": "true",
"dbtable": "users",
"connectionName": "glue-connection"
}
)
# S3にParquet形式で書き出し
glueContext.write_dynamic_frame.from_options(
frame=datasource,
connection_type="s3",
connection_options={"path": args['s3_target_path']},
format="parquet"
)
job.commit()
PYTHON
}
# Glue ETLジョブの定義
resource "aws_glue_job" "etl" {
name = "test-job"
role_arn = aws_iam_role.glue.arn
command {
name = "glueetl"
script_location = "s3://${aws_s3_bucket.glue_data.id}/scripts/etl_job.py"
python_version = "3"
}
# スクリプトに渡すパラメータ
default_arguments = {
"--job-language" = "python"
"--s3_target_path" = "s3://${aws_s3_bucket.glue_data.id}/rawdata/users/"
"--TempDir" = "s3://${aws_s3_bucket.glue_data.id}/temp/"
}
connections = [aws_glue_connection.main.name]
glue_version = "4.0" # Glue 4.0(Spark 3.3ベース)
worker_type = "G.1X" # ワーカータイプ(G.1X = 4 vCPU, 16 GB メモリ)
number_of_workers = 2 # ワーカー数(小規模データのため最小構成)
}
# ==============================================================
# Outputs(出力値)
# terraform apply後にコンソールに表示される情報
# ==============================================================
output "rds_endpoint" {
description = "RDSのエンドポイントアドレス(テストデータ登録時に使用)"
value = aws_db_instance.main.address
}
output "jump_server_instance_id" {
description = "踏み台サーバのインスタンスID(EC2 Instance Connect時に使用)"
value = aws_instance.jump.id
}
output "s3_bucket_name" {
description = "データ格納先のS3バケット名"
value = aws_s3_bucket.glue_data.id
}
9.2 Glueリソースの解説
Terraform コードのうち、このハンズオンの主題であるGlue関連のリソースについて解説します。
| リソース | Terraformリソース名 | 説明 |
|---|---|---|
| S3バケット | aws_s3_bucket / aws_s3_object |
Glueが出力するParquetデータの保存先となるS3バケットを作成。aws_s3_objectでバケット内にrawdata/users/フォルダ(プレフィックス)を作成 |
| IAMポリシー | aws_iam_policy |
Glueデータカタログ・RDS・S3・EC2(VPCネットワーキング用)・CloudWatch Logs(実行ログ出力用)へのアクセス権限を定義 |
| IAMロール | aws_iam_role / aws_iam_role_policy_attachment |
Glueジョブおよびクローラの実行に使用するロール。assume_role_policyでGlueサービスからの引き受けを許可し、上記のIAMポリシーをアタッチ |
| VPCエンドポイント | aws_vpc_endpoint |
GlueはPrivate Subnetに配置されるため、Gateway型のVPCエンドポイントを作成してVPC内部からS3への通信経路を確保。route_table_idsにはGlueサブネットが使用するルートテーブル(VPCのメインルートテーブル)を指定 |
| Glue Connection | aws_glue_connection |
GlueからRDSにJDBC接続するための設定。connection_propertiesにJDBC URL・ユーザ名・パスワードを設定し、physical_connection_requirementsでGlue用のサブネットとセキュリティグループを指定。RDSのエンドポイントはaws_db_instance.main.addressで自動参照されるため、手動でのコピーが不要 |
| Glueクローラ | aws_glue_crawler |
S3上のParquetデータを自動スキャンし、データカタログにテーブル定義(スキーマ)を自動作成する。s3_targetにETLジョブの出力先パス(rawdata/users/)を指定。ハンズオン本編と同じクローラ方式で、手動でのスキーマ定義が不要 |
| ETLスクリプト | aws_s3_object(etl_script) |
PySparkで記述されたETLスクリプトをS3にアップロード。JDBC Connection経由でRDSのusersテーブルからデータを抽出し、S3にParquet形式で保存する。ハンズオンのVisual ETLで自動生成されるスクリプトと同等の処理 |
| Glue ETLジョブ | aws_glue_job |
Glueジョブの定義。スクリプトの場所、IAMロール、Glueバージョン、ワーカータイプ・数などを指定。default_argumentsでスクリプトに渡すパラメータ(出力先S3パスなど)を設定 |
9.3 実行方法
Terraform実行
terraform.tfvarsを作成し、変数を設定します。
db_master_password = "your-password-here"
s3_bucket_suffix = "123456789012" # ご自身のAWSアカウントIDに置き換えてください
まず、Terraformの初期化を行います。プロバイダのダウンロードなどが実行されます。
terraform init
次に、作成されるリソースの実行計画を確認します。どのようなリソースが作成されるかを事前に確認できます。
terraform plan
問題がなければ、実際にリソースを作成します。確認プロンプトが表示されるので、yesと入力してください。
terraform apply
実行が完了すると、RDSのエンドポイントや踏み台サーバのインスタンスIDが出力されます。
以下のコマンドで、RDSのエンドポイントを確認します。この値は次のステップで使用するため、コピーしておいてください。
terraform output rds_endpoint
踏み台サーバにアクセスしてテストデータを登録
踏み台サーバ(JumpServer)にEC2 Instance Connectで接続し、ハンズオン本編と同じ手順でテストデータを登録します。
踏み台サーバに接続したら、MySQLクライアントをインストールします。
sudo dnf install -y mariadb105
RDSに接続します。<RDSのエンドポイント>の部分は、ステップ1で確認したRDSエンドポイントの値に置き換えてください。
mysql -h <RDSのエンドポイント> -u admin -p
パスワードの入力を求められるので、terraform.tfvarsに設定したdb_master_passwordの値を入力します。
接続できたら、データベースを作成します。
CREATE DATABASE datalake_test;
続いて、テーブルを作成します。
CREATE TABLE datalake_test.users (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
age INT,
gender INT
);
最後に、テストデータを投入します。
INSERT INTO datalake_test.users (name, age, gender) VALUES
('Yamada Taro', 28, 1),
('Suzuki Hanako', 25, 2),
('Sato Kenji', 34, 1),
('Tanaka Yuki', 22, 2),
('Kobayashi Shota', 31, 1),
('Watanabe Aiko', 29, 2),
('Ito Naoki', 40, 1),
('Nakamura Mika', 27, 2),
('Kato Kenta', 36, 1),
('Yamamoto Rina', 33, 2);
Glue ETLジョブを実行
Glue ETLジョブはTerraformでリソースを作成しただけでは自動実行されません。AWSマネジメントコンソールから手動で実行します。
AWSマネジメントコンソールで「AWS Glue」を開き、左メニューから「ETL jobs」を選択します。ジョブ一覧からtest-jobを選択し、画面右上の「Run」ボタンをクリックしてジョブを実行します。「Runs」タブでジョブの実行状況を確認でき、ステータスが「Succeeded」になれば完了です。
Glueクローラを実行
ETLジョブが完了したら、S3に出力されたParquetデータのスキーマを自動検出するため、クローラを実行します。
AWSマネジメントコンソールで「AWS Glue」の左メニューから「Crawlers」を選択します。クローラ一覧からtest-crawlerを選択し、「Run」ボタンをクリックして実行します。数分経過するとステータスが「Completed」になり、「Data Catalog」>「Tables」にusersテーブルが自動作成されます。
Athenaでデータを確認
クローラの実行が完了したら、Athenaのダッシュボードを開き、usersテーブルに対してSQLクエリを実行できます。
SELECT * FROM users;
| 💡 ポイント |
|---|
| QuickSightはTerraformでの管理に対応していないため、可視化を行う場合はハンズオン本編の「データの可視化」セクションの手順に従って手動でセットアップしてください。 |
リソースの削除
環境を削除する場合は、以下のコマンドを実行します。
terraform destroy
terraform destroyの前にQuickSightのアカウントを手動で削除してください。QuickSightは月額課金のため、放置すると料金が発生し続けます。