背景・目的
以前、Redshift Serverlessをマネコンから試してみましたが、今回はCDKで作る機会があったので、その時のメモになります。
実践
下記の手順で作成しました。
前提
1.CDKをインストールします
npm install -g aws-cdk
2.インストールされたかバージョンを確認します
% cdk --version
2.1117.0 (build 1fe7075)
%
CDKプロジェクトの初期化
1.CDKプロジェクトを初期化します
cdk init app --language typescript
・・・
✅ All done!
2.以下のファイルが生成されます。
infra/
├── bin/
│ └── infra.ts ← エントリポイント
├── lib/
│ ├── infra-stack.ts ← Stack定義
├── test/
│ └── infra.test.ts ← テスト雛形
├── cdk.json ← CDK設定
├── tsconfig.json ← TypeScript設定
├── package.json ← 依存パッケージ
├── package-lock.json
├── jest.config.js
├── .gitignore
├── .npmignore
└── README.md
lib下のStack定義を修正
- ファイルをリネームします(任意)
- 下記のように書き換えます
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as redshiftserverless from 'aws-cdk-lib/aws-redshiftserverless';
export class RedshiftDevStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const env = 'dev';
// VPC with 3 private subnets (3 AZs)
const vpc = new ec2.Vpc(this, 'Vpc', {
ipAddresses: ec2.IpAddresses.cidr('XX.XX.0.0/16'),
maxAzs: 3,
natGateways: 0,
subnetConfiguration: [
{
name: 'Private',
subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
cidrMask: 24,
},
],
});
// VPC Endpoints
vpc.addInterfaceEndpoint('SecretsManagerEndpoint', {
service: ec2.InterfaceVpcEndpointAwsService.SECRETS_MANAGER,
});
vpc.addGatewayEndpoint('S3Endpoint', {
service: ec2.GatewayVpcEndpointAwsService.S3,
});
// Security Group for Redshift
const sg = new ec2.SecurityGroup(this, 'RedshiftSg', {
vpc,
description: 'SG for Redshift Serverless',
allowAllOutbound: true,
});
sg.addIngressRule(sg, ec2.Port.tcp(5439), 'Self reference');
// Redshift Serverless Namespace
const namespace = new redshiftserverless.CfnNamespace(this, 'Namespace', {
namespaceName: `${env}-redshift-namespace`,
dbName: `${env}-redshift-db`,
adminUsername: 'xxxxx',
manageAdminPassword: true,
logExports: ['userlog', 'connectionlog', 'useractivitylog'],
});
// Redshift Serverless Workgroup
const workgroup = new redshiftserverless.CfnWorkgroup(this, 'Workgroup', {
workgroupName: `${env}-redshift-workgroup`,
namespaceName: namespace.namespaceName,
baseCapacity: 4,
maxCapacity: 1024,
port: 5439,
publiclyAccessible: false,
enhancedVpcRouting: true,
securityGroupIds: [sg.securityGroupId],
subnetIds: vpc.isolatedSubnets.map(s => s.subnetId),
});
workgroup.addDependency(namespace);
}
}
下記を作成しています。
-
VPC
- CIDR: XX.XX.0.0/16
- AZは3つ
- PrivateサブネットのCIDR:/24で自動生成される
- NAT Gateway:なし
- IGW:なし
- ルートはローカルのみ。SubnetTypeのパラメータは下記の通り
SubnetType ルートテーブルに追加されるルート PUBLIC IGWへのルート PRIVATE_WITH_EGRESS NAT GWへのルート PRIVATE_ISOLATED ローカルのみ(何も追加しない)
-
VPCe
- SecretsManagerにパスワードを保存するため
- EVR有効時にS3へVPC経由でアクセスするため
-
セキュリティグループ
- Outbound: 制御無し
- Inbound: 5439のみ許可
-
Redshift Serverless Namespace
- manageAdminPassword: True。SecretsManagerでパスワードを自動で生成
- logExports: 監査ログをCloudWatch Logsにエクスポートする
ログ種別 内容 userlog ユーザーの作成・削除・権限変更などの操作記録 connectionlog 接続・切断の記録(誰がいつ接続したか) useractivitylog 実行されたSQL文の記録 -
Redshift Serverless Workgroup
- baseCapacity: 4(クエリ時の最小RPU、アイドル時は0)、maxCapacity: 1024(スケール上限)
- ポート:5439
- セキュリティグループ:作成済みのSG
- subnetIds:作成したサブネットID
- enhancedVpcRouting: true
- S3等へのデータアクセスをVPC経由に強制する
- セキュリティグループやVPC Flow Logsで通信を制御・監視可能
- 有効時はS3用VPC Endpoint等が必要
bin下のエントリポイントを修正
- ファイルをリネームします(任意)
- 下記のように書き換えます
#!/usr/bin/env node
import * as cdk from 'aws-cdk-lib/core';
import { RedshiftDevStack } from '../lib/redshift-dev-stack';
const app = new cdk.App();
new RedshiftDevStack(app, 'RedshiftDevStack', {
env: {
account: process.env.CDK_DEFAULT_ACCOUNT,
region: process.env.CDK_DEFAULT_REGION || 'ap-northeast-1',
},
});
testコードの修正
- ファイルをリネームします(任意)
- 下記のように書き換えます
import * as cdk from 'aws-cdk-lib';
import { Template } from 'aws-cdk-lib/assertions';
import { RedshiftDevStack } from '../lib/redshift-dev-stack';
const app = new cdk.App();
const stack = new RedshiftDevStack(app, 'TestStack');
const template = Template.fromStack(stack);
test('VPC created with correct CIDR', () => {
template.hasResourceProperties('AWS::EC2::VPC', {
CidrBlock: 'XX.XX.0.0/16',
});
});
test('3 private subnets created', () => {
template.resourceCountIs('AWS::EC2::Subnet', 3);
});
test('Redshift Namespace created', () => {
template.hasResourceProperties('AWS::RedshiftServerless::Namespace', {
NamespaceName: 'dev-redshift-namespace',
DbName: 'dev-redshift-db',
AdminUsername: 'XXXX',
ManageAdminPassword: true,
});
});
test('Redshift Workgroup created', () => {
template.hasResourceProperties('AWS::RedshiftServerless::Workgroup', {
WorkgroupName: 'dev-redshift-workgroup',
BaseCapacity: 4,
MaxCapacity: 1024,
Port: 5439,
PubliclyAccessible: false,
});
});
test('Security Group has self-reference ingress on 5439', () => {
template.hasResourceProperties('AWS::EC2::SecurityGroup', {
GroupDescription: 'SG for Redshift Serverless',
SecurityGroupName: 'dev-sg-redshift',
});
});
test('Secrets Manager VPC Endpoint created', () => {
template.hasResourceProperties('AWS::EC2::VPCEndpoint', {
ServiceName: { 'Fn::Join': ['', ['com.amazonaws.', { Ref: 'AWS::Region' }, '.secretsmanager']] },
});
});
test('No NAT Gateway', () => {
template.resourceCountIs('AWS::EC2::NatGateway', 0);
});
3.テストを実行します
npm test
4.いくつかエラーになりました。Private Subnet、Security Groupが失敗してます
・・・・
FAIL test/redshift-dev-stack.test.ts (6.083 s)
✓ VPC created with correct CIDR (7 ms)
✕ 3 private subnets created (3 ms)
✓ Redshift Namespace created
✓ Redshift Workgroup created
✕ Security Group has self-reference ingress on 5439 (2 ms)
✓ Secrets Manager VPC Endpoint created
✓ No NAT Gateway
・・・・
Test Suites: 1 failed, 1 total
Tests: 2 failed, 5 passed, 7 total
Snapshots: 0 total
Time: 6.248 s
Ran all test suites.
5.元のコードを修正(サブネット数3→2、SecurityGroupNameの削除等)して再テストします
npm test
> infra@0.1.0 test
> jest
・・・・
PASS test/redshift-dev-stack.test.ts (5.859 s)
✓ VPC created with correct CIDR (7 ms)
✓ 3 private subnets created
✓ Redshift Namespace created
✓ Redshift Workgroup created
✓ Security Group has self-reference ingress on 5439 (1 ms)
✓ Secrets Manager VPC Endpoint created (1 ms)
✓ No NAT Gateway
・・・・
Test Suites: 1 passed, 1 total
Tests: 7 passed, 7 total
Snapshots: 0 total
Time: 6.013 s, estimated 7 s
Ran all test suites.
Cfnテンプレートを生成
1.cdk synthを実行します
2.cdk.outの下にファイルが生成されました
cdk.out/
├── RedshiftDevStack.template.json ← CFnテンプレート本体
├── RedshiftDevStack.assets.json ← アセットのS3アップロード先
├── RedshiftDevStack.metadata.json ← リソースとコードの対応
├── manifest.json ← スタック一覧・デプロイ順序
├── tree.json ← コンストラクトツリー
├── cdk.out ← バージョン情報
└── asset.7fa1e.../ ← Lambda関数コード
├── index.js ← デフォルトSG制限ロジック
└── __entrypoint__.js ← Lambda起動ハンドラ
CDKのデプロイ準備(初回のみ)
CDKがAWSにデプロイするために必要なS3バケットやIAMロールを作成します。
アカウント×リージョンごとに初回1回だけ実行が必要です。
1.cdk bootstrapを実行します
cdk bootstrap
Bootstrapping environment aws://XXXXXXXXXX/ap-northeast-1...
・・・
CDKToolkit | 12/12 | 8:54:00 PM | CREATE_COMPLETE | AWS::CloudFormation::Stack | CDKToolkit
✅ Environment aws://XXXXXXXXXX/ap-northeast-1 bootstrapped.
2.CloudFormationで、CDKToolkitスタックがCREATE_COMPLETEになっていました
3.S3の下に、ファイルが出来ていました

