mazyu36の日記

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

AWS CDKにおけるAurora MySQLのリカバリについて

導入

AWS CDK構築したAurora MySQLの運用を検討する中で、データの復旧方法の整理が必要となったので色々調査、検証してみた。

前提

前提としてAurora MySQLAWS CDKの管理下のままデータを可能な手段について検証している(AWS CDKの管理下の状態を維持したままにしたいため)。

そのため手動でスナップショットから別のAuroraクラスターを作成し、AWS CDKで構築したクラスターは削除する等の手段は考慮していない。

検証内容

以下の記事を大いに参考にさせていただいた。

zenn.dev

上記の記事を踏まえるとAWS CDKにおけるリカバリとしては以下の手段のいずれかが良さそうである。

  1. Backtrack機能を使って巻き戻す
  2. コード上でスナップショットを指定してスタック更新する
  3. スナップショットから別のDBを復元し、手動でデータを移行する

3は力技なので対象外とし、1,2についてAWS CDKで検証してみた。

※2023/1/5 追記

元記事では Aurora MySQL3はBacktrack非対応と記載していたが、アップデートで対応された。

aws.amazon.com

以下の記事の内容と組み合わせればAurora MySQL2(Backtrack対応)-> Aurora MySQL3(Backtrack対応)へのアップグレードも可能である。

mazyu36.hatenablog.com

先に結論

検証した結果踏まえ、自分なら以下のようにするという考えを記載。

  • Backtrackでの戻しが許容できるのであればまずは導入しておき、これでリカバリができないかを検討する。
  • Snapshotからリカバリする道も実装上は用意しておき、Backtrackでリカバリ不可の場合はこちらで対応する。

検証内容詳細

1. Backtrack機能を使って巻き戻す

Backtrack機能の概要

データベースを一定時間巻き戻したり、進めたりすることが可能な機能。 スナップショットからのリカバリ等と比べると簡易な手順でリカバリすることが可能。

詳細は以下の公式ドキュメントや記事が参考になる。

docs.aws.amazon.com dev.classmethod.jp

以下個人的に気をつけたほうが良いと思ったポイントを記載する。

  • Backtrackはクラスター作成時に有効にする必要がある。既存のクラスターに対して有効化することはできない。
  • Backtrackウィンドウ(戻せる期間)は最大72時間。戻すために保持するレコードに比例して料金が増えるので、長ければ良いというものではない。

CDKによるBacktrackの実現

