
実践!AWS CDK #23 RDS インスタンス
この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
はじめに
今回は最後のリソース、RDS の DB インスタンスを作成します。
前回作成した DB クラスターに紐付けます。
前回の記事はこちら。
AWS 構成図
今回でこの構成が完成します。

2 台の DB インスタンスを立ち上げ、そのうち 1 台が Aurora レプリカとなります。
設計
プロパティは以下の通り。
devio-stg-rds-instance-1a
| 項目 | 値 | 
|---|---|
| Availability Zone | ap-northeast-1a | 
| エンジン | Aurora MySQL | 
| DB インスタンスクラス | db.r5.large | 
| DB クラスター ID | devio-stg-rds-cluster | 
| サブネットグループ | devio-stg-rds-sng | 
| パラメータグループ | 前々回作ったやつ | 
| モニタリング詳細度 | 60 秒 | 
| モニタリングロール | devio-stg-role-rds | 
| パフォーマンスインサイト | 有効 | 
| パフォーマンスインサイト保持期間 | 7 日 | 
| マイナーバージョン自動アップグレード | 無効 | 
| メンテナンスウィンドウ | 月曜 5:00-5:30 JST(日曜 20:00-20:30 UTC) | 
devio-stg-rds-instance-1c
| 項目 | 値 | 
|---|---|
| Availability Zone | ap-northeast-1c | 
| エンジン | Aurora MySQL | 
| DB インスタンスクラス | db.r5.large | 
| DB クラスター ID | devio-stg-rds-cluster | 
| サブネットグループ | devio-stg-rds-sng | 
| パラメータグループ | 前々回作ったやつ | 
| モニタリング詳細度 | 60 秒 | 
| モニタリングロール | devio-stg-role-rds | 
| パフォーマンスインサイト | 有効 | 
| パフォーマンスインサイト保持期間 | 7 日 | 
| マイナーバージョン自動アップグレード | 無効 | 
| メンテナンスウィンドウ | 月曜 5:30-6:00 JST(日曜 20:30-21:00 UTC) | 
2 つのインスタンスで異なる部分は AZ とメンテナンスウィンドウくらいです。
メンテナンスウィンドウに関してはクラスターとインスタンスそれぞれで 30 分ずつ時間をずらしました。
実装
RDS に関する処理を行うクラスに、ハイライト部分を追記しました。
import * as cdk from '@aws-cdk/core';
import { CfnDBSubnetGroup, CfnDBClusterParameterGroup, CfnDBParameterGroup, CfnDBCluster, CfnDBInstance } from '@aws-cdk/aws-rds';
import { CfnSubnet, CfnSecurityGroup } from '@aws-cdk/aws-ec2';
import { CfnSecret } from '@aws-cdk/aws-secretsmanager';
import { CfnRole } from '@aws-cdk/aws-iam';
import { Resource } from './abstract/resource';
import { SecretsManager, OSecretKey } from './secretsManager';
interface InstanceInfo {
    readonly id: string;
    readonly availabilityZone: string;
    readonly preferredMaintenanceWindow: string;
    readonly resourceName: string;
    readonly assign: (instance: CfnDBInstance) => void;
}
export class Rds extends Resource {
    public dbCluster: CfnDBCluster;
    public dbInstance1a: CfnDBInstance;
    public dbInstance1c: CfnDBInstance;
    private static readonly engine = 'aurora-mysql';
    private static readonly databaseName = 'devio';
    private static readonly dbInstanceClass = 'db.r5.large';
    private readonly subnetDb1a: CfnSubnet;
    private readonly subnetDb1c: CfnSubnet;
    private readonly securityGroupRds: CfnSecurityGroup;
    private readonly secretRdsCluster: CfnSecret;
    private readonly iamRoleRds: CfnRole;
    private readonly instances: InstanceInfo[] = [
        {
            id: 'RdsDbInstance1a',
            availabilityZone: 'ap-northeast-1a',
            preferredMaintenanceWindow: 'sun:20:00-sun:20:30',
            resourceName: 'rds-instance-1a',
            assign: instance => this.dbInstance1a = instance
        },
        {
            id: 'RdsDbInstance1c',
            availabilityZone: 'ap-northeast-1c',
            preferredMaintenanceWindow: 'sun:20:30-sun:21:00',
            resourceName: 'rds-instance-1c',
            assign: instance => this.dbInstance1c = instance
        }
    ];
    constructor(
        subnetDb1a: CfnSubnet,
        subnetDb1c: CfnSubnet,
        securityGroupRds: CfnSecurityGroup,
        secretRdsCluster: CfnSecret,
        iamRoleRds: CfnRole
    ) {
        super();
        this.subnetDb1a = subnetDb1a;
        this.subnetDb1c = subnetDb1c;
        this.securityGroupRds = securityGroupRds;
        this.secretRdsCluster = secretRdsCluster;
        this.iamRoleRds = iamRoleRds;
    };
    createResources(scope: cdk.Construct) {
        const subnetGroup = this.createSubnetGroup(scope);
        const clusterParameterGroup = this.createClusterParameterGroup(scope);
        const parameterGroup = this.createParameterGroup(scope);
        this.dbCluster = this.createCluster(scope, subnetGroup, clusterParameterGroup);
        for (const instanceInfo of this.instances) {
            const instance = this.createInstance(scope, instanceInfo, this.dbCluster, subnetGroup, parameterGroup);
            instanceInfo.assign(instance);
        }
    }
    private createSubnetGroup(scope: cdk.Construct): CfnDBSubnetGroup {
        const subnetGroup = new CfnDBSubnetGroup(scope, 'RdsDbSubnetGroup', {
            dbSubnetGroupDescription: 'Subnet Group for RDS',
            subnetIds: [this.subnetDb1a.ref, this.subnetDb1c.ref],
            dbSubnetGroupName: this.createResourceName(scope, 'rds-sng')
        });
        return subnetGroup;
    }
    private createClusterParameterGroup(scope: cdk.Construct): CfnDBClusterParameterGroup {
        const clusterParameterGroup = new CfnDBClusterParameterGroup(scope, 'RdsDbClusterParameterGroup', {
            description: 'Cluster Parameter Group for RDS',
            family: 'aurora-mysql5.7',
            parameters: { time_zone: 'UTC' }
        });
        return clusterParameterGroup;
    }
    private createParameterGroup(scope: cdk.Construct): CfnDBParameterGroup {
        const parameterGroup = new CfnDBParameterGroup(scope, 'RdsDbParameterGroup', {
            description: 'Parameter Group for RDS',
            family: 'aurora-mysql5.7'
        });
        return parameterGroup;
    }
    private createCluster(scope: cdk.Construct, subnetGroup: CfnDBSubnetGroup, clusterParameterGroup: CfnDBClusterParameterGroup): CfnDBCluster {
        const cluster = new CfnDBCluster(scope, 'RdsDbCluster', {
            engine: Rds.engine,
            backupRetentionPeriod: 7,
            databaseName: Rds.databaseName,
            dbClusterIdentifier: this.createResourceName(scope, 'rds-cluster'),
            dbClusterParameterGroupName: clusterParameterGroup.ref,
            dbSubnetGroupName: subnetGroup.ref,
            enableCloudwatchLogsExports: ['error'],
            engineMode: 'provisioned',
            engineVersion: '5.7.mysql_aurora.2.10.0',
            masterUserPassword: SecretsManager.getDynamicReference(this.secretRdsCluster, OSecretKey.MasterUserPassword),
            masterUsername: SecretsManager.getDynamicReference(this.secretRdsCluster, OSecretKey.MasterUsername),
            port: 3306,
            preferredBackupWindow: '19:00-19:30',
            preferredMaintenanceWindow: 'sun:19:30-sun:20:00',
            storageEncrypted: true,
            vpcSecurityGroupIds: [this.securityGroupRds.attrGroupId]
        });
        return cluster;
    }
    private createInstance(scope: cdk.Construct, instanceInfo: InstanceInfo, cluster: CfnDBCluster, subnetGroup: CfnDBSubnetGroup, parameterGroup: CfnDBParameterGroup): CfnDBInstance {
        const instance = new CfnDBInstance(scope, instanceInfo.id, {
            dbInstanceClass: Rds.dbInstanceClass,
            autoMinorVersionUpgrade: false,
            availabilityZone: instanceInfo.availabilityZone,
            dbClusterIdentifier: cluster.ref,
            dbInstanceIdentifier: this.createResourceName(scope, instanceInfo.resourceName),
            dbParameterGroupName: parameterGroup.ref,
            dbSubnetGroupName: subnetGroup.ref,
            enablePerformanceInsights: true,
            engine: Rds.engine,
            monitoringInterval: 60,
            monitoringRoleArn: this.iamRoleRds.attrArn,
            performanceInsightsRetentionPeriod: 7,
            preferredMaintenanceWindow: instanceInfo.preferredMaintenanceWindow,
        });
        return instance;
    }
}
特に複雑なことはしておらず。いつものようにリソースを作成するためのメソッドを追加しています。
メインのプログラムはこちら。
ハイライト部分を追記しました。
~ 省略 ~
export class DevioStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);
    ~ 省略 ~
    // RDS
    const rds = new Rds(
      subnet.db1a,
      subnet.db1c,
      securityGroup.rds,
      secretsManager.secretRdsCluster,
      iamRole.rds
    );
    rds.createResources(this);
  }
}
モニタリングロールを Rds クラスに渡すためにコンストラクタにパラメータを追加しました。
テスト
テストコードはこちら。
ハイライト部分を追記しました。
import { expect, countResources, haveResource, anything } from '@aws-cdk/assert';
import * as cdk from '@aws-cdk/core';
import * as Devio from '../../lib/devio-stack';
test('Rds', () => {
    const app = new cdk.App();
    const stack = new Devio.DevioStack(app, 'DevioStack');
    expect(stack).to(countResources('AWS::RDS::DBSubnetGroup', 1));
    expect(stack).to(haveResource('AWS::RDS::DBSubnetGroup', {
        DBSubnetGroupDescription: 'Subnet Group for RDS',
        SubnetIds: anything(),
        DBSubnetGroupName: 'undefined-undefined-rds-sng'
    }));
    expect(stack).to(countResources('AWS::RDS::DBClusterParameterGroup', 1));
    expect(stack).to(haveResource('AWS::RDS::DBClusterParameterGroup', {
        Description: 'Cluster Parameter Group for RDS',
        Family: 'aurora-mysql5.7',
        Parameters: { time_zone: 'UTC' }
    }));
    expect(stack).to(countResources('AWS::RDS::DBParameterGroup', 1));
    expect(stack).to(haveResource('AWS::RDS::DBParameterGroup', {
        Description: 'Parameter Group for RDS',
        Family: 'aurora-mysql5.7'
    }));
    expect(stack).to(countResources('AWS::RDS::DBCluster', 1));
    expect(stack).to(haveResource('AWS::RDS::DBCluster', {
        Engine: 'aurora-mysql',
        BackupRetentionPeriod: 7,
        DatabaseName: 'devio',
        DBClusterIdentifier: 'undefined-undefined-rds-cluster',
        DBClusterParameterGroupName: anything(),
        DBSubnetGroupName: anything(),
        EnableCloudwatchLogsExports: ['error'],
        EngineMode: 'provisioned',
        EngineVersion: '5.7.mysql_aurora.2.10.0',
        MasterUsername: anything(),
        MasterUserPassword: anything(),
        Port: 3306,
        PreferredBackupWindow: '19:00-19:30',
        PreferredMaintenanceWindow: 'sun:19:30-sun:20:00',
        StorageEncrypted: true,
        VpcSecurityGroupIds: anything()
    }));
    expect(stack).to(countResources('AWS::RDS::DBInstance', 2));
    expect(stack).to(haveResource('AWS::RDS::DBInstance', {
        DBInstanceClass: 'db.r5.large',
        AutoMinorVersionUpgrade: false,
        AvailabilityZone: 'ap-northeast-1a',
        DBClusterIdentifier: anything(),
        DBInstanceIdentifier: 'undefined-undefined-rds-instance-1a',
        DBParameterGroupName: anything(),
        DBSubnetGroupName: anything(),
        EnablePerformanceInsights: true,
        Engine: 'aurora-mysql',
        MonitoringInterval: 60,
        MonitoringRoleArn: anything(),
        PerformanceInsightsRetentionPeriod: 7,
        PreferredMaintenanceWindow: 'sun:20:00-sun:20:30',
    }));
    expect(stack).to(haveResource('AWS::RDS::DBInstance', {
        DBInstanceClass: 'db.r5.large',
        AutoMinorVersionUpgrade: false,
        AvailabilityZone: 'ap-northeast-1c',
        DBClusterIdentifier: anything(),
        DBInstanceIdentifier: 'undefined-undefined-rds-instance-1c',
        DBParameterGroupName: anything(),
        DBSubnetGroupName: anything(),
        EnablePerformanceInsights: true,
        Engine: 'aurora-mysql',
        MonitoringInterval: 60,
        MonitoringRoleArn: anything(),
        PerformanceInsightsRetentionPeriod: 7,
        PreferredMaintenanceWindow: 'sun:20:30-sun:21:00',
    }));
});
以下を確認しています。
- DB インスタンスのリソースが 2 つあること
- 各リソースのプロパティが正しいこと
確認
マネジメントコンソール上でリソースを確認してみましょう。

