実践!AWS CDK #22 RDS クラスター

題字・息子たち
2021.07.26

はじめに

今回は RDS の Aurora DB クラスターを作成します。
クラスターの作成のみで DB インスタンスは作りません。(こいつは次回)

前回の記事はこちら。

AWS 構成図

1

設計

プロパティは以下の通り。

devio-stg-rds-cluster

項目
DB クラスター ID devio-stg-rds-cluster
エンジン Aurora MySQL
エンジンバージョン 5.7.mysql_aurora.2.10.0
キャパシティータイプ プロビジョニング済み: シングルマスター
DB 名 devio
マスターユーザー名 admin(Secrets Manager から取得)
マスターパスワード Secrets Manager で自動生成
暗号化 有効
ログエクスポート CloudWatch Logs: エラーログ
メンテナンスウィンドウ 月曜 4:30-5:00 JST(日曜 19:30-20:00 UTC)
自動バックアップ 有効(7 日)
バックアップウィンドウ 4:00-4:30 JST(19:00-19:30 UTC)
サブネットグループ devio-stg-rds-sng
クラスターパラメータグループ 前回作ったやつ
VPC セキュリティグループ devio-stg-sg-rds
データベースポート 3306

実装

RDS に関する処理を行うクラスに、ハイライト部分を追記しました。

lib/resource/rds.ts

import * as cdk from '@aws-cdk/core';
import { CfnDBSubnetGroup, CfnDBClusterParameterGroup, CfnDBParameterGroup, CfnDBCluster } from '@aws-cdk/aws-rds';
import { CfnSubnet, CfnSecurityGroup } from '@aws-cdk/aws-ec2';
import { CfnSecret } from '@aws-cdk/aws-secretsmanager';
import { Resource } from './abstract/resource';
import { SecretsManager, OSecretKey } from './secretsManager';

export class Rds extends Resource {
    public dbCluster: CfnDBCluster;

    private readonly subnetDb1a: CfnSubnet;
    private readonly subnetDb1c: CfnSubnet;
    private readonly securityGroupRds: CfnSecurityGroup;
    private readonly secretRdsCluster: CfnSecret;

    private static readonly databaseName = 'devio';

    constructor(
        subnetDb1a: CfnSubnet,
        subnetDb1c: CfnSubnet,
        securityGroupRds: CfnSecurityGroup,
        secretRdsCluster: CfnSecret
    ) {
        super();
        this.subnetDb1a = subnetDb1a;
        this.subnetDb1c = subnetDb1c;
        this.securityGroupRds = securityGroupRds;
        this.secretRdsCluster = secretRdsCluster;
    };

    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);
    }

    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: 'aurora-mysql',
            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;
    }
}

事前に Secrets Manager で作成しておいた マスターユーザー名マスターパスワード を以下のコードでクラスターのプロパティに設定しています。

masterUserPassword: SecretsManager.getDynamicReference(this.secretRdsCluster, OSecretKey.MasterUserPassword),
masterUsername: SecretsManager.getDynamicReference(this.secretRdsCluster, OSecretKey.MasterUsername),

[お詫び]

一貫性を保つため、これまでに作成した RDS 関連リソースの 論理 IDリソース名 を変更しました。
前回までの表記と異なる部分がありますのでご注意ください。すみませんが 最新のソースコード とさせてください。

変更箇所は こちら です。


メインのプログラムはこちら。
ハイライト部分を追記しました。

lib/devio-stack.ts

~ 省略 ~

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
    );
    rds.createResources(this);
  }
}

セキュリティグループと Secrets Manager の Cfn インスタンスを Rds クラスのコンストラクタに追加で渡しています。

テスト

テストコードはこちら。
ハイライト部分を追記しました。

test/resource/rds.test.ts

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()
    }));
});

以下を確認しています。

  • DB クラスターのリソースが 1 つあること
  • リソースのプロパティが正しいこと

確認

マネジメントコンソール上でリソースを確認してみましょう。

2

クラスターが作成されております。

インスタンスが紐付いていないのでエンドポイントのステータスは 作成中 ですが、こちらはインスタンスを作成すれば 利用可能 になるはずです。

3

設定したプロパティも正しく反映されています。

4

Secrets Manager から取得した マスターユーザー名マスターパスワード も問題ありません。

5

ヨシ!

CloudFormation 版

今回のコードを CFn で書くと以下のようになります。

RdsDbCluster:
  Type: AWS::RDS::DBCluster
  Properties:
    Engine: aurora-mysql
    BackupRetentionPeriod: 7
    DatabaseName: devio
    DBClusterIdentifier: devio-stg-rds-cluster
    DBClusterParameterGroupName:
      Ref: RdsDbClusterParameterGroup
    DBSubnetGroupName:
      Ref: RdsDbSubnetGroup
    EnableCloudwatchLogsExports:
      - error
    EngineMode: provisioned
    EngineVersion: 5.7.mysql_aurora.2.10.0
    MasterUsername:
      Fn::Join:
        - ""
        - - "{{resolve:secretsmanager:"
          - Ref: SecretRdsCluster
          - :SecretString:MasterUsername}}
    MasterUserPassword:
      Fn::Join:
        - ""
        - - "{{resolve:secretsmanager:"
          - Ref: SecretRdsCluster
          - :SecretString:MasterUserPassword}}
    Port: 3306
    PreferredBackupWindow: 19:00-19:30
    PreferredMaintenanceWindow: sun:19:30-sun:20:00
    StorageEncrypted: true
    VpcSecurityGroupIds:
      - Fn::GetAtt:
          - SecurityGroupRds
          - GroupId

設定したプロパティが多めなのでちょいと長いですね。

GitHub

今回のソースコードは コチラ です。

おわりに

DB クラスターの作成が完了しました。
通常はクラスターのみという構成はなかなかないと思いますが、今回のような単体での作成もできるんですね。

次回はこのクラスターにインスタンスを紐付けていきましょう。

リンク