CloudFormationでgp3のRDS DBインスタンスを作成してみた

これが CLP やってみる
2022.11.11

CloudFormationでもgp3を指定できるのかな

こんにちは、のんピ(@non____97)です。

先日、RDSがgp3をサポートしましたね。

私は基本的に構築はAWS CDKで行うので、CloudFormationもgp3をサポートしているのか非常に気になりました。

AWS::RDS::DBInstanceを確認してみると、以下のようにストレージタイプはstandardgp2io1のみで、gp3はサポートしていないようです。

StorageType

Specifies the storage type to be associated with the DB instance.

Valid values: standard | gp2 | io1

The standard value is also known as magnetic.

If you specify io1, you must also include a value for the Iops parameter.

Default: io1 if the Iops parameter is specified, otherwise gp2

For more information, see Amazon RDS DB Instance Storage in the Amazon RDS User Guide.

Amazon Aurora

Not applicable. Aurora data is stored in the cluster volume, which is a single, virtual volume that uses solid state drives (SSDs).

Required: No

Type: String

Update requires: Some interruptions

AWS::RDS::DBInstance - AWS CloudFormation

これは悲しいですね。

ただ、どうしても私は諦めきれませんでした。

そんな私の足掻きをご覧ください。

いきなりまとめ

  • ドキュメントには記載されていないが、CloudFormationでgp3のRDS DBインスタンスを作成できる
  • (少なくともRDSの)CloudFormationのコードはJavaで書かれている

CloudFormationのソースコードを眺めてみる

サポートされる/されないは別として、ドキュメントに書かれていなくても実は動かせるなんてことはよくある話です。

どうしてもCloudFormationで設定したかったので、GitHubでRDS DBインスタンスのソースコードを見てみます。

すると、[DBInstance] Add GP3 StorageThroughput Attribute #353というPRを見つけました。

このPRでStorageThroughputをパラメーターとして受け付けるようになったようです。

さらに、このPRの変更履歴を見てみると、以下のようにAWS SDKのバージョンを2.17.267から2.18.11に変更していました。

aws-rds-dbinstance/pom.xml

        <dependency>
            <groupId>software.amazon.awssdk</groupId>
            <artifactId>rds</artifactId>
-            <version>2.17.267</version>
+            <version>2.18.11</version>
        </dependency>
        <dependency>
            <groupId>software.amazon.awssdk</groupId>

他のソースコードを見る限りCloudFormationはJavaで書かれているようです。AWS SDK for JavaのAPI Referenceを確認してみましょう。

AWS SDK for Javaのバージョンが2.18.14にはなりますが、確かにstorageType()は受け取る値としてgp3をサポートしていますね。

storageType

public final String storageType() Specifies the storage type to be associated with the DB instance.

Valid values: gp2 | gp3 | io1 | standard

If you specify io1 or gp3, you must also include a value for the Iops parameter.

Default: io1 if the Iops parameter is specified, otherwise gp2

CreateDbInstanceRequest (AWS SDK for Java - 2.18.14)

加えて、CloudFormationのJavaのソースコードを読んでも「standardgp2io1以外のストレージタイプは拒否する」といった処理はしていないようでした。

なんだかgp3を指定できる気がしてきました。

やってみた

それではRDSのストレージタイプにgp3を指定してみましょう。

今回はCloudFormationではなく、AWS CDKで定義します。

2022/11/10現在のAWS CDKの最新バージョンである2.50.0のストレージタイプのenumであるStorageTypeはgp3をサポートしていませんでした。そのため、escape hatchesで指定します。

./lib/rds-stack.ts

import * as cdk from "aws-cdk-lib";
import { Construct } from "constructs";