前回作成したクラスター配下に、今回作成したインスタンス 2 台が紐付いています。
前回は 作成中 だった各エンドポイントのステータスも 利用可能 に。

パフォーマンスインサイトも確認可能です。


続いて(少し長くなりますが)EC2 インスタンスからの疎通確認を行います。
MySQL クライアントのインストール
SSM のセッションマネージャーから EC2 にログインし、公式のインストール手順 を参考に MySQL のクライアントをインストールします。
以下のコマンドを実行し、MySQL の yum リポジトリをシステムのリポジトリリストに追加します。
$ sudo yum -y install https://dev.mysql.com/get/mysql80-community-release-el7-3.noarch.rpm

次に現在設定されているサブリポジトリのバージョンを確認します。
$ yum repolist all | grep mysql

現状バージョン 8.0 が有効化されているので、こちらを無効化し 5.7 を有効化します。
$ sudo yum-config-manager --disable mysql80-community $ sudo yum-config-manager --enable mysql57-community
再度ステータスの確認。
$ yum repolist all | grep mysql

MySQL クライアントをインストールします。
$ sudo yum -y install mysql-community-client

インストールできました。

ここまでの MySQL クライアントインストール処理も EC2 の UserData に追加しておきます。
#!/bin/bash # Apache のインストール sudo yum -y install httpd sudo systemctl enable httpd sudo systemctl start httpd # MySQL クライアントのインストール sudo yum -y install https://dev.mysql.com/get/mysql80-community-release-el7-3.noarch.rpm sudo yum-config-manager --disable mysql80-community sudo yum-config-manager --enable mysql57-community sudo yum -y install mysql-community-client
RDS インスタンスへの接続
クラスター(ライター)エンドポイントをターゲットに以下のコマンドを実行します。(ここでのクラスターエンドポイントは devio-stg-rds-cluster.cluster-cndvsdcsdzja.ap-northeast-1.rds.amazonaws.com)
パスワードは Secrets Manager に保存されているものを入力します。
$ mysql -h devio-stg-rds-cluster.cluster-cndvsdcsdzja.ap-northeast-1.rds.amazonaws.com -P 3306 -u admin -p

