AQ Tech Blog

Terraformで構築したRDS(Amazon Aurora MySQL)の、メジャーバージョンアップデートが意外と面倒だった

作成者: tsuyoshi.watanabe|2023年12月23日

 

本記事はAsiaQuest Advent Calendarの21日目です。

はじめに

クラウドインテグレーション部の渡邊です。
今回は、Terraformを利用して、RDS(Amazon Aurora MySQL)のメジャーバージョンをアップグレードする際にハマったことと、その対処について説明します。

環境情報

  • Terraform v1.2.6
  • macOS 13.4

発生事象詳細

今回使用した初期コードはこちらです。
ネットワークリソース等は、既存環境の値を用いる想定でコードを作成しました。
variableは省略しているため、ご自身の環境に合ったものをご用意ください。

provider "aws" {
region = "ap-northeast-1"
}

terraform {
required_version = "~> 1.2.0"
backend "s3" {
bucket = "test-bucket"
key = "actions/rds/terraform.tfstate"
region = "ap-northeast-1"
}
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.64.0"
}
}
}

resource "aws_db_subnet_group" "db-subgp" {
name = "test-dbsg"
subnet_ids = [
var.subnet-1a,
var.subnet-1c
]
}

resource "aws_rds_cluster_parameter_group" "dbcpg" {
name = "test-dbcpg"
family = "aurora-mysql5.7"
description = "cluster parameter group"
}

resource "aws_rds_cluster" "db" {
cluster_identifier = "test-db-cluster"
db_cluster_parameter_group_name = aws_rds_cluster_parameter_group.dbcpg.name
db_subnet_group_name = aws_db_subnet_group.db-subgp.name
vpc_security_group_ids = [var.sg]
engine_mode = "provisioned"
master_username = "admin"
master_password = "Passw0rd!"
engine = "aurora-mysql"
engine_version = "5.7.mysql_aurora.2.12.0"
port = 3306
final_snapshot_identifier = "temp"
}

resource "aws_rds_cluster_instance" "db-instance" {
identifier = "test-db-instance-01"
cluster_identifier = aws_rds_cluster.db.id
instance_class = "db.t3.small"
engine = aws_rds_cluster.db.engine
engine_version = aws_rds_cluster.db.engine_version
ca_cert_identifier = "rds-ca-rsa2048-g1"
}

事象

メジャーバージョンをアップグレードするために、少なくとも、本構成では以下のパラメータを変更する必要があります。

  • aws_rds_clusterのパラメータ:engine_version
  • aws_rds_cluster_instanceのパラメータ:engine_version
  • aws_rds_cluster_parameter_groupのパラメータ:family

これらを変更してterraform applyコマンドを実行して、設定変更を適用したところ、エラーが発生しました。

$ terraform apply
aws_rds_cluster_parameter_group.dbcpg: Refreshing state... [id=test-dbcpg]
aws_db_subnet_group.db-subgp: Refreshing state... [id=test-dbsg]
aws_rds_cluster.db: Refreshing state... [id=test-db-cluster]
aws_rds_cluster_instance.db-instance: Refreshing state... [id=test-db-instance-01]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
~ update in-place
-/+ destroy and then create replacement

Terraform will perform the following actions:

# aws_rds_cluster.db will be updated in-place
~ resource "aws_rds_cluster" "db" {
~ engine_version = "5.7.mysql_aurora.2.12.0" -> "8.0.mysql_aurora.3.05.0"
id = "test-db-cluster"
tags = {}
# (36 unchanged attributes hidden)
}

# aws_rds_cluster_instance.db-instance will be updated in-place
~ resource "aws_rds_cluster_instance" "db-instance" {
~ engine_version = "5.7.mysql_aurora.2.12.0" -> "8.0.mysql_aurora.3.05.0"
id = "test-db-instance-01"
tags = {}
# (26 unchanged attributes hidden)
}

# aws_rds_cluster_parameter_group.dbcpg must be replaced
-/+ resource "aws_rds_cluster_parameter_group" "dbcpg" {
~ arn = "arn:aws:rds:ap-northeast-1:315408206347:cluster-pg:test-dbcpg" -> (known after apply)
~ family = "aurora-mysql5.7" -> "aurora-mysql8.0" # forces replacement
~ id = "test-dbcpg" -> (known after apply)
name = "test-dbcpg"
+ name_prefix = (known after apply)
- tags = {} -> null
~ tags_all = {} -> (known after apply)
# (1 unchanged attribute hidden)
}

Plan: 1 to add, 2 to change, 1 to destroy.
aws_rds_cluster_parameter_group.dbcpg: Destroying... [id=test-dbcpg]
aws_rds_cluster_parameter_group.dbcpg: Still destroying... [id=test-dbcpg, 10s elapsed]
aws_rds_cluster_parameter_group.dbcpg: Still destroying... [id=test-dbcpg, 20s elapsed]

