AWS CLI スクリプトで Aurora Serverless v1 (MySQL) を v2 にアップグレードする

## サービス終了！

いよいよ Amazon Aurora Serverless v1 のサービス終了日が迫ってきたので、稼働中の v1 データベースを Aurora Serverless v2 にアップグレードする必要があります。

Serverless v1 のまま放置するとサービス終了日以降に強制アップグレードが敢行されてしまうので、事前に動作確認をすることができない上に、パラメータ設定もデフォルト値になってしまいます。

## アップグレードの手順

公式ドキュメントに移行手順が紹介されていますが、ちょっと難解でした。
[Aurora Serverless v2 への移行 - Amazon Aurora](https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/AuroraUserGuide/aurora-serverless-v2.upgrade.html)

DevelopersIO の記事がとても分かりやすいです。
[Amazon Aurora Serverlessをv1からv2にアップグレードしてみた | DevelopersIO](https://dev.classmethod.jp/articles/moving-from-aurora-serverless-v1-tov2/)

どうやら、Serverless v1 から v2 に一気にアップグレードする方法は存在しないようです。

スナップショットからクラスターを復元する時にプロパティを変更することで Aurora をアップグレードするのですが、変更できるプロパティには制限があります。

そのため、まず最初のスナップショットから MySQL 5.7 の Provisioned (Serverless でない) クラスターに復元し、次の復元で MySQL 8.0 に変更して、最後にインスタンスを Provisioned から Serverless に変更することで、Serverless v2 へのアップグレードが完了します。

```plantuml
@startuml
|Serverless v1\n(MySQL 5.7)|
Start
 :(1) スナップショット取得;
|Provisioned クラスター\n(MySQL 5.7)|
 :(2a) クラスターを復元;
 :(2b) インスタンスを作成;
 :(3) スナップショット取得;
|Provisioned クラスター\n(MySQL 8.0)|
 :(4a) クラスターを復元;
 :(4b) インスタンスを作成;
 :(5) インスタンスを Serverless\nに変更;
|Serverless v2\n(MySQL 8.0)|
stop
@enduml
```

この手順の良いところは、Serverless v2 へのアップグレードが完成した後も元の Serverless v1 がそのまま残るので、本番環境を使ったトライアルが何度でもできることです。

スクリプト実行の所要時間は、テスト用の小規模なクラスターでも 1 時間以上かかりました。本番環境でのトライアルで所要時間を計測しておくことをおすすめします。

## 事前準備 (パラメータグループ)

MySQL のメジャーバージョンが変わるので、パラメータグループは Serverless v1 用とは別に作成しておく必要があります。以下は CloudFormation で作成する例です。

- 文字の照合順序を MySQL 5.7 と同じにしたい場合は、以下のように `collation_server: utf8mb4_general_ci` を指定します
- mysql_native_password 認証プラグインの警告がログに出力されることを避けたい場合は、以下のように `log_error_suppression_list: MY-013360` を指定します

```yaml
AWSTemplateFormatVersion: "2010-09-09"

Resources:
  RdsDbClusterParameterGroup:
    Type: AWS::RDS::DBClusterParameterGroup
    Properties:
      DBClusterParameterGroupName: mysql80-parameter-group
      Family: aurora-mysql8.0
      Parameters:
        character_set_server: utf8mb4
        collation_server: utf8mb4_general_ci
        log_error_suppression_list: MY-013360
        time_zone: Asia/Tokyo
```

- [AWS::RDS::DBClusterParameterGroup - AWS CloudFormation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-rds-dbclusterparametergroup.html)

## AWS CLI スクリプト

この章のコードを最後まで続けて実行することで、Serverless v2 へのアップグレードが完了します。

まず定数を定義します。対象とする AWS アカウントのプロファイル名を引数とします。

現在の Serverless v1 クラスターのセキュリティグループ、サブネットグループ、DB クラスター ID を `DB_SECURITY_GROUP_NAME`、`DB_SUBNET_GROUP`、`OLD_CLUSTER_ID` に指定してください。

事前準備で作成したパラメータグループを `DB_PARAMETER_GROUP` に指定してください。

```shell
#!/bin/bash
set -eu

readonly PROFILE="${1}"

readonly DB_SECURITY_GROUP_NAME="db-security-group"
readonly DB_SUBNET_GROUP="db-subnet-group"

readonly OLD_CLUSTER_ID="old-db-cluster"
readonly MID_CLUSTER_ID="mid-db-cluster"
readonly NEW_CLUSTER_ID="new-db-cluster"

readonly MID_INSTANCE_ID="mid-db-instance1"
readonly NEW_INSTANCE_ID="new-db-instance1"

readonly OLD_SNAPSHOT="old-snapshot"
readonly MID_SNAPSHOT="mid-snapshot"

readonly DB_PARAMETER_GROUP="mysql80-parameter-group"
readonly MIN_CAPACITY="0.5"
readonly MAX_CAPACITY="1"
```

### 1. Serverless v1 のスナップショットを取得

```plantuml
@startuml
|Serverless v1\n(MySQL 5.7)|
Start
 #palegreen:(1) スナップショット取得;
|Provisioned クラスター\n(MySQL 5.7)|
|Provisioned クラスター\n(MySQL 8.0)|
|Serverless v2\n(MySQL 8.0)|
@enduml
```

現在のデータベースのスナップショットを取得します。`create-db-cluster-snapshot` はすぐにリターンするので、完了するまで `wait` で待機します。

```shell
aws rds create-db-cluster-snapshot \
  --db-cluster-identifier "${OLD_CLUSTER_ID}" \
  --db-cluster-snapshot-identifier "${OLD_SNAPSHOT}" \
  --profile "${PROFILE}"

aws rds wait db-cluster-snapshot-available \
  --db-cluster-identifier "${OLD_CLUSTER_ID}" \
  --db-cluster-snapshot-identifier "${OLD_SNAPSHOT}" \
  --profile "${PROFILE}"
```

- [create-db-cluster-snapshot — AWS CLI Command Reference](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/rds/create-db-cluster-snapshot.html)
- [db-cluster-snapshot-available — AWS CLI Command Reference](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/rds/wait/db-cluster-snapshot-available.html)

### 2. スナップショットから Provisioned クラスター (MySQL 5.7) を復元

```plantuml
@startuml
|Serverless v1\n(MySQL 5.7)|
Start
 :(1) スナップショット取得;
|Provisioned クラスター\n(MySQL 5.7)|
 #palegreen:(2a) クラスターを復元;
 #palegreen:(2b) インスタンスを作成;
|Provisioned クラスター\n(MySQL 8.0)|
|Serverless v2\n(MySQL 8.0)|
@enduml
```

まずセキュリティグループの名前から ID を取得しておきます。jq コマンドを使用するので、なければインストールしておいてください。

```shell
db_security_group_id=$(aws ec2 describe-security-groups \
  --filters "Name=group-name,Values=${DB_SECURITY_GROUP_NAME}" \
  --profile "${PROFILE}" \
  --output json | jq -r ".SecurityGroups[0].GroupId")
```

- [describe-security-groups — AWS CLI Command Reference](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/ec2/describe-security-groups.html)


Serverless v1 のスナップショットから Provisioned クラスターを復元します。ここで MySQL のバージョンを変更することはできないので、元の Serverless v1 と同じ MySQL 5.7 での復元になります。`--engine-version` は Serverless v1 と同じバージョンにします。

```shell
aws rds restore-db-cluster-from-snapshot \
  --availability-zones "ap-northeast-1a" "ap-northeast-1c" \
  --db-cluster-identifier "${MID_CLUSTER_ID}" \
  --snapshot-identifier "${OLD_SNAPSHOT}" \
  --engine "aurora-mysql" \
  --engine-version "5.7.mysql_aurora.2.11.4" \
  --engine-mode "provisioned" \
  --db-subnet-group-name "${DB_SUBNET_GROUP}" \
  --vpc-security-group-ids "${db_security_group_id}" \
  --profile "${PROFILE}"

aws rds wait db-cluster-available \
  --db-cluster-identifier "${MID_CLUSTER_ID}" \
  --profile "${PROFILE}"
```

- [restore-db-cluster-from-snapshot — AWS CLI Command Reference](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/rds/restore-db-cluster-from-snapshot.html)
- [db-cluster-available — AWS CLI Command Reference](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/rds/wait/db-cluster-available.html)

クラスターの中に Provisioned インスタンスを 1 つ作成します。

```shell
aws rds create-db-instance \
  --db-cluster-identifier "${MID_CLUSTER_ID}" \
  --db-instance-identifier "${MID_INSTANCE_ID}" \
  --db-instance-class "db.t3.small" \
  --availability-zone ap-northeast-1a \
  --engine aurora-mysql \
  --no-publicly-accessible \
  --profile "${PROFILE}"

aws rds wait db-instance-available \
  --db-instance-identifier "${MID_INSTANCE_ID}" \
  --profile "${PROFILE}"
```

- [create-db-instance — AWS CLI Command Reference](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/rds/create-db-instance.html)
- [db-instance-available — AWS CLI Command Reference](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/rds/wait/db-instance-available.html)

### 3. Provisioned クラスター (MySQL 5.7) のスナップショットを取得

```plantuml
@startuml
|Serverless v1\n(MySQL 5.7)|
Start
 :(1) スナップショット取得;
|Provisioned クラスター\n(MySQL 5.7)|
 :(2a) クラスターを復元;
 :(2b) インスタンスを作成;
 #palegreen:(3) スナップショット取得;
|Provisioned クラスター\n(MySQL 8.0)|
|Serverless v2\n(MySQL 8.0)|
@enduml
```

復元したクラスターのスナップショットを取得します。

```shell
aws rds create-db-cluster-snapshot \
  --db-cluster-identifier "${MID_CLUSTER_ID}" \
  --db-cluster-snapshot-identifier "${MID_SNAPSHOT}" \
  --profile "${PROFILE}"

aws rds wait db-cluster-snapshot-available \
  --db-cluster-identifier "${MID_CLUSTER_ID}" \
  --db-cluster-snapshot-identifier "${MID_SNAPSHOT}" \
  --profile "${PROFILE}"
```

スナップショットを取得したらこのクラスターとインスタンスは不要になるので、料金が発生しないように削除か一時停止をしておきます。`wait` の必要はありません。

以下は一時停止の例なので、1 週間以内に削除しないと起動して料金が発生してしまいます。

```shell
aws rds stop-db-cluster \
  --db-cluster-identifier "${MID_CLUSTER_ID}" \
  --profile "${PROFILE}" || :
```

- [stop-db-cluster — AWS CLI Command Reference](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/rds/stop-db-cluster.html)

### 4. スナップショットから Provisioned クラスター (MySQL 8.0) を復元

```plantuml
@startuml
|Serverless v1\n(MySQL 5.7)|
Start
 :(1) スナップショット取得;
|Provisioned クラスター\n(MySQL 5.7)|
 :(2a) クラスターを復元;
 :(2b) インスタンスを作成;
 :(3) スナップショット取得;
|Provisioned クラスター\n(MySQL 8.0)|
 #palegreen:(4a) クラスターを復元;
 #palegreen:(4b) インスタンスを作成;
|Serverless v2\n(MySQL 8.0)|
@enduml
```

2 回目の復元で MySQL のバージョンを 8.0 に変更します。`--engine-version` は指定できる最新のバージョンにします。

```shell
aws rds restore-db-cluster-from-snapshot \
  --availability-zones "ap-northeast-1a" "ap-northeast-1c" \
  --db-cluster-identifier "${NEW_CLUSTER_ID}" \
  --snapshot-identifier "${MID_SNAPSHOT}" \
  --engine "aurora-mysql" \
  --engine-version "8.0.mysql_aurora.3.06.1" \
  --db-subnet-group-name "${DB_SUBNET_GROUP}" \
  --vpc-security-group-ids "${db_security_group_id}" \
  --enable-cloudwatch-logs-exports "audit" "error" "general" "slowquery" \
  --engine-mode "provisioned" \
  --db-cluster-parameter-group-name "${DB_PARAMETER_GROUP}" \
  --no-publicly-accessible \
  --serverless-v2-scaling-configuration \
    "MinCapacity=${MIN_CAPACITY},MaxCapacity=${MAX_CAPACITY}" \
  --profile "${PROFILE}"

aws rds wait db-cluster-available \
  --db-cluster-identifier "${NEW_CLUSTER_ID}" \
  --profile "${PROFILE}"
```

クラスターの中に Provisioned インスタンスを 1 つ作成します。2 つ目以降のインスタンスは、必要に応じて作成してください。

```shell
aws rds create-db-instance \
  --db-cluster-identifier "${NEW_CLUSTER_ID}" \
  --db-instance-identifier "${NEW_INSTANCE_ID}" \
  --db-instance-class "db.t3.medium" \
  --availability-zone ap-northeast-1a \
  --engine aurora-mysql \
  --no-publicly-accessible \
  --profile "${PROFILE}"

aws rds wait db-instance-available \
  --db-instance-identifier "${NEW_INSTANCE_ID}" \
  --profile "${PROFILE}"
```

### 5. Provisioned インスタンスを Serverless に変更

```plantuml
@startuml
|Serverless v1\n(MySQL 5.7)|
Start
 :(1) スナップショット取得;
|Provisioned クラスター\n(MySQL 5.7)|
 :(2a) クラスターを復元;
 :(2b) インスタンスを作成;
 :(3) スナップショット取得;
|Provisioned クラスター\n(MySQL 8.0)|
 :(4a) クラスターを復元;
 :(4b) インスタンスを作成;
 #palegreen:(5) インスタンスを Serverless\nに変更;
|Serverless v2\n(MySQL 8.0)|
stop
@enduml
```

最後に、インスタンスクラスを Serverless に変更します。

```shell
aws rds modify-db-instance \
  --db-instance-identifier "${NEW_INSTANCE_ID}" \
  --db-instance-class "db.serverless" \
  --apply-immediately \
  --profile "${PROFILE}"

aws rds wait db-instance-available \
  --db-instance-identifier "${NEW_INSTANCE_ID}" \
  --profile "${PROFILE}"
```

- [modify-db-instance — AWS CLI Command Reference](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/rds/modify-db-instance.html)

`modify-db-instance` は `wait` を使用してもすぐにリターンしてしまったので、完了まで待機する処理を自作して追加しました。このループを抜けたら、Serverless v2 へのアップグレードが完了です。

```shell
instance_class=""

while [[ "${instance_class}" != "db.serverless" ]]; do
  instance_class=$(aws rds describe-db-instances \
    --db-instance-identifier "${NEW_INSTANCE_ID}" \
    --profile "${PROFILE}" \
    --output json | jq -r '.DBInstances[0].DBInstanceClass')
  sleep 15
done
```

- [describe-db-instances — AWS CLI Command Reference](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/rds/describe-db-instances.html)

## アップグレード後の Serverless v2 への移行

この時点で元の Serverless v1 と新しい v2 はどちらも動作しています。

データベースを使用するアプリケーションの設定で、参照先を v2 側のエンドポイントに変更することで移行が完了します。

この作業も自動化する場合は、以下のように新しい Serverless v2 クラスターのエンドポイントを CLI で取得することができます。

```shell
new_db_endpoint=$(aws rds describe-db-cluster-endpoints \
  --db-cluster-identifier "${NEW_CLUSTER_ID}" \
  --filters "Name=db-cluster-endpoint-type,Values=WRITER" \
  --profile "${PROFILE}" \
  --output json | jq -r ".DBClusterEndpoints[0].Endpoint")
```

- [describe-db-cluster-endpoints — AWS CLI Command Reference](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/rds/describe-db-cluster-endpoints.html)

アプリケーションの正常動作が確認できたら、古い Serverless v1 クラスターを削除してください。

## 参考リンク

- [Aurora Serverless v2 への移行 - Amazon Aurora](https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/AuroraUserGuide/aurora-serverless-v2.upgrade.html)
- [Amazon Aurora Serverlessをv1からv2にアップグレードしてみた | DevelopersIO](https://dev.classmethod.jp/articles/moving-from-aurora-serverless-v1-tov2/)
- [rds — AWS CLI Command Reference](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/rds/index.html#cli-aws-rds)
- [AWS::RDS::DBClusterParameterGroup - AWS CloudFormation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-rds-dbclusterparametergroup.html)