接続できました!
クラスター作成時に指定した DB devio も作成されています。

DB devio に user テーブルを作成してみます。
$ create table devio.user (id int, name varchar(8));
テーブルの作成(書き込み)も OK ですね。


なお RDS への接続時にリーダーエンドポイントを指定した場合、書き込み処理は行なえません。その名の通り、書き込み処理を行う場合はクラスター(ライター)エンドポイントを、読み込み処理のみを行う場合はリーダーエンドポイントを指定するようにしましょう。(ここでのリーダーエンドポイントは devio-stg-rds-cluster.cluster-ro-cndvsdcsdzja.ap-northeast-1.rds.amazonaws.com)

CloudFormation 版
今回のコードを CFn で書くと以下のようになります。
RdsDbInstance1a:
  Type: AWS::RDS::DBInstance
  Properties:
    DBInstanceClass: db.r5.large
    AutoMinorVersionUpgrade: false
    AvailabilityZone: ap-northeast-1a
    DBClusterIdentifier:
      Ref: RdsDbCluster
    DBInstanceIdentifier: devio-stg-rds-instance-1a
    DBParameterGroupName:
      Ref: RdsDbParameterGroup
    DBSubnetGroupName:
      Ref: RdsDbSubnetGroup
    EnablePerformanceInsights: true
    Engine: aurora-mysql
    MonitoringInterval: 60
    MonitoringRoleArn:
      Fn::GetAtt:
        - RoleRds
        - Arn
    PerformanceInsightsRetentionPeriod: 7
    PreferredMaintenanceWindow: sun:20:00-sun:20:30