aws_rds_cluster_parameter_group.dbcpg: Still destroying... [id=test-dbcpg, 2m50s elapsed]

│ Error: deleting RDS Cluster Parameter Group (test-dbcpg): operation error RDS: DeleteDBClusterParameterGroup, https response error StatusCode: 400, RequestID: d851f851-9ed1-4da7-a322-a1637a468a80, InvalidDBParameterGroupState: One or more database instances are still members of this parameter group test-dbcpg, so the group cannot be deleted


aws_rds_cluster_parameter_groupのreplaceによってdestroyの実行を試みますが、DBがデフォルトのクラスタパラメータグループ(以降、CPGと呼ぶ)に関連付けられているため、エラーが発生しました。

対処

状況や考慮したこと

  • Terraformはreplaceの場合、通常はcreateの前にdestroyから実行されます。(下記参照)

By default, when Terraform must change a resource argument that cannot be updated in-place due to remote API limitations, Terraform will instead destroy the existing object and then create a new replacement object with the new configured arguments.

引用元:The lifecycle Meta-Argument

  • depend_on
    • リソースの依存関係を定義するメタ引数
    • DBの変更はdestroyが発生しないため、以下のようにdepend_onを使用しても、まずdestroyが実行されてしまいます。
    • よって、実行順序が変更されず、エラーが解消されないです。
  depends_on = [ aws_rds_cluster.db , aws_rds_cluster_instance.db-instance ]
  • lifecycle
    • リソースの動作をカスタマイズするメタ引数
    • lifecycleでcreate_before_destroyを使用することで、destroyの前に、createが実行されます。
    • しかし、以下の通り、同名のCPGがすでにあるので失敗します。
  lifecycle {
create_before_destroy = true
}

│ Error: creating DB Cluster Parameter Group (test-dbcpg): DBParameterGroupAlreadyExists: Parameter group test-dbcpg already exists
│ status code: 400, request id: 3f62eb3b-a4b8-4186-8fbb-da2a02ccb29a

