LoginSignup
3
2

More than 1 year has passed since last update.

aws-cdkでVPCを作ってRDSと踏み台サーバを設定したメモ

Last updated at Posted at 2021-12-30

概要

Amazon Auroraの開始方法CDK Patternsを参考にしてRDS Proxyを利用したAPIをデプロイしてみたをみながら、CDK v2を使って試してみる。今回の作成範囲は、RDSと踏み台のみとする。
なお、RDSを作ってもたもたしていると50円くらいかかる可能性がある。
踏み台のポートフォワーディングについては別記事とした。

ソースコード

今回作成するアーキテクチャ

image.png

環境

  • 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の設定を行う。

image.png

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ファイルに環境変数として設定している。

cdk/bin/vpc.ts
#!/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,
});
cdk/.env
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のページを開き、セッションの開始を行う。
image.png

powershellから接続を行う。

aws ssm start-session --target i-hoge

image.png
踏み台サーバにログインできるところまで確認。

つぎに、踏み台サーバから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を終了

image.png

shellファイルにして簡単に接続できるようにしてみた。

./dbaccess.sh

image.png

ポートの絞り込み

現在の設定では、VPC内からのアクセスは全てのポートで受け入れる設定になっている。
まずはその設定を削除する。

cdk/lib/vpc-stack.ts
-    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の接続ポートのみ開く設定を行う。

cdk/lib/vpc-stack.ts
+ securityGroupPrivate.addIngressRule(securityGroupPublic, Port.tcp(5432));

./dbaccess.shを実行し、アクセスができることを確認。

料金

1時間くらいで0.16$取られている。こまめに勘定してますね!

image.png

参考

公式

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に接続してみた

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2