RdsDbInstance1c:
  Type: AWS::RDS::DBInstance
  Properties:
    DBInstanceClass: db.r5.large
    AutoMinorVersionUpgrade: false
    AvailabilityZone: ap-northeast-1c
    DBClusterIdentifier:
      Ref: RdsDbCluster
    DBInstanceIdentifier: devio-stg-rds-instance-1c
    DBParameterGroupName:
      Ref: RdsDbParameterGroup
    DBSubnetGroupName:
      Ref: RdsDbSubnetGroup
    EnablePerformanceInsights: true
    Engine: aurora-mysql
    MonitoringInterval: 60
    MonitoringRoleArn:
      Fn::GetAtt:
        - RoleRds
        - Arn
    PerformanceInsightsRetentionPeriod: 7
    PreferredMaintenanceWindow: sun:20:30-sun:21:00
GitHub
今回のソースコードは コチラ です。
おわりに
今回で当初目標としていた構成を AWS CDK ですべて実装することができました!
プログラムを書きながら学べたことがとても多く、非常に充実した取り組みでした。
本シリーズはこの回をもって 一部完 とさせていただきます。
以降はもう少しのんびりとした周期で、構成の拡張 や AWS CDK の小ワザ などをお伝えしていきたいと思います。
ありがとうございました。
リンク
- class CfnDBInstance (construct) | AWS CDK API Reference
- AWS::RDS::DBInstance | AWS CloudFormation User Guide
- DB インスタンスクラス | Amazon Aurora User Guide
- Amazon Aurora でのレプリケーション | Amazon Aurora User Guide
- MySQL データベースエンジンを実行している DB インスタンスへの接続 | Amazon RDS User Guide
- Installing MySQL on Linux Using the MySQL Yum Repository | MySQL 8.0 Reference Manual
- MySQL Yum Repository | MySQL Community Downloads
- Amazon Linux2にMySQLクライアントを4バージョンいれてみた。Ansible Playbookを添えて | DevelopersIO