│ with aws_rds_cluster_parameter_group.dbcpg,
│ on main.tf line 37, in resource "aws_rds_cluster_parameter_group" "dbcpg":
│ 37: resource "aws_rds_cluster_parameter_group" "dbcpg" {

以上の対応でも難しいことから、手動で正しくメジャーバージョンアップグレードができるように、適切な順番で設定変更して対処する方針となりました。
なお、今回はCPGのみカスタムし、インスタンスパラメータグループはデフォルトを使用しております。
よって、パラメータグループの設定変更はCPGのみを対象としております。

実際に対処してみた

設定適用の流れは以下の通りです。

① 1回目の適用

  • メジャーバージョンアップグレードを許可するパラメータallow_major_version_upgradeをtrueにする
  • デフォルトのCPG(default.aurora-mysql X.X)へ関連付ける
  • 設定変更を即時反映するパラメータapply_immediatelyをtrueにする

② 2回目の適用

  • カスタムしたCPGaws_rds_cluster_parameter_groupのファミリーfamilyを変更する
  • (オプション)変更後のengine_versionのサポートするインスタンスクラスinstance_classへ変更

③ 3回目の適用

  • クラスタaws_rds_clusterおよびインスタンスaws_rds_cluster_instanceのエンジンバージョンengine_versionを変更してバージョンアップ
  • カスタムCPGaws_rds_cluster_parameter_groupへ再度関連付ける

 

① 1回目の適用

  • メジャーバージョンアップグレードを許可するパラメータallow_major_version_upgradeをtrueにする
    allow_major_version_upgradeをtrueにしないと、メジャーバージョンアップグレードに失敗するので、事前に設定しておきます。
    デフォルトでfalseであるため注意しましょう。

  • デフォルトのCPG(default.aurora-mysql X.X)へ関連付ける
    カスタムCPGおよびエンジンバージョンをアップグレードするために、デフォルトのCPGに関連付けた後、もともとアタッチしていたCPGのバージョンを変更する方針とします。

  • 設定変更を即時反映するパラメータapply_immediatelyをtrueにする
    設定変更を即時反映するために、インスタンスおよびクラスタにパラメータapply_immediatelyを追加します。
    インスタンス、およびクラスタのapply_immediatelyのデフォルト値はfalseなので注意しましょう。

1回目の適用による変更後のコードは以下です。

resource "aws_rds_cluster" "db" {
cluster_identifier = "test-db-cluster"
- db_cluster_parameter_group_name = aws_rds_cluster_parameter_group.dbcpg.name
+ db_cluster_parameter_group_name = "default.aurora-mysql5.7"
db_subnet_group_name = aws_db_subnet_group.db-subgp.name
vpc_security_group_ids = [var.sg]
engine_mode = "provisioned"
master_username = "admin"
master_password = "Passw0rd!"
engine = "aurora-mysql"
engine_version = "5.7.mysql_aurora.2.12.0"
+ allow_major_version_upgrade = true
port = 3306
final_snapshot_identifier = "temp"
+ apply_immediately = true
}
resource "aws_rds_cluster_instance" "db-instance" {
identifier = "test-db-instance-01"
cluster_identifier = aws_rds_cluster.db.id
instance_class = "db.t3.small"
engine = aws_rds_cluster.db.engine
engine_version = aws_rds_cluster.db.engine_version
ca_cert_identifier = "rds-ca-rsa2048-g1"
+ apply_immediately = true
}

terraform applyコマンドで設定変更の適用に成功しました。

$ terraform apply

Terraform will perform the following actions:

# aws_rds_cluster.db will be updated in-place
~ resource "aws_rds_cluster" "db" {
~ allow_major_version_upgrade = false -> true
~ db_cluster_parameter_group_name = "test-dbcpg" -> "default.aurora-mysql5.7"
+ apply_immediately = true
id = "test-db-cluster"
tags = {}
# (36 unchanged attributes hidden)
}

# aws_rds_cluster_instance.db-instance will be updated in-place
~ resource "aws_rds_cluster_instance" "db-instance" {
+ apply_immediately = true
id = "test-db-instance-01"
tags = {}
# (26 unchanged attributes hidden)
}
Plan: 0 to add, 2 to change, 0 to destroy.

Apply complete! Resources: 0 added, 2 changed, 0 destroyed.


② 2回目の適用

  • カスタムしたCPGaws_rds_cluster_parameter_groupのファミリーfamilyを変更する
    この時点で、カスタムCPGに関連付けられているDBはない想定のため、以下のように、familyを変更します。
resource "aws_rds_cluster_parameter_group" "dbcpg" {
name = "test-dbcpg"
- family = "aurora-mysql5.7"
+ family = "aurora-mysql8.0"
description = "cluster parameter group"
}
  • (オプション)変更後のエンジンバーションengine_versionをサポートするインスタンスクラスinstance_classへ変更
    インスタンスクラスによっては、アップグレード前にインスタンスクラスinstance_classを変更する必要があります。
    なぜなら、アップグレード後にそのインスタンスサイズをサポートしていない場合があるからです。
    また、「① 1回目の適用」で実施した、設定変更を即時反映するパラメータapply_immediatelyをtrueに設定しておかないと、インスタンスクラスの変更が反映されず、以下のように後続のアップグレード作業でエラーが発生します。

│ Error: updating RDS Cluster (test-db-cluster): InvalidDBInstanceState: Cannot modify engine version because there is a pending orderable config change for DB instance: test-db-instance-01
│ status code: 400, request id: 9618c4fc-b42e-405e-8d91-0645d04b23bc

│ with aws_rds_cluster.db,
│ on main.tf line 44, in resource "aws_rds_cluster" "db":
│ 44: resource "aws_rds_cluster" "db" {

サポートしているインスタンスクラスは以下のように確認できます。
こちらは8.0.mysql_aurora.3.05.0のサポートするインスタンスクラスを表示します。

aws rds describe-orderable-db-instance-options \
--engine aurora-mysql \
--query 'OrderableDBInstanceOptions[].[DBInstanceClass,StorageType,Engine,EngineVersion]' \
--output table \
--region ap-northeast-1 \
| grep 8.0.mysql_aurora.3.05.0

以下のように、インスタンスクラスinstance_classを変更します。

resource "aws_rds_cluster_instance" "db-instance" {
identifier = "test-db-instance-01"
cluster_identifier = aws_rds_cluster.db.id
- instance_class = "db.t3.small"
+ instance_class = "db.t4g.medium"
engine = aws_rds_cluster.db.engine
engine_version = aws_rds_cluster.db.engine_version
ca_cert_identifier = "rds-ca-rsa2048-g1"
apply_immediately = true
}

即時反映も含めた設定を適用することで、後続の作業もエラーにならないはずです。

$ terraform apply     

Terraform will perform the following actions:

# aws_rds_cluster.db will be updated in-place
~ resource "aws_rds_cluster" "db" {
id = "test-db-cluster"
tags = {}
# (38 unchanged attributes hidden)
}

# aws_rds_cluster_instance.db-instance will be updated in-place
~ resource "aws_rds_cluster_instance" "db-instance" {
id = "test-db-instance-01"
~ instance_class = "db.t3.small" -> "db.t4g.medium"
tags = {}
# (26 unchanged attributes hidden)
}

Plan: 0 to add, 2 to change, 0 to destroy.

Apply complete! Resources: 0 added, 2 changed, 0 destroyed.

また、インスタンスクラスとメジャーバージョンアップグレードを同時に行おうとすると、メジャーバージョンアップグレード後のエンジンバージョンと、現在のインスタンスクラスの組み合わせが正しくない旨のエラーがでます。
そのため、まずインスタンスクラスの変更完了後、メジャーバージョンアップグレードを試みる必要があります。


│ Error: updating RDS Cluster (test-db-cluster): InvalidParameterCombination: RDS does not support creating a DB instance with the following combination: DBInstanceClass=db.t3.small, Engine=aurora-mysql, EngineVersion=8.0.mysql_aurora.3.05.0, LicenseModel=general-public-license. For supported combinations of instance class and database engine version, see the documentation.
│ status code: 400, request id: da1645b8-8bfd-4b23-a31f-5530fbe9bacd

│ with aws_rds_cluster.db,
│ on main.tf line 44, in resource "aws_rds_cluster" "db":
│ 44: resource "aws_rds_cluster" "db" {


③ 3回目の適用

  • クラスタaws_rds_clusterおよびインスタンスaws_rds_cluster_instanceのエンジンバージョンengine_versionを変更してバージョンアップ
  • カスタムCPGaws_rds_cluster_parameter_groupへ再度関連付ける

以上を踏まえて、変更したコードは以下のようになります。

resource "aws_rds_cluster_parameter_group" "dbcpg" {
name = "test-dbcpg"
family = "aurora-mysql8.0"
description = "cluster parameter group"
}

resource "aws_rds_cluster" "db" {
cluster_identifier = "test-db-cluster"
db_cluster_parameter_group_name = aws_rds_cluster_parameter_group.dbcpg.name
db_subnet_group_name = aws_db_subnet_group.db-subgp.name
vpc_security_group_ids = [var.sg]
engine_mode = "provisioned"
master_username = "admin"
master_password = "Passw0rd!"
engine = "aurora-mysql"
engine_version = "8.0.mysql_aurora.3.05.0"
allow_major_version_upgrade = true
port = 3306
final_snapshot_identifier = "temp"
apply_immediately = true
}

resource "aws_rds_cluster_instance" "db-instance" {
identifier = "test-db-instance-01"
cluster_identifier = aws_rds_cluster.db.id
# instance_class = "db.t3.small"
instance_class = "db.t4g.medium"
engine = aws_rds_cluster.db.engine
engine_version = aws_rds_cluster.db.engine_version
ca_cert_identifier = "rds-ca-rsa2048-g1"
apply_immediately = true
}

適用すると、成功しました。

$ terraform apply

# aws_rds_cluster.db will be updated in-place
~ resource "aws_rds_cluster" "db" {
~ db_cluster_parameter_group_name = "default.aurora-mysql5.7" -> "test-dbcpg"
~ engine_version = "5.7.mysql_aurora.2.12.0" -> "8.0.mysql_aurora.3.05.0"
id = "test-db-cluster"
tags = {}
# (37 unchanged attributes hidden)
}

# aws_rds_cluster_instance.db-instance will be updated in-place
~ resource "aws_rds_cluster_instance" "db-instance" {
~ engine_version = "5.7.mysql_aurora.2.12.0" -> "8.0.mysql_aurora.3.05.0"
id = "test-db-instance-01"
tags = {}
# (27 unchanged attributes hidden)
}

Plan: 0 to add, 2 to change, 0 to destroy.

Apply complete! Resources: 0 added, 2 changed, 0 destroyed.

 

メジャーバージョンアップグレードが成功しているか確認

コンソール画面およびTerraform側から確認すると、設定変更が問題なく行われていることがわかります。

$ terraform apply
aws_rds_cluster_parameter_group.dbcpg: Refreshing state... [id=test-dbcpg]
aws_db_subnet_group.db-subgp: Refreshing state... [id=test-dbsg]
aws_rds_cluster.db: Refreshing state... [id=test-db-cluster]
aws_rds_cluster_instance.db-instance: Refreshing state... [id=test-db-instance-01]

No changes. Your infrastructure matches the configuration.

Terraform has compared your real infrastructure against your configuration and found no differences, so no changes are needed.

Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

まとめ

  • メジャーバージョンのアップグレードは以下に注意する
    • カスタムCPGの関連付けの依存関係によるエラー
    • Terraformの実行順序(createの前にdestroyが実行される)の影響でdepend_onでは順番を制御できないことがある点
    • 既存のリソース名と同じにしたい場合、lifecycleで実行順序を制御しても発生する、同名のリソース作成のエラー
    • メジャーバージョンのアップグレード後にサポートするインスタンスサイズ
    • apply_immediatelyやallow_major_version_upgradeの有効化
    • 設定変更の順番(同時に設定変更すると失敗するパラメータがいくつかある)

【参考】

Resource: aws_rds_cluster

Resource: aws_rds_cluster_instance

The depends_on Meta-Argument