mazyu36の日記

某SIer所属のクラウドエンジニアのブログ

【CDK】 Aurora Serverless v2に対応したL2 ConstructのAPIを使用して旧APIからの移行を試す

AWS CDK v2.82.0でAurora Serverless v2が正式にL2 Constructに対応しました。

AWSの方によるCDKのアップデートに関するツイート。いつもこれでキャッチアップできており非常に助かっています。ありがたい。

これにともないAWS CDKでRDSのAPIに変更が入っています。 そのため単に確認するのみではなく、旧APIからの移行について検証してみました。

最初に試したこと、および結論を書いておきます。

  1. APIで作成したProvisionedインスタンスを持つAuroraクラスターを新APIに移行

    • 移行用のプロパティisFromLegacyInstancePropsを使えば問題ない。
  2. Escape Hatchesを使い、旧APIで無理やり作成したServerless v2インスタンスを持つAuroraクラスターを新APIに移行。

    • (v2.88.0より前)そのまま移行するとインスタンスの入れ替えが発生する。ただし新APIによるインスタンスが立ち上がった後、旧APIによるインスタンスを削除という動作となるためダウンタイムは短時間となる想定。
    • (v2.89.0以降)移行用のプロパティisFromLegacyInstancePropsを使えば問題ない。

目次

1. APIの変更内容

今回対象となるAPIrds.DatabaseClusterです。

Aurora Serverless v2への対応に伴い、インスタンスの設定内容の定義方法が変わっています。

API(v2.82.0より前)

以下のようにinstancesインスタンス数を指定、instansPropsインスタンスの設定を指定する形式となっていました。

この時instancePropsの設定内容は全インスタンス共通であるため、インスタンスごとに異なる設定にはできません。