CDKのデプロイ
1.cdk deployを実行します
cdk deploy
Synthesis time: 4.52s
Stack RedshiftDevStack
IAM Statement Changes
・・・・
"--require-approval" is enabled and stack includes security-sensitive updates: 'Do you wish to deploy these changes' (y/n)
RedshiftDevStack: deploying... [1/1]
RedshiftDevStack: creating CloudFormation changeset...
[██████████████████████▌···································] (7/18)
21:04:07 | CREATE_IN_PROGRESS | AWS::CloudFormation::Stack | RedshiftDevStack
21:04:25 | CREATE_IN_PROGRESS | AWS::EC2::RouteTable | Vpc/PrivateSubnet2/RouteTable
✨ Deployment time: 119.1s
Stack ARN:
arn:aws:cloudformation:ap-northeast-1:XXXXX:stack/RedshiftDevStack/XXXXX
✨ Total time: 123.62s
%
2.CloudFormationで、RedshiftDevStack スタックがCREATE_COMPLETEになっていました
リソースの確認
デプロイされたか確認します
Redshift
1.下記のコマンドで、namespaceを確認します
aws redshift-serverless list-namespaces --region ap-northeast-1
{
"namespaces": [
{
"adminPasswordSecretArn": "arn:aws:secretsmanager:ap-northeast-1:XXXXXXXXXX:secret:redshift!dev-redshift-namespaceXXXXX",
"adminUsername": "XXXXXXXXXX",
"creationDate": "2026-04-05T12:04:11.556000+00:00",
"dbName": "dev-redshift-db",
"iamRoles": [],
"kmsKeyId": "AWS_OWNED_KMS_KEY",
"logExports": [
"connectionlog",
"useractivitylog",
"userlog"
],
"namespaceArn": "arn:aws:redshift-serverless:ap-northeast-1:XXXXXXXXXX:namespace/XXXXXXXXXX",
"namespaceId": "XXXXXXXXXX",
"namespaceName": "dev-redshift-namespace",
"status": "AVAILABLE"
}
]
}
2.下記のコマンドでworkgroupを確認します
aws redshift-serverless list-workgroups --region ap-northeast-1
{
"workgroups": [
{
"baseCapacity": 4,
"configParameters": [
{
・・・・
"status": "AVAILABLE",
"subnetIds": [
"subnet-XXXXX",
"subnet-XXXXX",
"subnet-XXXXX"
],
}
]
}
クエリを実行する
- AWSマネコンからRedshift>サーバレスダッシュボードをクリックします
- Redshift Serverlessでクエリエディタ>クエリエディタv2でクエリをクリックします
- そのまま「Configure account」をクリックします(AWS管理キーでクエリ等を保存、ローカルからアップする際に使用するS3バケットの指定。)
- クエリエディタが別タブで開かれます。左側に作成したWorkgroupが表示されるので、クリックします
- AWS Secrets Managerを選択し、「Create connection」をクリックします
6.下記のクエリを実行します。current_user、current_database、バージョンが表示されました
-- 接続確認
SELECT current_user, current_database(), version();
7.下記のクエリを実行します
-- テスト用テーブル作成
CREATE TABLE public.test_table (
id INT IDENTITY(1,1),
name VARCHAR(100),
created_at TIMESTAMP DEFAULT GETDATE()
);
-- データ挿入
INSERT INTO public.test_table (name) VALUES ('テスト太郎'), ('テスト花子');
-- 確認
SELECT * FROM public.test_table;
10.Result 3を確認します。データとして登録されていました

考察
下記のようなことがわかりました。
- CDKのStackに account を指定しないと、maxAzs: 3 と書いてもサブネットが2AZしか作られない。process.env.CDK_DEFAULT_ACCOUNT で動的に取得するのが良い
- EVRを有効にするとS3等へのアクセスがVPC経由になり、セキュリティグループやFlow Logsで制御・監視できる。
参考

