S3テーブルのセットアップ
本ガイドでは、2025年5月のAWSアップデートに基づいて、リソースリンクを必要としずに Amazon Data FirehoseからS3テーブルへのデータストリーミングのための新しいアプローチについて説明します。
前提条件
-
適切な権限を持つAWSアカウント
-
インストールおよび設定済みのAWS CLI
-
AWS CDKの知識(インフラストラクチャアズコードを使用する場合)
-
AWS Analytics サービスとのS3テーブル統合の有効化
概要
新しいアプローチでは、s3tablescatalog カタログ形式を通じてFirehoseがS3テーブルに直接アクセスできるようにすることで、リソースリンク不要を実現します。
ステップ1: AWS Analyticsサービスとの S3テーブル統合を有効化
1.1 AWS コンソール経由
-
Amazon S3 コンソール → テーブルバケット に移動
-
まだ有効化されていない場合は、統合を有効化 をクリック
-
これにより、S3テーブルがAWSアナリティクスサービスで検出可能になります
1.2 AWS CLI経由
aws s3tables put-table-bucket-policy \
--table-bucket-arn "arn:aws:s3tables:REGION:ACCOUNT:bucket/BUCKET_NAME" \
--policy-document '{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {"Service": "analytics.amazonaws.com"},
"Action": "s3tables:*",
"Resource": "*"
}
]
}' \
--region REGION
ステップ2: S3テーブルバケットとテーブルを作成
2.1 テーブルバケットを作成
aws s3tables create-table-bucket \
--name "your-table-bucket-name" \
--region REGION
2.2 ネームスペースを作成
aws s3tables create-namespace \
--table-bucket-arn "arn:aws:s3tables:REGION:ACCOUNT:bucket/BUCKET_NAME" \
--namespace "your-namespace" \
--region REGION
2.3 スキーマ付きのテーブルを作成
# テーブル定義ファイルを作成
cat > table-definition.json << 'EOF'
{
"tableBucketARN": "arn:aws:s3tables:REGION:ACCOUNT:bucket/BUCKET_NAME",
"namespace": "your-namespace",
"name": "your-table-name",
"format": "ICEBERG",
"metadata": {
"iceberg": {
"schema": {
"fields": [
{"name": "id", "type": "int", "required": true},
{"name": "timestamp", "type": "timestamp"},
{"name": "data", "type": "string"}
]
}
}
}
}
EOF
# テーブルを作成
aws s3tables create-table --cli-input-json file://table-definition.json --region REGION
ステップ3: Firehose用のIAMロールを作成
3.1 信頼ポリシーを作成
cat > firehose-trust-policy.json << 'EOF'
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "firehose.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
EOF
3.2 IAMロールを作成
aws iam create-role \
--role-name "firehose-s3-tables-role" \
--assume-role-policy-document file://firehose-trust-policy.json \
--region REGION
ステップ4: S3テーブルアクセス用のIAMポリシーを作成
4.1 S3テーブルアクセスポリシーを作成
cat > s3-tables-policy.json << 'EOF'
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "S3TableAccessViaGlueFederation",
"Effect": "Allow",
"Action": [
"glue:GetTable",
"glue:GetDatabase",
"glue:UpdateTable"
],
"Resource": [
"arn:aws:glue:REGION:ACCOUNT:catalog/s3tablescatalog/TABLE_BUCKET_NAME",
"arn:aws:glue:REGION:ACCOUNT:catalog/s3tablescatalog",
"arn:aws:glue:REGION:ACCOUNT:catalog",
"arn:aws:glue:REGION:ACCOUNT:database/s3tablescatalog/TABLE_BUCKET_NAME/NAMESPACE_NAME",
"arn:aws:glue:REGION:ACCOUNT:table/s3tablescatalog/TABLE_BUCKET_NAME/NAMESPACE_NAME/TABLE_NAME"
]
},
{
"Sid": "LakeFormationDataAccess",
"Effect": "Allow",
"Action": [
"lakeformation:GetDataAccess"
],
"Resource": "*"
},
{
"Sid": "S3TablesDirectAccess",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3tables:REGION:ACCOUNT:bucket/TABLE_BUCKET_NAME",
"arn:aws:s3tables:REGION:ACCOUNT:bucket/TABLE_BUCKET_NAME/*"
]
},
{
"Sid": "S3BackupBucketAccess",
"Effect": "Allow",
"Action": [
"s3:AbortMultipartUpload",
"s3:GetBucketLocation",
"s3:GetObject",
"s3:ListBucket",
"s3:ListBucketMultipartUploads",
"s3:PutObject"
],
"Resource": [
"arn:aws:s3:::backup-bucket-name",
"arn:aws:s3:::backup-bucket-name/*"
]
}
]
}
EOF
# ポリシーをロールにアタッチ
aws iam put-role-policy \
--role-name "firehose-s3-tables-role" \
--policy-name "S3TablesFirehosePolicy" \
--policy-document file://s3-tables-policy.json \
--region REGION
重要: 以下のプレースホルダーを置換してください:
-
REGION: AWSリージョン(例:ap-northeast-1) -
ACCOUNT: AWSアカウントID -
TABLE_BUCKET_NAME: S3テーブルバケット名 -
NAMESPACE_NAME: S3テーブルネームスペース -
TABLE_NAME: S3テーブルテーブル名 -
backup-bucket-name: バックアップS3バケット名
ステップ5: Lake Formationのパーミッションを設定
5.1 AWSコンソール経由でパーミッションを付与
-
AWS Lake Formation コンソール に移動
-
データパーミッション → 付与 に移動
-
名前付きデータカタログリソース を選択
-
プリンシパル: Firehoseロール(
firehose-s3-tables-role)を選択 -
カタログ:
ACCOUNT:s3tablescatalog/TABLE_BUCKET_NAMEを選択 -
データベース: ネームスペースを選択
-
テーブル: テーブルまたは「すべてのテーブル」を選択
-
パーミッション: スーパー パーミッションを付与
-
付与 をクリック
5.2 AWS CLI経由でパーミッションを付与
aws lakeformation grant-permissions \
--principal "arn:aws:iam::ACCOUNT:role/firehose-s3-tables-role" \
--resource '{
"Table": {
"CatalogId": "ACCOUNT:s3tablescatalog/TABLE_BUCKET_NAME",
"DatabaseName": "NAMESPACE_NAME",
"Name": "TABLE_NAME"
}
}' \
--permissions "ALL" \
--region REGION
ステップ6: Firehose配信ストリームを作成
6.1 AWSコンソール経由
-
Amazon Data Firehose コンソール に移動
-
配信ストリームを作成 をクリック
-
ソース: Direct PUT を選択
-
宛先: Apache Iceberg Tables を選択
-
ストリーム名: ストリーム名を入力
-
宛先設定:
-
カタログ:
ACCOUNT:s3tablescatalog/TABLE_BUCKET_NAME -
データベース: ネームスペース名
-
テーブル: テーブル名
-
IAMロール: Firehoseロールを選択
-
S3バックアップ: バックアップバケットを設定
-
配信ストリームを作成 をクリック
6.2 AWS CLI経由
cat > firehose-config.json << 'EOF'
{
"DeliveryStreamName": "your-firehose-stream-name",
"DeliveryStreamType": "DirectPut",
"IcebergDestinationConfiguration": {
"RoleARN": "arn:aws:iam::ACCOUNT:role/firehose-s3-tables-role",
"CatalogConfiguration": {
"CatalogARN": "arn:aws:glue:REGION:ACCOUNT:catalog/s3tablescatalog/TABLE_BUCKET_NAME"
},
"DestinationTableConfigurationList": [
{
"DestinationDatabaseName": "NAMESPACE_NAME",
"DestinationTableName": "TABLE_NAME"
}
],
"BufferingHints": {
"IntervalInSeconds": 60,
"SizeInMBs": 64
},
"S3Configuration": {
"RoleARN": "arn:aws:iam::ACCOUNT:role/firehose-s3-tables-role",
"BucketARN": "arn:aws:s3:::backup-bucket-name"
}
}
}
EOF
aws firehose create-delivery-stream --cli-input-json file://firehose-config.json --region REGION
ステップ7: CDK実装(オプション)
7.1 CDKコンストラクト例
import { ITableBucket } from '@aws-cdk/aws-s3tables-alpha';
import {
aws_kinesisfirehose as firehose,
aws_iam as iam,
aws_s3 as s3,
Stack,
} from 'aws-cdk-lib';
import { Construct } from 'constructs';
export class S3TablesFirehoseConstruct extends Construct {
readonly deliveryStream: firehose.CfnDeliveryStream;
readonly firehoseRole: iam.IRole;
constructor(scope: Construct, id: string, props: {
namespaceName: string;
tableName: string;
tableBucket: ITableBucket;
}) {
super(scope, id);
const { region, account } = Stack.of(this);
// Create backup bucket
const backupBucket = new s3.Bucket(this, 'FirehoseBackupBucket');
// Create Firehose role
this.firehoseRole = new iam.Role(this, 'FirehoseRole', {
assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'),
inlinePolicies: {
S3TablesAccess: new iam.PolicyDocument({
statements: [
new iam.PolicyStatement({
sid: 'S3TableDirectCatalogAccess',
actions: ['glue:GetDatabase', 'glue:GetTable', 'glue:UpdateTable'],
resources: [
`arn:aws:glue:${region}:${account}:catalog/s3tablescatalog/${props.tableBucket.tableBucketName}`,
`arn:aws:glue:${region}:${account}:catalog/s3tablescatalog`,
`arn:aws:glue:${region}:${account}:catalog`,
`arn:aws:glue:${region}:${account}:database/s3tablescatalog/${props.tableBucket.tableBucketName}/${props.namespaceName}`,
`arn:aws:glue:${region}:${account}:table/s3tablescatalog/${props.tableBucket.tableBucketName}/${props.namespaceName}/${props.tableName}`,
],
}),
new iam.PolicyStatement({
sid: 'LakeFormationDataAccess',
actions: ['lakeformation:GetDataAccess'],
resources: ['*'],
}),
new iam.PolicyStatement({
sid: 'S3TablesDirectBucketAccess',
actions: ['s3:GetObject', 's3:PutObject', 's3:DeleteObject', 's3:ListBucket'],
resources: [
props.tableBucket.tableBucketArn,
`${props.tableBucket.tableBucketArn}/*`,
],
}),
],
}),
},
});
// Grant backup bucket permissions
backupBucket.grantReadWrite(this.firehoseRole);
// Create Firehose delivery stream
this.deliveryStream = new firehose.CfnDeliveryStream(this, 'DeliveryStream', {
deliveryStreamType: 'DirectPut',
icebergDestinationConfiguration: {
roleArn: this.firehoseRole.roleArn,
catalogConfiguration: {
catalogArn: `arn:aws:glue:${region}:${account}:catalog/s3tablescatalog/${props.tableBucket.tableBucketName}`,
},
destinationTableConfigurationList: [{
destinationDatabaseName: props.namespaceName,
destinationTableName: props.tableName,
}],
bufferingHints: {
intervalInSeconds: 60,
sizeInMBs: 64,
},
s3Configuration: {
roleArn: this.firehoseRole.roleArn,
bucketArn: backupBucket.bucketArn,
},
} as any,
});
}
}
ステップ8: セットアップをテスト
8.1 テストデータを送信
# テストデータを作成
cat > test-data.json << 'EOF'
{"id": 1, "timestamp": "2025-07-18T10:00:00Z", "data": "test message"}
EOF
# Firehoseにデータを送信
aws firehose put-record \
--delivery-stream-name "your-firehose-stream-name" \
--record '{"Data": "{\"id\": 1, \"timestamp\": \"2025-07-18T10:00:00Z\", \"data\": \"test message\"}"}' \
--region REGION
8.2 S3テーブルのデータを検証
# Athenaを使用してクエリ
aws athena start-query-execution \
--query-string "SELECT * FROM\"s3tablescatalog/TABLE_BUCKET_NAME\".\"NAMESPACE_NAME\".\"TABLE_NAME\" LIMIT 10" \
--result-configuration "OutputLocation=s3://athena-results-bucket/" \
--region REGION
以前のアプローチとの主な違い
❌ 旧アプローチ(リソースリンク付き)
-
デフォルトGlueカタログでのリソースリンク作成が必要
-
複数カタログを使用した複雑なセットアップ
-
潜在的な同期の問題
✅ 新アプローチ(S3テーブルの直接アクセス)
-
リソースリンク不要
-
s3tablescatalogを通じたS3テーブルへの直接アクセス -
よりシンプルで信頼性の高いセットアップ
-
パフォーマンスの向上
トラブルシューティング
一般的な問題
- パーミッションエラー
-
IAMロールにすべての必要な権限があることを確認
-
Lake Formationのパーミッションが付与されていることを確認
-
S3テーブル統合が有効化されていることを確認
- テーブルが見つからない
-
テーブルがS3テーブルに存在することを確認
-
ネームスペースとテーブル名が正確に一致することを確認
-
カタログARN形式を確認
- アクセス拒否
-
Lake Formationのパーミッションを確認
-
IAMポリシーが正しいARN形式を使用していることを確認
-
S3テーブルバケットのパーミッションを確認
検証コマンド
# S3テーブルリソースを確認
aws s3tables list-table-buckets --region REGION
aws s3tables list-namespaces --table-bucket-arn "arn:aws:s3tables:REGION:ACCOUNT:bucket/BUCKET_NAME" --region REGION
aws s3tables list-tables --table-bucket-arn "arn:aws:s3tables:REGION:ACCOUNT:bucket/BUCKET_NAME" --namespace "NAMESPACE_NAME" --region REGION
# IAMロールを確認
aws iam get-role --role-name "firehose-s3-tables-role"
aws iam list-role-policies --role-name "firehose-s3-tables-role"
# Lake Formationのパーミッションを確認
aws lakeformation list-permissions --principal "arn:aws:iam::ACCOUNT:role/firehose-s3-tables-role" --region REGION
まとめ
この新しいアプローチにより、リソースリンク不要にすることで、FirehoseとS3テーブルのセットアップが大幅に簡素化されます。直接カタログアクセスにより、より高いパフォーマンスと信頼性が得られ、全体的なアーキテクチャの複雑さが軽減されます。 成功の重要な要素は以下の通りです:
-
新しいARN形式による適切なIAMパーミッション
-
正確なLake Formationのパーミッション
-
s3tablescatalogカタログARN形式の使用 -
ネームスペースとテーブルの直接参照