new rds.DatabaseCluster(scope, 'Database', {
      // ... 略

      // --------------- Old API----------------
      // インスタンスの数を指定
      instances: 1,
      // インスタンスのプロパティを設定(全インスタンス共通)
      instanceProps: {
        vpc: props.vpcConstruct.vpc,
        vpcSubnets: {
          subnets: [props.vpcConstruct.subnetDB1a, props.vpcConstruct.subnetDB1c],
        },
        securityGroups: [props.securityGroupConstruct.databaseSg],
        instanceType: ec2.InstanceType.of(InstanceClass.T3, InstanceSize.LARGE),
      },

API(v2.82.0から)

上記のinstancesinstancePropsdeprecatedとなり、代わりにwriterreaderにおいてインスタンスごとに個別の設定を行えるようになりました。

これによりAurora Serverless v2に対応したのみではなく、インスタンスごとに異なる設定を行う柔軟なクラスタ構築が可能となりました。

以下が実装例です。Writerをdb.t3.large、Readerをdb.t3.large+Serverless v2にしています。

なおACUに関してはserverlessV2MaxCapacityserverlessV2MinCapacityで定義しますが、こちらはServerless v2インスタンス全てで共通の設定となります。

new rds.DatabaseCluster(scope, 'Database', {
      // ... 略

      // --------------- New API----------------
      // VPC, Subnet, SG設定などは共通
      vpc: props.vpcConstruct.vpc,
      vpcSubnets: {
        subnets: [props.vpcConstruct.subnetDB1a, props.vpcConstruct.subnetDB1c],
      },
      securityGroups: [props.securityGroupConstruct.databaseSg],

      // Writerインスタンスの設定
      writer: rds.ClusterInstance.provisioned('Writer', {
        instanceType: ec2.InstanceType.of(InstanceClass.T3, InstanceSize.LARGE),
      }),
      // Readerインスタンスの設定
      readers: [
        // Readerインスタンス(Provisioned)
        rds.ClusterInstance.provisioned('Reader1', {
          instanceType: ec2.InstanceType.of(InstanceClass.T3, InstanceSize.LARGE),
        }),
        // Readerインスタンス(Serverless v2)
        rds.ClusterInstance.serverlessV2('Reader2', {
        })
      ],
      // Serverless v2(ACU)の設定 ※Serverless V2インスタンスは共通のACUを適用
      serverlessV2MaxCapacity: 2.0,
      serverlessV2MinCapacity: 0.5,

      // ... 略
})

2. 旧APIから新APIの移行について

上記のようにAPIに変更が入ったため移行方法の確認および検証をします。

①Provisionedの場合

Provisionedの場合、新APIではrds.ClusterInstance.provisionedで定義する形になりますが、プロパティにisFromLegacyInstancePropsという項目があります。

Only used for migrating existing clusters from using instanceProps to writer and readers.

要は旧APIから移行する場合はこのフラグをtrueにせよということのようです。 では実際に試してみます。

まずは旧APIdb.t3.largeのWriterインスタンスを持つAuroraクラスターをデプロイします。

new rds.DatabaseCluster(scope, 'Database', {
      // ... 略

      // --------------- Old API----------------
      // インスタンスの数を指定
      instances: 1,
      // インスタンスのプロパティを設定(全インスタンス共通)
      instanceProps: {
        vpc: props.vpcConstruct.vpc,
        vpcSubnets: {
          subnets: [props.vpcConstruct.subnetDB1a, props.vpcConstruct.subnetDB1c],
        },
        securityGroups: [props.securityGroupConstruct.databaseSg],
        instanceType: ec2.InstanceType.of(InstanceClass.T3, InstanceSize.LARGE),
      },

      // ... 略
)

以下のようにWriterのみを持つAuroraクラスターが作成されました。

次に新APIに定義を変更します。

new rds.DatabaseCluster(scope, 'Database', {
      // ... 略

      // --------------- New API----------------
      // VPC, Subnet, SG設定などは共通
      vpc: props.vpcConstruct.vpc,
      vpcSubnets: {
        subnets: [props.vpcConstruct.subnetDB1a, props.vpcConstruct.subnetDB1c],
      },
      securityGroups: [props.securityGroupConstruct.databaseSg],

      // Writerインスタンスの設定
      writer: rds.ClusterInstance.provisioned('Writer', {
        instanceType: ec2.InstanceType.of(InstanceClass.T3, InstanceSize.LARGE),
        isFromLegacyInstanceProps: true
      }),

      // ... 略

)

上記の後、cdk diffをとってみました。そうすると以下のようにdestroyになってしまいました。

Resources
[-] AWS::RDS::DBInstance Database/Instance1 DatabaseInstance1844F58FD destroy
[+] AWS::RDS::DBInstance Database/Writer DatabaseWriter2C00925F

IDがズレているのが原因と考え、新APIでIDを旧APIで生成したものと揃えてみます。

new rds.DatabaseCluster(scope, 'Database', {
      // ... 略

      // --------------- New API----------------
      // VPC, Subnet, SG設定などは共通
      vpc: props.vpcConstruct.vpc,
      vpcSubnets: {
        subnets: [props.vpcConstruct.subnetDB1a, props.vpcConstruct.subnetDB1c],
      },
      securityGroups: [props.securityGroupConstruct.databaseSg],

      // Writerインスタンスの設定
      writer: rds.ClusterInstance.provisioned('Instance1`', {  // ★旧APIにより生成されたWriterインスタンスのIDであるInstance1に変更。
        instanceType: ec2.InstanceType.of(InstanceClass.T3, InstanceSize.LARGE),
        isFromLegacyInstanceProps: true
      }),

      // ... 略

)

この状態でcdk diffをとると差分なしとなりました。この状態でcdk deployしても特に変更が入らずに完了します。

無事に旧APIから新APIに移行できました。

There were no differences

ちなみにこのあと、isFromLegacyInstancePropsfalseにするとどうなるかを試してみました。

new rds.DatabaseCluster(scope, 'Database', {
      // ... 略

      // --------------- New API----------------
      // VPC, Subnet, SG設定などは共通
      vpc: props.vpcConstruct.vpc,
      vpcSubnets: {
        subnets: [props.vpcConstruct.subnetDB1a, props.vpcConstruct.subnetDB1c],
      },
      securityGroups: [props.securityGroupConstruct.databaseSg],

      // Writerインスタンスの設定
      writer: rds.ClusterInstance.provisioned('Instance1`', {  
        instanceType: ec2.InstanceType.of(InstanceClass.T3, InstanceSize.LARGE),
        isFromLegacyInstanceProps: false. // ★falseに変更
      }),

      // ... 略

)

この状態でcdk diffをとると再度destroyとなりました。旧APIから移行した後にインスタンスの置き換えを避けるためには、isFromLegacyInstancePropstrueのままとする必要があることがわかりました。

[-] AWS::RDS::DBInstance Database/Instance1 DatabaseInstance1844F58FD destroy
[+] AWS::RDS::DBInstance Database/Instance1 DatabaseInstance14A23AADF

②Escape Hatchesで作ったServerless v2の場合

以下の記事でも紹介していますが、旧APIでもEscape Hatchesを使用すれば、(若干強引ではあるものの)Serverless v2のインスタンスが作れました。

mazyu36.hatenablog.com

ではこの方法でServerless v2のインスタンスを作った状態で、新APIに移行したらどうなるのか試してみました。

まずは以下のようにEscape Hatchesを使用してServerless v2のWriterを持つAurora Clusterを作成します。

new rds.DatabaseCluster(scope, 'Database', {
          // ... 略

      // --------------- Old API----------------
      // インスタンスの数を指定
      instances: 1,
      // インスタンスのプロパティを設定(全インスタンス共通)
      instanceProps: {
        vpc: props.vpcConstruct.vpc,
        vpcSubnets: {
          subnets: [props.vpcConstruct.subnetDB1a, props.vpcConstruct.subnetDB1c],
        },
        securityGroups: [props.securityGroupConstruct.databaseSg],
        instanceType: new ec2.InstanceType('serverless'), //インスタンスタイプでserverlessを指定
      },
           // ... 略
)

    // Escape Hatchesを使用して設定。
    const cfnCluster = this.dbCluster.node.defaultChild as rds.CfnDBCluster;
    cfnCluster.addPropertyOverride('ServerlessV2ScalingConfiguration', {
      'MaxCapacity': 2,
      'MinCapacity': 0.5,
    });
    cfnCluster.addPropertyDeletionOverride('EngineMode');

以下のように作成できました。

では次に新APIで同じ内容を定義します。Serverless v2の場合は新APIではrds.ClusterInstance.serverlessV2を使用します。

v2.88.0までの場合

Provisionedと異なる点として、Serverless v2において旧APIからの移行用の項目(isFromLegacyInstanceProps)は存在しません。

そのため以下のように、旧APIで作成した際と同じ設定値にするのみになります。

new rds.DatabaseCluster(scope, 'Database', {
      // ... 略

      // --------------- New API----------------
      // VPC, Subnet, SG設定などは共通
      vpc: props.vpcConstruct.vpc,
      vpcSubnets: {
        subnets: [props.vpcConstruct.subnetDB1a, props.vpcConstruct.subnetDB1c],
      },
      securityGroups: [props.securityGroupConstruct.databaseSg],

      // Writerインスタンスの設定
      writer: rds.ClusterInstance.serverlessV2('Instance1', {
      }),
      // Readerインスタンスは無し
      readers: [
      ],
      // Serverless v2(ACU)の設定 
      serverlessV2MaxCapacity: 2.0,
      serverlessV2MinCapacity: 0.5,

      // ... 略

)

この状態でcdk diffをとると、destroyとなりインスタンスの入れ替えが発生します。予想通りと言えば予想通りです。

Resources
[-] AWS::RDS::DBInstance Database/Instance1 DatabaseInstance1844F58FD destroy
[+] AWS::RDS::DBInstance Database/Instance1 DatabaseWriter7794273E

ではこの状態でcdk deployに突き進んでみます。デプロイを開始するとまず入れ替え先(新APIによるServerless v2)のインスタンスがリーダーとして作成されました。


そして入れ替え先(新APIによるServerless v2)のインスタンスの起動が終わるとライターに昇格し、入れ替え元(旧APIによるServerless v2)のインスタンスがリーダーとなり削除されました。


最終的に入れ替え先(新APIによるServerless v2)のインスタンスのみとなり入れ替えが完了しました。


インスタンスの入れ替えは発生するものの、入れ替え先を起動してから入れ替え元を削除するという動作なので、ダウンタイムは抑えられそうです。

v2.89.0以降の場合 ※2023/8/2追加

v2.89.0において、Severless v2用にも旧APIからの移行用のプロパティ(isFromLegacyInstanceProps)が追加されました。これによりEscape Hatches+旧APIでデプロイしたServerless v2インスタンスも、Provisionedと同様の方法で新APIに移行できます。

github.com

以下がv2.89.0以降で、旧API→新APIに移行する際の実装例になります。

v2.88.0以前との違いとしてはrds.ClusterInstance.serverlessV2においてisFromLegacyInstancePropsが使えるようになっている点です。

new rds.DatabaseCluster(scope, 'Database', {
      // ... 略

      // --------------- New API----------------
      // VPC, Subnet, SG設定などは共通
      vpc: props.vpcConstruct.vpc,
      vpcSubnets: {
        subnets: [props.vpcConstruct.subnetDB1a, props.vpcConstruct.subnetDB1c],
      },
      securityGroups: [props.securityGroupConstruct.databaseSg],

      // Writerインスタンスの設定
      writer: rds.ClusterInstance.serverlessV2('Instance1', {
        isFromLegacyInstanceProps: true  // ★ここ
      }),
      // Readerインスタンスは無し
      readers: [
      ],
      // Serverless v2(ACU)の設定 
      serverlessV2MaxCapacity: 2.0,
      serverlessV2MinCapacity: 0.5,

      // ... 略
)

ではEscape HatchesでServerless v2インスタンスをデプロイした状態で、上記に書き換えcdk diffをとってみます。

想定通り差分なしとなりました。当然ですがcdk deployをしても何も起こりません。

There were no differences

✨  Number of stacks with differences: 0

ではこの状態でisFromLegacyInstancePropsfalseにし、cdk diffをとってみます。するとProvisionedの時と同様にdestroyになりました。

Serverless v2でも同様に旧APIから移行した後にインスタンスの置き換えを避けるためには、isFromLegacyInstancePropstrueのままとする必要があることがわかりました。

Resources
[-] AWS::RDS::DBInstance Database/Instance1 DatabaseInstance1844F58FD destroy
[+] AWS::RDS::DBInstance Database/Instance1 DatabaseInstance14A23AADF

3. 終わりに

個人的にServerless v2に対応したことよりも、クラスター内でインスタンスごとに設定を変えられるようになったことの方が嬉しいですね。

Readが圧倒的に多い等のケースもあるので、うまく活用していきたいです。