概要
Amazon Auroraの開始方法とCDK Patternsを参考にしてRDS Proxyを利用したAPIをデプロイしてみたをみながら、CDK v2を使って試してみる。今回の作成範囲は、RDSと踏み台のみとする。
なお、RDSを作ってもたもたしていると50円くらいかかる可能性がある。
踏み台のポートフォワーディングについては別記事とした。
今回作成するアーキテクチャ
環境
- Windows 10
- aws cli,awscli-session-managerをインストール済
- コマンドはpowershellを使用
今回は、chocolateyで以下のようにインストール済
cinst -y awscli awscli-session-manager
インストール後はbashやvscodeの再起動推奨。
session-manager-plugin
VPCの準備
DBクラスターを作成する前に、Virtual Private Cloud (VPC) が必要です。
とあるので、まずはVPCの設定を行う。
CDKのスタックは以下。
まずはRDSのセキュリティグループのインバウンドルールは全て受け入れる状態にしておく。
接続が確認できたら閉じる予定。
import { Aspects, Stack, StackProps, Tag, Tags } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { CfnInternetGateway, CfnRoute, CfnRouteTable, CfnVPCGatewayAttachment, Peer, Port, PrivateSubnet, PrivateSubnetProps, PublicSubnet, PublicSubnetProps, RouterType, SecurityGroup, SubnetType, Vpc } from 'aws-cdk-lib/aws-ec2';
import { SubnetGroup } from 'aws-cdk-lib/aws-rds';
interface VpcStackProps extends StackProps {
subnetGroupName: string
}
export class VpcStack extends Stack {
constructor(scope: Construct, id: string, props: VpcStackProps) {
super(scope, id, { ...props, subnetGroupName: undefined } as StackProps);
const cidr = '10.0.0.0/16';
const vpc = new Vpc(this, 'VPC', {
cidr,
natGateways: 0, // デフォルトは1。
subnetConfiguration: [
{
cidrMask: 24,
name: "public-subnet",
subnetType: SubnetType.PUBLIC
},
], // デフォルトはAZごとに1つのパブリックサブネットと1つのプライベートサブネット
maxAzs: 3 // 3を指定すると、 public: 10.0.0.0 - 10.0.0.2 の3つ分のcidrBlockが使われる。
});
Tags.of(vpc).add('Name', 'vpc');
// プライベートSubnet(RDS用)
const privateSubnetProps: PrivateSubnetProps[] = [
{ availabilityZone: 'ap-northeast-1a', vpcId: vpc.vpcId, cidrBlock: '10.0.3.0/24' },
// ap-northeast-1b は使えない
{ availabilityZone: 'ap-northeast-1c', vpcId: vpc.vpcId, cidrBlock: '10.0.4.0/24' },
{ availabilityZone: 'ap-northeast-1d', vpcId: vpc.vpcId, cidrBlock: '10.0.5.0/24' },
]
const subnets = privateSubnetProps.map((prop, i) => {
const subnet = new PrivateSubnet(this, `MyPrivateSubnet${i}`, prop);
Tags.of(subnet).add('Name', `private-subnet-${i}`);
Tags.of(subnet).add('aws-cdk:subnet-type', SubnetType.PRIVATE_ISOLATED);
return subnet
});
//------------------ Aurora用の設定 ----------------------------------
const securityGroupPrivate = new SecurityGroup(this, 'SecurityGroupForPrivateSubnets', {
vpc,
description: 'seburity group for Aurora and vpc lambda'
})
Tags.of(securityGroupPrivate).add('Name', 'SecurityGroupForPrivateSubnets');
securityGroupPrivate.addIngressRule(Peer.ipv4(cidr), Port.allTcp());
const subnetGroupForAurora = new SubnetGroup(this, 'SubnetGroupForAurora', {
vpc,
vpcSubnets: { subnets },
description: 'subnet group for Aurora db',
subnetGroupName: props.subnetGroupName
});
Tags.of(subnetGroupForAurora).add('Name', 'SubnetGroupForAurora');
//------------------ 踏み台用の設定 ----------------------------------
const securityGroupPublic = new SecurityGroup(this, 'SecurityGroupForPublicSubnets', {
vpc,
description: 'seburity group for bastion'
})
Tags.of(securityGroupPublic).add('Name', 'SecurityGroupForPublicSubnets');
//------------------ 共通設定 ----------------------------------
// 作成したリソース全てにタグをつける
Aspects.of(this).add(new Tag('Stack', id));
}
}
Security Group Changes
┌───┬──────────────────────────────────────────┬─────┬─────────────┬─────────────────┐
│ │ Group │ Dir │ Protocol │ Peer │
├───┼──────────────────────────────────────────┼─────┼─────────────┼─────────────────┤
│ + │ ${SecurityGroupForPrivateSubnts.GroupId} │ In │ TCP 0-65535 │ 10.0.0.0/16 │
│ + │ ${SecurityGroupForPrivateSubnts.GroupId} │ Out │ Everything │ Everyone (IPv4) │
└───┴──────────────────────────────────────────┴─────┴─────────────┴─────────────────┘
なお、設定値はdotenvを使って.env
ファイルに環境変数として設定している。
#!/usr/bin/env node
import 'source-map-support/register';
import { VpcStack } from '../lib/vpc-stack';
import { App } from 'aws-cdk-lib';
import { config } from 'dotenv'
config();
if (!process.env.SUBNET_GROUP_NAME) throw Error('please set environment variable SUBNET_GROUP_NAME')
const app = new App();
new VpcStack(app, 'VpcStack', {
env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION },
subnetGroupName: process.env.SUBNET_GROUP_NAME,
});
SUBNET_GROUP_NAME=SubnetGroupForAurora
RDSのPostgresのバージョン確認
最新版のバージョンが使えるか確認。
aws rds describe-orderable-db-instance-options --engine aurora-postgresql --engine-version 13.4 --query 'OrderableDBInstanceOptions[].[DBInstanceClass,StorageType,Engine,EngineVersion]' --output table --region ap-northeast-1 '
AuroraPostgresのRDSProxyはバージョン10と11のみサポートしているので、13はRDSProxyを使おうと思うと使えない。
RDSの設定
VPCの設定値を控えておいて、環境変数を通して設定するようにしている。
Postgresを使うにあたって、インスタンスのバージョンに制限があるので注意。t2.micro
では使用できない。
RDSのAdminユーザのパスワードなどはSecretを経由して設定を行う。
import { Aspects, RemovalPolicy, SecretValue, Stack, StackProps, Tag, Tags } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { InstanceClass, InstanceSize, InstanceType, Peer, Port, PrivateSubnet, PrivateSubnetProps, SecurityGroup, Vpc } from 'aws-cdk-lib/aws-ec2';
import { AuroraPostgresEngineVersion, Credentials, DatabaseCluster, DatabaseClusterEngine, DatabaseProxy, ParameterGroup, ProxyTarget, SubnetGroup } from 'aws-cdk-lib/aws-rds';
import * as iam from 'aws-cdk-lib/aws-iam';
import { Secret } from 'aws-cdk-lib/aws-secretsmanager';
interface AuroraStackProps extends StackProps {
vpcId: string
sgId: string
subnetGroupName: string
dbSecretName: string
dbAdminName: string
dbUserPassword: string
}
export class AuroraStack extends Stack {
constructor(scope: Construct, id: string, props: AuroraStackProps) {
// デフォルトのpropsとの意図しない競合を防ぐため、自前で設定したプロパティを削除して親に渡す
const superProps = {
...props, vpcId: undefined, sgId: undefined, subnetName: undefined
, dbSecretName: undefined, dbAdminName: undefined, dbUserPassword: undefined
} as StackProps
super(scope, id, superProps);
const vpc = Vpc.fromLookup(this, 'Vpc', { vpcId: props.vpcId })
const securityGroup = SecurityGroup.fromLookupById(this, 'SecurityGroup', props.sgId);
// subnetGroupNameはlowecaseで作成される
const subnetGroup = SubnetGroup.fromSubnetGroupName(this, 'SubnetGroup', props.subnetGroupName.toLowerCase());
const secret = new Secret(this, 'DBCredentioalSecret', {
secretName: props.dbSecretName,
generateSecretString: {
secretStringTemplate: JSON.stringify({ username: props.dbAdminName }),
excludePunctuation: true, // '、/、"、@、スペースはpostgresのパスワードに利用できないので除外
includeSpace: false,
generateStringKey: 'password'
}
})
Tags.of(secret).add('Name', props.dbSecretName);
const cluster = new DatabaseCluster(this, 'clusterForAurora', {
// LTSのバージョンを選択.RDSProxyは10と11のみのサポート 2021.12.10
engine: DatabaseClusterEngine.auroraPostgres({ version: AuroraPostgresEngineVersion.VER_11_9 }),
removalPolicy: RemovalPolicy.DESTROY, // 本番運用だと消しちゃだめだと思う
defaultDatabaseName: 'postgres',
instanceProps: {
vpc,
securityGroups: [securityGroup],
// postgresを使える最安値 (2021.12.10)
instanceType: InstanceType.of(InstanceClass.T4G, InstanceSize.MEDIUM),
},
instances: 1,
subnetGroup,
// parameterGroup,
credentials: Credentials.fromSecret(secret)
});
// 作成したリソース全てにタグをつける
Aspects.of(this).add(new Tag('Stack', id));
}
}
踏み台サーバの設定
RDSをVPCのPrivateSubnet内に作成したため、アクセスは踏み台経由で行うものとする。
cdkのBastionHostLinuxクラスを使うと踏み台用のSSM設定がされた状態でEC2を立てることができる。
今回のCDKで作成したpublicサブネットでは、参照先のようにsubnetSelection: {subnetType: ec2.SubnetType.PUBLIC,}
と指定すると、, There are no 'Public' subnet groups in this VPC. Available types: Isolated
のエラーが表示されてしまう。
そのため、subnetのidを指定して設定している。
また、RDS用のSecretへの読み取り権限を付与している。
import { , Stack, StackProps, Tags } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { BastionHostLinux, InstanceType, PublicSubnet, SecurityGroup, Vpc } from 'aws-cdk-lib/aws-ec2';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import { Secret } from 'aws-cdk-lib/aws-secretsmanager';
interface BastionStackProps extends StackProps {
vpcId: string
sgId: string
dbSecretName: string
publicSubnetId: string
}
export class BastionStack extends Stack {
constructor(scope: Construct, id: string, props: BastionStackProps) {
// デフォルトのpropsとの意図しない競合を防ぐため、自前で設定したプロパティを削除して親に渡す
const superProps = {
...props, vpcId: undefined, sgId: undefined, subnetName: undefined
, dbSecretName: undefined, dbAdminName: undefined, dbUserPassword: undefined
} as StackProps
super(scope, id, superProps);
const vpc = Vpc.fromLookup(this, 'Vpc', { vpcId: props.vpcId })
const bastionGroup = SecurityGroup.fromLookupById(this, 'SecurityGroup', props.sgId);
// パブリックサブネットに踏み台サーバを配置する
const subnet = PublicSubnet.fromPublicSubnetAttributes(this, 'PublicSubnet', {
subnetId: props.publicSubnetId,
availabilityZone: 'ap-northeast-1a'
});
const host = new BastionHostLinux(this, 'BastionHost', {
vpc,
instanceType: InstanceType.of(
ec2.InstanceClass.T2,
ec2.InstanceSize.MICRO
),
securityGroup: bastionGroup,
subnetSelection: {
availabilityZones: [subnet.availabilityZone],
subnets: [subnet]
}
});
Tags.of(host).add('Name', 'BastionLinux');
host.instance.addUserData(
'yum -y update',
'yum install -y postgresql jq', // postgres 9.2がインストールされる。 RDSのpostgres11よりも古いので、より完璧を目指すならpostgres11をインストールしたい
// DB接続用のbash作成
"echo '#!/bin/bash' > /usr/bin/dbaccess.sh",
`echo 'secret=$(aws secretsmanager get-secret-value --region ap-northeast-1 --secret-id ${props.dbSecretName} | jq .SecretString | jq fromjson)' >> /usr/bin/dbaccess.sh`,
"echo 'user=$(echo $secret | jq -r .username)' >> /usr/bin/dbaccess.sh",
"echo 'password=$(echo $secret | jq -r .password)' >> /usr/bin/dbaccess.sh",
"echo 'endpoint=$(echo $secret | jq -r .host)' >> /usr/bin/dbaccess.sh",
"echo 'PGPASSWORD=$password psql -h $endpoint -U $user -d postgres' >> /usr/bin/dbaccess.sh",
"chmod 755 /usr/bin/dbaccess.sh"
);
// 認証情報へのアクセス許可
Secret.fromSecretNameV2(this, 'RDSSecret', props.dbSecretName).grantRead(host);
}
}
✅ BastionStack
Outputs:
BastionStack.BastionHostBastionHostxxxx = i-hoge
SSMを通した接続
SSMで接続するので、まずはコンソールでSession Managerのページを開き、セッションの開始を行う。
powershellから接続を行う。
aws ssm start-session --target i-hoge
つぎに、踏み台サーバからRDSへアクセスを行う。psqlを使用する。
接続情報は、踏み台サーバに最初からインストールされているaws-cliを使って取得する。
sh-4.2$ secret=$(aws secretsmanager get-secret-value --region ap-northeast-1 --secret-id db-secrets | jq .SecretString | jq fromjson)
sh-4.2$ user=$(echo $secret | jq -r .username)
sh-4.2$ password=$(echo $secret | jq -r .password)
sh-4.2$ endpoint=$(echo $secret | jq -r .host)
sh-4.2$ PGPASSWORD=$password psql -h $endpoint -U $user -d postgres
試しにpostgresqlのユーザを確認。
select usename from pg_user; -- ユーザを確認するSQL
\q -- psqlを終了
shellファイルにして簡単に接続できるようにしてみた。
./dbaccess.sh
ポートの絞り込み
現在の設定では、VPC内からのアクセスは全てのポートで受け入れる設定になっている。
まずはその設定を削除する。
- securityGroupPrivate.addIngressRule(Peer.ipv4(cidr), Port.allTcp());
./dbaccess.sh
を実行し、アクセスができなくなっていることを確認。
psql: could not connect to server: Connection timed out
Is the server running on host "aurorastack-clusterforauroradbhoge.cluster-fuga.ap-northeast-1.rds.amazonaws.com" (10.0.x.xx) and accepting
TCP/IP connections on port 5432?
つぎに、RDSの接続ポートのみ開く設定を行う。
+ securityGroupPrivate.addIngressRule(securityGroupPublic, Port.tcp(5432));
./dbaccess.sh
を実行し、アクセスができることを確認。
料金
1時間くらいで0.16$取られている。こまめに勘定してますね!
参考
公式
api reference - ec2 - VPC
NAT Gateway - 料金
Amazon Aurora Getting Started
長期サポート
Amazon Aurora DB インスタンスのインスタンスクラスを変更またはスケーリングするときにエラーが発生したのはなぜですか?
AuroraPostgresのRDSProxyはバージョン10と11のみサポート
RDS インスタンスに接続するように Lambda 関数を設定する方法を教えてください。
BastionHostLinux
AWS Systems Manager 経由で SSH トンネルを使用してプライベート VPC リソースにアクセスするにはどうすればよいですか?
そのほか
aws-cdkのv2が来たのでドキュメントを読んでみたメモ
【AWS CDK】VPC コンストラクタでまとめて作成したサブネットの CIDR ブロックでセキュリティグループの設定をする方法
CDK(TypeScript)で自分好みのVPCを作りたい
AWSCDKでVPCをデプロイする
実践!AWS CDK #2 VPC
実践!AWS CDK #19 Secrets Manager
実践!AWS CDK #22 RDS クラスター
実践!AWS CDK #23 RDS インスタンス
aws-cdkでAurora Postgresqlを起動するために試行錯誤した結果
AWS CDKを使用してPostgres12データベースクラスタをプロビジョニングできません
CDK Patternsを参考にしてRDS Proxyを利用したAPIをデプロイしてみた
[AWS SAM] Amazon RDS Proxy をつかって Amazon Aurora MySQL に接続するサーバーレスアプリケーションを構築する
Node.jsからPostgreSQLへコネクションプールを使った接続
今から始めるLambda⑦「LambdaからRDS(Aurora)に接続する 後編」
[Node.js + PostgreSQL]Node.js アプリから PostgreSQL DB に接続
Node-Postgres
を使ったときに、試行錯誤してできたPostgresクラス
VSCodeからPostgreSQLへ接続してSQL発行まで
CDKで作成した踏み台サーバにAWS SSM Session Manager と AWS Instance Connect を利用してSSH接続する
AWS CDK で3つ以上の AZ に Subnet を作成するにはアカウントとリージョンを必ず指定する必要があります
初めてのAWS Session Manager(SSM)
yumでPostgreSQLをインストールしてみよう
PostgreSQLの起動・接続・終了・停止コマンドとよく使うpsqlコマンド
Session Managerを使ってEC2の先にあるRDSに接続してみた