export class RdsStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // VPC
    const vpc = new cdk.aws_ec2.Vpc(this, "VPC", {
      ipAddresses: cdk.aws_ec2.IpAddresses.cidr("10.0.1.0/24"),
      enableDnsHostnames: true,
      enableDnsSupport: true,
      natGateways: 0,
      maxAzs: 2,
      subnetConfiguration: [
        {
          name: "Public",
          subnetType: cdk.aws_ec2.SubnetType.PUBLIC,
          cidrMask: 26,
        },
        {
          name: "Isolated",
          subnetType: cdk.aws_ec2.SubnetType.PRIVATE_ISOLATED,
          cidrMask: 26,
        },
      ],
    });

    // RDS Subnet Group
    const subnetGroup = new cdk.aws_rds.SubnetGroup(this, "RDS Subnet Group", {
      vpc,
      description: "RDS Subnet Group",
      subnetGroupName: "rds-subgrp",
      vpcSubnets: {
        subnetType: cdk.aws_ec2.SubnetType.PRIVATE_ISOLATED,
      },
    });

    // RDS DB Instance
    const dbInstance = new cdk.aws_rds.DatabaseInstance(
      this,
      "RDS DB Instance",
      {
        engine: cdk.aws_rds.DatabaseInstanceEngine.postgres({
          version: cdk.aws_rds.PostgresEngineVersion.VER_14_4,
        }),
        vpc,
        allocatedStorage: 20,
        availabilityZone: vpc.availabilityZones[0],
        backupRetention: cdk.Duration.days(0),
        instanceIdentifier: "rds-db-instance",
        instanceType: cdk.aws_ec2.InstanceType.of(
          cdk.aws_ec2.InstanceClass.T3,
          cdk.aws_ec2.InstanceSize.MICRO
        ),
        multiAz: false,
        port: 5432,
        publiclyAccessible: false,
        storageEncrypted: true,
        storageType: cdk.aws_rds.StorageType.GP2,
        subnetGroup,
      }
    );

    // Set DB Instance storage type to gp3
    const cfnDbInstance = dbInstance.node
      .defaultChild as cdk.aws_rds.CfnDBInstance;

    cfnDbInstance.addPropertyOverride("StorageType", "gp3");
  }
}

こちらのコードでデプロイすると、確かにストレージタイプがgp3になっており、IOPS、スループットも表示されていました。

作成されたDBインスタンスの確認

AWS CLIでRDS DBインスタンスの設定を確認すると以下のようになります。

$ aws rds describe-db-instances
{
    "DBInstances": [
        {
            "DBInstanceIdentifier": "rds-db-instance",
            "DBInstanceClass": "db.t3.micro",
            "Engine": "postgres",
            "DBInstanceStatus": "available",
            "MasterUsername": "postgres",
            "Endpoint": {
                "Address": "rds-db-instance.cicjym7lykmq.us-east-1.rds.amazonaws.com",
                "Port": 5432,
                "HostedZoneId": "Z2R2ITUGPM61AM"
            },
            "AllocatedStorage": 20,
            "InstanceCreateTime": "2022-11-10T11:53:47.878000+00:00",
            "PreferredBackupWindow": "07:00-07:30",
            "BackupRetentionPeriod": 0,
            "DBSecurityGroups": [],
            "VpcSecurityGroups": [
                {
                    "VpcSecurityGroupId": "sg-0a5833c43b7305a67",
                    "Status": "active"
                }
            ],
            "DBParameterGroups": [
                {
                    "DBParameterGroupName": "default.postgres14",
                    "ParameterApplyStatus": "in-sync"
                }
            ],
            "AvailabilityZone": "us-east-1a",
            "DBSubnetGroup": {
                "DBSubnetGroupName": "rds-subgrp",
                "DBSubnetGroupDescription": "RDS Subnet Group",
                "VpcId": "vpc-0b760a6bfa7ddfbf4",
                "SubnetGroupStatus": "Complete",
                "Subnets": [
                    {
                        "SubnetIdentifier": "subnet-07246e0e3e1c6a1dc",
                        "SubnetAvailabilityZone": {
                            "Name": "us-east-1b"
                        },
                        "SubnetOutpost": {},
                        "SubnetStatus": "Active"
                    },
                    {
                        "SubnetIdentifier": "subnet-091b4b6aae89acbe2",
                        "SubnetAvailabilityZone": {
                            "Name": "us-east-1a"
                        },
                        "SubnetOutpost": {},
                        "SubnetStatus": "Active"
                    }
                ]
            },
            "PreferredMaintenanceWindow": "thu:08:47-thu:09:17",
            "PendingModifiedValues": {},
            "MultiAZ": false,
            "EngineVersion": "14.4",
            "AutoMinorVersionUpgrade": true,
            "ReadReplicaDBInstanceIdentifiers": [],
            "LicenseModel": "postgresql-license",
            "Iops": 3000,
            "OptionGroupMemberships": [
                {
                    "OptionGroupName": "default:postgres-14",
                    "Status": "in-sync"
                }
            ],
            "PubliclyAccessible": false,
            "StorageType": "gp3",
            "DbInstancePort": 0,
            "StorageEncrypted": true,
            "KmsKeyId": "arn:aws:kms:us-east-1:<AWSアカウントID>:key/4544a543-32d6-4103-a1df-313116669a3e",
            "DbiResourceId": "db-WEKRXTLQALPG5SHWDSS2PYMZZA",
            "CACertificateIdentifier": "rds-ca-2019",
            "DomainMemberships": [],
            "CopyTagsToSnapshot": true,
            "MonitoringInterval": 0,
            "DBInstanceArn": "arn:aws:rds:us-east-1:<AWSアカウントID>:db:rds-db-instance",
            "IAMDatabaseAuthenticationEnabled": false,
            "PerformanceInsightsEnabled": false,
            "DeletionProtection": false,
            "AssociatedRoles": [],
            "TagList": [
                {
                    "Key": "aws:cloudformation:stack-id",
                    "Value": "arn:aws:cloudformation:us-east-1:<AWSアカウントID>:stack/RdsStack/a1d78750-5c27-11ed-a166-126e4ccc5485"
                },
                {
                    "Key": "aws:cloudformation:stack-name",
                    "Value": "RdsStack"
                },
                {
                    "Key": "aws:cloudformation:logical-id",
                    "Value": "RDSDBInstance1137C508"
                }
            ],
            "CustomerOwnedIpEnabled": false,
            "ActivityStreamStatus": "stopped",
            "BackupTarget": "region",
            "NetworkType": "IPV4",
            "StorageThroughput": 125
        }
    ]
}

