AWS Copilot で管理しているデータベースを変更・リストアする際に注意すること

## はじめに

以前の記事で AWS Copilot で Phoenix と Aurora Serverless を立ち上げる方法を紹介しました

https://qiita.com/RyoWakabayashi/items/6eeda41be7941bd0b485

この場合のデータベースのリストア手順がややこしかったので記事にまとめておきます

## Amazon RDS におけるバックアップについて

RDS では自動バックアップと手動スナップショットを使用できます

https://aws.amazon.com/jp/rds/features/backup/

いずれのバックアップも DB インスタンスのストレージボリュームのスナップショットになっていて、個別にデータを出し入れするわけではなく、全体を復旧する形になります

特に注意しなければならないのが、 **バックアップからのリストアはクラスター、インスタンスの作り直しになる** という点です

既存のデータベースに対してデータを入れ込むのではなく、 **スナップショットから新しいクラスター、インスタンスを作成します**

また、**自動バックアップはクラスター、インスタンスが削除されると、一緒に削除されてしまいます**

バックアップを残しておきたい場合、手動スナップショットを取るか、自動バックアップをコピーする必要があります

## CloudFormation における RDS の変更について

もう一点注意しなければならないのが、 CloudFormation で管理している（AWS Copilot の場合もこれに当たります） RDS を変更したとき、項目によっては **クラスターが更新されるのではなく、新しく作られることです**

公式ドキュメントにも特に注意する事項として書かれています

https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-rds-dbcluster.html#cfn-rds-dbcluster-backupretentionperiod

> We highly recommend that you take a snapshot of the database before updating the stack. If you don't, you lose the data when AWS CloudFormation replaces your DB cluster.

和訳

> スタックを更新する前にデータベースのスナップショットを撮ることを強くお勧めします。そうしないと、AWS CloudFormationがDBクラスターを置き換える際にデータが失われます。

クラスターが作り直しになるのはドキュメント上で `Update requires: Replacement` と書かれている以下の項目です

- AvailabilityZones
- DatabaseName
- DBClusterIdentifier
- DBSubnetGroupName
- DBSystemId
- EngineMode
- KmsKeyId
- PubliclyAccessible
- RestoreToTime
- RestoreType
- SnapshotIdentifier
- SourceDBClusterIdentifier
- SourceRegion
- StorageEncrypted
- UseLatestRestorableTime

例えば後から `StorageEncrypted: true` に設定しようとすると、データベースが作り直されてデータは全て消えます

しかも前述の通り、**自動バックアップはクラスターと道連れに消えてしまうため、バックアップも消えます**

## リソースの保護

うっかり消えてしまうことを防ぐため、対象となるクラスターに以下の指定を加えましょう

- DeletionPolicy: Retain
  CloudFormation の stack を削除してもリソースは残るようになります
- UpdateReplacePolicy: Retain
  リソースの更新時に作り替えになったとしても、古い方のリソースが残るようになります

```diff_yaml
  XXXDBCluster:
    Metadata:
      'aws:copilot:description': 'The XXX Aurora Serverless v2 database cluster'
    Type: 'AWS::RDS::DBCluster'
+   DeletionPolicy: Retain
+   UpdateReplacePolicy: Retain
    Properties:
...
  XXXDBWriterInstance:
    Metadata:
      'aws:copilot:description': 'The XXX Aurora Serverless v2 writer instance'
    Type: 'AWS::RDS::DBInstance'
+   DeletionPolicy: Retain
+   UpdateReplacePolicy: Retain
    Properties:
...
```

ただし、リソースが消えなくなるため、不要になったら削除するのを忘れないようにする必要があります

## CloudFormation からのリストア

クラスターのプロパティーとして `SnapshotIdentifier` を指定します

```diff_yaml
...
  XXXDBCluster:
    Metadata:
      'aws:copilot:description': 'The XXX Aurora Serverless v2 database cluster'
    Type: 'AWS::RDS::DBCluster'
    DeletionPolicy: Retain
    UpdateReplacePolicy: Retain
    Properties:
      Engine: 'aurora-postgresql'
...
      Port: 5432
+     SnapshotIdentifier: "<リストアしたいスナップショットのID>"
...
```

この状態で環境をデプロイすればリストアされます

```bash
copilot env deploy
```

:::note info
`UpdateReplacePolicy: Retain` を指定していない場合、この時点で古いクラスター（リストア前）が消え、アプリケーションでは DB 接続エラーが発生します

`UpdateReplacePolicy: Retain` を指定している場合、古いクラスターは残っているため、アプリケーションは古いクラスターに接続したままになります
:::

リストア先は新しいクラスターになるため、アプリケーションのコンテナ上にある接続情報（環境変数等）を更新する必要があります

コンテナ自体は何も変わっていないので `--force` で強制更新します

```bash
copilot svc deploy --force
```

:::note warn
**重要な注意点があります**

一回 `SnapshotIdentifier` を指定した場合、**データベースの状態を保つには次回以降も同じスナップショットのIDを指定し続ける必要があります**

リストア後に `SnapshotIdentifier` を消して再度デプロイすると、データベースは空の状態で再び作成されてしまいます
:::

## データベースの変更・リストア手順

ここまでのことを踏まえて、 `StorageEncrypted: true` への設定変更など、クラスターの作り替えが発生するような場合、以下の手順を実施することになります

### システムメンテナンスをアナウンスする

データベースのスナップショット取得、リストアが必要なため、一時的にシステムの利用を停止します

:::note info
`UpdateReplacePolicy: Retain` の指定によってデータベースの切替自体は無停止で実行可能ですが、スナップショット取得後にユーザーが行った操作をリストアできないため、利用停止します
:::

### システムを利用停止状態にする

「工事中」的な表示にして利用できないようにします

### 手動スナップショットを取得する

AWS CLI の場合、以下のようにしてスナップショットを取得できます

```bash
aws rds create-db-cluster-snapshot \
  --db-cluster-identifier $DB_CLUSTER_ID \
  --db-cluster-snapshot-identifier $DB_CLUSTER_SNAPSHOT_ID
```

### 変更を CloudFormation に適用し、SnapshotIdentifierを指定する

適用したい内容と同時に SnapshotIdentifier によってスナップショットからのリストアを指定します

### デプロイする

デプロイし、データベースの作り替え、接続情報の更新を実施します

```bash
copilot env deploy

copilot svc deploy --force
```

### 稼働確認

DB の設定、接続先、データの内容、システム動作を確認します

### 旧クラスターの削除

動作に問題ないことが確認できたら変更前のクラスターを削除します

## まとめ

AWS Copilot は簡単にアプリケーションを管理・デプロイできますが、実際に中で動いている CloudFormation や RDS の仕様を理解していないと、うっかり重要なデータを消してしまう可能性があります

公式ドキュメントなどを読み込み、意図しない動作が発生しないようにしましょう