クラスター作成時にbacktrackWindowを設定すればよい。

    // DBクラスターを作成
    this.dbCluster = new rds.DatabaseCluster(scope, 'Database', {
      engine: rds.DatabaseClusterEngine.auroraMysql({
        version: AuroraMysqlEngineVersion.VER_2_10_0
      }),
      // backtrackの設定。ここでは300秒(5分)
      backtrackWindow: cdk.Duration.seconds(300),
     // 以下略

上記で構築したAuroraクラスターにおいて、CDK上でBacktrackウィンドウを変更した場合にどのような挙動になるかも試してみた。

※CloudFormationのドキュメントを見る限りではUpdate requires: No interruptionなのでreplaceは発生しないはずだが一応確認。

    // DBクラスターを作成
    this.dbCluster = new rds.DatabaseCluster(scope, 'Database', {
      engine: rds.DatabaseClusterEngine.auroraMysql({
        version: AuroraMysqlEngineVersion.VER_2_10_0
      }),
      // backtrackウィンドウ600秒に変更
      backtrackWindow: cdk.Duration.seconds(600),
     // 以下略

cdk diffをとってみたところ、想定通りreplaceは発生せず、設定値変更が反映されるのみである。デプロイもそこまで時間はかからない。

% cdk diff
Stack CdkFargateBastionStack
Resources
[~] AWS::RDS::DBCluster Database DatabaseB269D8BB
 └─ [~] BacktrackWindow
     ├─ [-] 300
     └─ [+] 600

2. コード上でスナップショットを指定してスタック更新する

CDKによるスナップショットからのリカバリ

SnapshotIdentifierにおいて対象のスナップショットを指定し、リカバリが可能である。 ただしL2 ConstructであるDatabaseClusterでは当該項目を指定することができない。

そのためEscape Hatchesを使用して設定する。 簡単にいうとL2/L3 Constructでは隠蔽化されている項目について、L1レベルで編集することを可能とするもの。

zenn.dev

今回の場合は以下のように行う。

    // escape hatchを使用してプロパティにアクセス
    const cfnCluster = this.dbCluster.node.defaultChild as rds.CfnDBCluster;

    // snapshotIdentifierにリカバリに使用するスナップショットのarnを指定
    cfnCluster.snapshotIdentifier = "arn:aws:rds:ap-northeast-1:123456789012:cluster-snapshot:xxxxxx";
    
    // UsernameとUserPasswordを未設定に上書き
    cfnCluster.masterUsername = undefined;
    cfnCluster.masterUserPassword = undefined;

※UsernameやUserPasswordはsnapshotから適用されるため、未設定にする必要がある。その他にも未設定にしないといけない項目があるので、ドキュメントを参考に実装と照らし合わせて対応が必要。 docs.aws.amazon.com

スナップショット未指定→指定ありとしてスタック更新した時の挙動

では、cfnCluster.snapshotIdentifierを未設定の状態でクラスターをデプロイし、その後上記のように設定した場合にどのような挙動となるかを確認する。

CloudFormationのドキュメント上はUpdate requires: ReplacementとなっているのでReplaceが発生するはずである。

cdk diffをとってみたところ、想定通りreplaceが発生する。データはほとんど入っていない状態であったがデプロイには20分ほどかかった。

% cdk diff
Resources
[~] AWS::RDS::DBCluster Database DatabaseB269D8BB replace
 ├─ [-] MasterUserPassword
 │   └─ {"Fn::Join":["",["{{resolve:secretsmanager:",{"Ref":"DatabaseSecret3B817195"},":SecretString:password::}}"]]}
 ├─ [-] MasterUsername (may cause replacement)
 │   └─ {"Fn::Join":["",["{{resolve:secretsmanager:",{"Ref":"DatabaseSecret3B817195"},":SecretString:username::}}"]]}
 └─ [+] SnapshotIdentifier (requires replacement)
     └─ arn:aws:rds:ap-northeast-1:xxxxxxxxxxxxx:cluster-snapshot:test-snapshot-1
[~] AWS::RDS::DBInstance Database/Instance1 DatabaseInstance1844F58FD replace
 └─ [~] DBClusterIdentifier (requires replacement)
     └─ [~] .Ref:
         ├─ [-] DatabaseB269D8BB
         └─ [+] DatabaseB269D8BB (replaced)

snapshotIdentifierを指定あり、かつその他項目を更新した場合のスタック更新

CloudFormationの記載を見ると、同じsnapshotIdentifierを指定し続ければ、他の項目をアップデートしてもリストアが再度行われない(データが先祖返りしない)と記載されている。

After you restore a DB cluster with a SnapshotIdentifier property, you must specify the same SnapshotIdentifier property for any future updates to the DB cluster. When you specify this property for an update, the DB cluster is not restored from the snapshot again, and the data in the database is not changed.

こちらの挙動を確認してみる。試しにDBのインスタンスクラスだけ変えてcdk diffをとってみると、想定通り当該項目の更新のみとなった。

% cdk diff
Stack CdkFargateBastionStack
Resources
[~] AWS::RDS::DBInstance Database/Instance1 DatabaseInstance1844F58FD
 └─ [~] DBInstanceClass
     ├─ [-] db.t3.small
     └─ [+] db.t3.medium

snapshotIdentifierを別のものに変えた場合の挙動

snapshotIdentifierを変えた場合は当然だが、再度リストアが行われる(replaceも発生する)。以下cdk diffの結果である。

% cdk diff
Stack CdkFargateBastionStack
Resources
[~] AWS::RDS::DBCluster Database DatabaseB269D8BB replace
 └─ [~] SnapshotIdentifier (requires replacement)
     ├─ [-] arn:aws:rds:ap-northeast-1:xxxxxxxxx:cluster-snapshot:test-snapshot-1
     └─ [+] arn:aws:rds:ap-northeast-1:xxxxxxxxx:cluster-snapshot:test-snapshot-2
[~] AWS::RDS::DBInstance Database/Instance1 DatabaseInstance1844F58FD replace
 └─ [~] DBClusterIdentifier (requires replacement)
     └─ [~] .Ref:
         ├─ [-] DatabaseB269D8BB
         └─ [+] DatabaseB269D8BB (replaced)

※snapshotIdentifierを設定あり→未指定とすると空のクラスターにreplaceされてしまうため注意。

However, if you don't specify the SnapshotIdentifier property, an empty DB cluster is created, and the original DB cluster is deleted.

どうやって実装しておくのが良さそうか

自分が実装するなら以下のように設定値で切り替えられるようにしておくのが良さそうかと思った。

環境によってスナップショットから復元したい or ゼロから作りたい等変わるかと思うので。

    // スナップショットを使うかと、対象のarnを設定値として切り替えられるようにしておく。
    // スナップショット未使用から使用に変更する場合や、対象のスナップショットを変える場合に使用
    type SnapshotConfig = {
      shouldUseSnapshot: boolean,
      snapshotIdentifier: string
    }
    const snapshotConfig: SnapshotConfig = {
      shouldUseSnapshot: true,
      snapshotIdentifier: 'arn:xxxxx'
    }


    // DBクラスターを作成
    this.dbCluster = new rds.DatabaseCluster(scope, 'Database', {
      engine: rds.DatabaseClusterEngine.auroraMysql({
        version: AuroraMysqlEngineVersion.VER_2_10_0
      }),
      //中略
    })

    // 設定値を元にスナップショットを使用する場合のみ設定
    if (snapshotConfig.shouldUseSnapshot) {
      const cfnCluster = this.dbCluster.node.defaultChild as rds.CfnDBCluster;
      cfnCluster.snapshotIdentifier = snapshotConfig.snapshotIdentifier;
      cfnCluster.masterUsername = undefined;
      cfnCluster.masterUserPassword = undefined;
    }

DBクラスターのreplace発生後にSecrets Managerはどうなるか

今回の実装では以下のようにcredentialsの設定によりDBの接続情報(Secrets Manager)を生成していた。

    // DBクラスターを作成
    this.dbCluster = new rds.DatabaseCluster(scope, 'Database', {
      engine: rds.DatabaseClusterEngine.auroraMysql({
        version: AuroraMysqlEngineVersion.VER_2_10_0
      }),
      backtrackWindow: cdk.Duration.seconds(600),
      // credentialの設定内容をもとにSecrets Managerの接続情報が自動生成される。usernameのみadminを指定
      credentials: {
        username: 'admin',
        secretName: 'dbSecret'
      },
    // 以下略

この状態でsnapshotidentifierの設定より、ホスト名が変わった場合どうなるか検証してみたところ、Secrets Managerのホスト名は置き換え後のものに問題なく変わっていた。

この点は特に心配しなくて良さそう。

ではECSのタスク定義から参照しているホスト名はどうなるか

ECSのタスク定義で、Secrets ManagerのDBの接続情報を参照(環境変数として注入)はよくあるケースかと思う。

ではスタック更新がかかりホスト名が変わった場合にどうなるかというと、ECSのコンテナ上では更新がされていなかった(つまりDBへの接続不可の状態となっていた)。 これはECSが環境変数に値を注入するタイミングがコンテナ起動時のためと思われる。

つまりスタック更新をかけた後はECSタスクの再起動もセットで必要と思われる。

おわりに

スマートにリカバリするのはなかなか難しい。この辺り他の方はどうやっているのか知りたい。