スループットとIOPSを変更してみる

せっかくgp3なので、スループットとIOPSをベースラインのものから変更してみましょう。

RDSのgp3はストレージサイズが400 GiB以上からスループットとIOPSを変更できるようになります。今回は以下のように設定を変更します。

  • ストレージサイズ : 20 GiB -> 400 GiB
  • スループット : 125 MiBps -> 600 MiBps
  • IOPS : 3000 IOPS -> 15,000 IOPS

AWS CDKのコードは以下の通りです。

./lib/rds-stack.ts

import * as cdk from "aws-cdk-lib";
import { Construct } from "constructs";

export class RdsStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // VPC
    const vpc = new cdk.aws_ec2.Vpc(this, "VPC", {
      ipAddresses: cdk.aws_ec2.IpAddresses.cidr("10.0.1.0/24"),
      enableDnsHostnames: true,
      enableDnsSupport: true,
      natGateways: 0,
      maxAzs: 2,
      subnetConfiguration: [
        {
          name: "Public",
          subnetType: cdk.aws_ec2.SubnetType.PUBLIC,
          cidrMask: 26,
        },
        {
          name: "Isolated",
          subnetType: cdk.aws_ec2.SubnetType.PRIVATE_ISOLATED,
          cidrMask: 26,
        },
      ],
    });

    // RDS Subnet Group
    const subnetGroup = new cdk.aws_rds.SubnetGroup(this, "RDS Subnet Group", {
      vpc,
      description: "RDS Subnet Group",
      subnetGroupName: "rds-subgrp",
      vpcSubnets: {
        subnetType: cdk.aws_ec2.SubnetType.PRIVATE_ISOLATED,
      },
    });

    // RDS DB Instance
    const dbInstance = new cdk.aws_rds.DatabaseInstance(
      this,
      "RDS DB Instance",
      {
        engine: cdk.aws_rds.DatabaseInstanceEngine.postgres({
          version: cdk.aws_rds.PostgresEngineVersion.VER_14_4,
        }),
        vpc,
        allocatedStorage: 400,
        availabilityZone: vpc.availabilityZones[0],
        backupRetention: cdk.Duration.days(0),
        instanceIdentifier: "rds-db-instance",
        instanceType: cdk.aws_ec2.InstanceType.of(
          cdk.aws_ec2.InstanceClass.T3,
          cdk.aws_ec2.InstanceSize.MICRO
        ),
        multiAz: false,
        port: 5432,
        publiclyAccessible: false,
        storageEncrypted: true,
        storageType: cdk.aws_rds.StorageType.GP2,
        subnetGroup,
      }
    );

    // Set DB Instance storage type to gp3
    const cfnDbInstance = dbInstance.node
      .defaultChild as cdk.aws_rds.CfnDBInstance;

    cfnDbInstance.addPropertyOverride("StorageType", "gp3");

    // Storage Throughput
    cfnDbInstance.addPropertyOverride("StorageThroughput", "600");

    // IOPS
    cfnDbInstance.addPropertyOverride("Iops", "15000");
  }
}

npx cdk diffで変更の差分を確認してみます。

>  (master ⚡) npx cdk diff
Stack RdsStack
Resources
[~] AWS::RDS::DBInstance RDS DB Instance RDSDBInstance1137C508
 ├─ [~] AllocatedStorage
 │   ├─ [-] 20
 │   └─ [+] 400
 ├─ [+] Iops
 │   └─ 15000
 └─ [+] StorageThroughput
     └─ 600

意図した通りの変更が行われそうですね。

それでは、こちらのコードでデプロイします。

デプロイ後、RDSのコンソールを確認すると、IOPSとスループットが指定したものに変更されていました。

更新後のDBインスタンス

AWS CLIでRDS DBインスタンスの設定を確認すると以下のようになります。

$ aws rds describe-db-instances
{
    "DBInstances": [
        {
            "DBInstanceIdentifier": "rds-db-instance",
            "DBInstanceClass": "db.t3.micro",
            "Engine": "postgres",
            "DBInstanceStatus": "available",
            "MasterUsername": "postgres",
            "Endpoint": {
                "Address": "rds-db-instance.cicjym7lykmq.us-east-1.rds.amazonaws.com",
                "Port": 5432,
                "HostedZoneId": "Z2R2ITUGPM61AM"
            },
            "AllocatedStorage": 400,
            "InstanceCreateTime": "2022-11-10T11:53:47.878000+00:00",
            "PreferredBackupWindow": "07:00-07:30",
            "BackupRetentionPeriod": 0,
            "DBSecurityGroups": [],
            "VpcSecurityGroups": [
                {
                    "VpcSecurityGroupId": "sg-0a5833c43b7305a67",
                    "Status": "active"
                }
            ],
            "DBParameterGroups": [
                {
                    "DBParameterGroupName": "default.postgres14",
                    "ParameterApplyStatus": "in-sync"
                }
            ],
            "AvailabilityZone": "us-east-1a",
            "DBSubnetGroup": {
                "DBSubnetGroupName": "rds-subgrp",
                "DBSubnetGroupDescription": "RDS Subnet Group",
                "VpcId": "vpc-0b760a6bfa7ddfbf4",
                "SubnetGroupStatus": "Complete",
                "Subnets": [
                    {
                        "SubnetIdentifier": "subnet-07246e0e3e1c6a1dc",
                        "SubnetAvailabilityZone": {
                            "Name": "us-east-1b"
                        },
                        "SubnetOutpost": {},
                        "SubnetStatus": "Active"
                    },
                    {
                        "SubnetIdentifier": "subnet-091b4b6aae89acbe2",
                        "SubnetAvailabilityZone": {
                            "Name": "us-east-1a"
                        },
                        "SubnetOutpost": {},
                        "SubnetStatus": "Active"
                    }
                ]
            },
            "PreferredMaintenanceWindow": "thu:08:47-thu:09:17",
            "PendingModifiedValues": {},
            "MultiAZ": false,
            "EngineVersion": "14.4",
            "AutoMinorVersionUpgrade": true,
            "ReadReplicaDBInstanceIdentifiers": [],
            "LicenseModel": "postgresql-license",
            "Iops": 15000,
            "OptionGroupMemberships": [
                {
                    "OptionGroupName": "default:postgres-14",
                    "Status": "in-sync"
                }
            ],
            "PubliclyAccessible": false,
            "StorageType": "gp3",
            "DbInstancePort": 0,
            "StorageEncrypted": true,
            "KmsKeyId": "arn:aws:kms:us-east-1:<AWSアカウントID>:key/4544a543-32d6-4103-a1df-313116669a3e",
            "DbiResourceId": "db-WEKRXTLQALPG5SHWDSS2PYMZZA",
            "CACertificateIdentifier": "rds-ca-2019",
            "DomainMemberships": [],
            "CopyTagsToSnapshot": true,
            "MonitoringInterval": 0,
            "DBInstanceArn": "arn:aws:rds:us-east-1:<AWSアカウントID>:db:rds-db-instance",
            "IAMDatabaseAuthenticationEnabled": false,
            "PerformanceInsightsEnabled": false,
            "DeletionProtection": false,
            "AssociatedRoles": [],
            "TagList": [
                {
                    "Key": "aws:cloudformation:stack-id",
                    "Value": "arn:aws:cloudformation:us-east-1:<AWSアカウントID>:stack/RdsStack/a1d78750-5c27-11ed-a166-126e4ccc5485"
                },
                {
                    "Key": "aws:cloudformation:stack-name",
                    "Value": "RdsStack"
                },
                {
                    "Key": "aws:cloudformation:logical-id",
                    "Value": "RDSDBInstance1137C508"
                }
            ],
            "CustomerOwnedIpEnabled": false,
            "ActivityStreamStatus": "stopped",
            "BackupTarget": "region",
            "NetworkType": "IPV4",
            "StorageThroughput": 600
        }
    ]
}

これが CLP やってみる

CloudFormationでRDS DBインスタンスのストレージタイプにgp3を指定できるか確認してみました。

「ドキュメントに書いてないから出来るか分からないなぁ」で悩みすぎるのではなく、勢いでトライしてみたら上手くいきました。これがCLPの「やってみる」です。(ちょっと違う気もする)

自分なりに仮説を立てて、繰り返しトライアンドエラーする重要性を改めて実感しました。

この記事が誰かの助けになれば幸いです。

以上、AWS事業本部 コンサルティング部の のんピ(@non____97)でした!