TerraformでAmazon Auroraのクローンを作ってみた
お疲れさまです。とーちです。
本ブログはterraform Advent Calendar 2024の5日目になります。
TerraformでAmazon Aurora MySQL(以下Aurora)のクローンを作る機会があったので、そのときに詰まったところなどを共有します。
Auroraのクローンについて
Amazon Auroraでデータベースのクローンを作成できる機能となっており、Copy-on-Writeプロトコルにより高速に本番環境等のデータを別環境に持ち込むことができます。ユースケースとしては、本番環境から開発環境へのクローンやデータ分析クエリを実行するためのクローン作成などがあるかと思います。
Terraformで作成する場合に必要なResource Blocks
AWS CLIからAuroraクローンを作成する際の手順としては以下となります。
restore-db-cluster-to-point-in-time
で元のクラスターと同じストレージデータを持つ Aurora DB クラスターを作成create-db-instance
CLI コマンドを使用して、クラスターに含めるDB インスタンスを作成- ご参考:https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/AuroraUserGuide/Aurora.Managing.Clone.html#Aurora.Managing.Clone.create
そのため基本的には上記で作成している通りに、AuroraクラスターとAuroraインスタンスを作成するためのResource Blocksを定義すればOKです。具体的には以下となります。
- aws_rds_cluster
- aws_rds_cluster_instance
- aws_security_groupやaws_rds_cluster_parameter_groupなどの周辺リソース
クローン元Aurora作成のためのTerraformコード
それではまずクローン元となるAuroraを作成するためのTerraformコードを見ていきます。なお、今回ご紹介するコードはモジュールとして呼び出すことを想定して作っています。
クローン元となるAuroraは普通に作成しているだけなので特別、注意点等はありませんが、一点だけKMSについて触れておきます。Auroraクローンでは他アカウントにクローンする際にデフォルト暗号化キーで作成されたAuroraをクローンすることはできません。Auroraクローンはクロスアカウントで実施することが多いと思うので、KMSキーは作成しておいたほうがいいと思います。KMSキーの権限は必要に応じて絞ってください。
data "aws_caller_identity" "current" {}
resource "aws_kms_key" "main" {
description = "KMS key"
enable_key_rotation = true
deletion_window_in_days = 20
policy = jsonencode({
Version = "2012-10-17"
Id = "key-default-1"
Statement = [
{
Sid = "Enable IAM User Permissions"
Effect = "Allow"
Principal = {
AWS = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"
},
Action = "kms:*"
Resource = "*"
},
{
Sid = "Grant necessary permissions to the destination AWS account for cloning"
Effect = "Allow"
Principal = {
AWS = "arn:aws:iam::${var.targetAccountID}:root"
},
Action = "kms:*"
Resource = "*"
}
]
})
}
resource "aws_kms_alias" "main" {
name = "alias/rds/${var.prefix}-cluster"
target_key_id = aws_kms_key.main.key_id
}
# rds
resource "aws_rds_cluster" "main" {
cluster_identifier = "${var.prefix}-rds-aurora-mysql-cluster"
engine = "aurora-mysql"
engine_version = var.rds_cluster_info.rds_engine_version
availability_zones = var.rds_cluster_info.availability_zones
database_name = var.rds_cluster_info.database_name
master_username = var.rds_cluster_info.master_username
manage_master_user_password = true
db_subnet_group_name = aws_db_subnet_group.main.name
vpc_security_group_ids = [aws_security_group.main.id]
db_cluster_parameter_group_name = aws_rds_cluster_parameter_group.main.name
backup_retention_period = var.rds_cluster_info.backup_retention_period
preferred_backup_window = var.rds_cluster_info.preferred_backup_window
delete_automated_backups = var.rds_cluster_info.delete_automated_backups
port = 3306
storage_encrypted = true
storage_type = ""
kms_key_id = aws_kms_key.main.arn
enabled_cloudwatch_logs_exports = var.rds_cluster_info.cloudwatch_logs_exports
preferred_maintenance_window = var.rds_cluster_info.preferred_maintenance_window
skip_final_snapshot = false
final_snapshot_identifier = "${var.prefix}-cluster-final-snapshot-${formatdate("YYYYMMDDHHmmss", timestamp())}"
apply_immediately = var.rds_cluster_info.apply_immediately
allow_major_version_upgrade = var.rds_cluster_info.allow_major_version_upgrade
deletion_protection = var.rds_cluster_info.deletion_protection
lifecycle {
ignore_changes = [
# 3AZ未満が設定されている場合、RDSは自動的に3AZを割り当てようとするため必ず差分が発生してしまう。そのため無視設定としている
# https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/rds_cluster
availability_zones,
# formatdateにより毎回変わるため
final_snapshot_identifier
]
}
}
resource "aws_rds_cluster_instance" "main" {
for_each = var.rds_instance_info
identifier = each.key
cluster_identifier = aws_rds_cluster.main.cluster_identifier
instance_class = each.value.rds_instance_class
engine = aws_rds_cluster.main.engine
engine_version = aws_rds_cluster.main.engine_version
db_parameter_group_name = aws_db_parameter_group.main[each.key].name
publicly_accessible = false
performance_insights_enabled = each.value.performance_insights_enabled
performance_insights_retention_period = each.value.performance_insights_retention_period
auto_minor_version_upgrade = each.value.auto_minor_version_upgrade
preferred_maintenance_window = each.value.preferred_maintenance_window
apply_immediately = each.value.apply_immediately
ca_cert_identifier = each.value.ca_cert_identifier
promotion_tier = each.value.promotion_tier
}
## security group for rds
resource "aws_security_group" "main" {
name = "${var.prefix}-sg-aurora-mysql"
description = "${var.prefix}-sg-aurora-mysql"
vpc_id = var.vpc_id
tags = {
Name = "${var.prefix}-sg-aurora-mysql"
}
}
# security group outbound rule for rds
resource "aws_vpc_security_group_egress_rule" "main" {
for_each = var.rds_outbounds_rules
security_group_id = aws_security_group.main.id
cidr_ipv4 = each.value.cidr_ipv4
from_port = each.value.from_port
to_port = each.value.to_port
ip_protocol = each.value.ip_protocol
}
# security group inbound rule for rds
resource "aws_vpc_security_group_ingress_rule" "main" {
for_each = var.rds_inbounds_rules
security_group_id = aws_security_group.main.id
referenced_security_group_id = each.value.referenced_security_group_id
cidr_ipv4 = each.value.cidr_ipv4
from_port = each.value.from_port
to_port = each.value.to_port
ip_protocol = each.value.ip_protocol
}
## subnet group
resource "aws_db_subnet_group" "main" {
name = "${var.prefix}-subnetgroup"
subnet_ids = var.isolated_subnet_ids
tags = {
Name = "DB subnet group"
}
}
resource "aws_rds_cluster_parameter_group" "main" {
name_prefix = var.rds_cluster_parameter_group_info.rds_cluster_parameter_group_name_prefix
family = var.rds_cluster_and_db_parameter_group_engine_family
description = "${var.rds_cluster_parameter_group_info.rds_cluster_parameter_group_name_prefix} parameter group"
dynamic "parameter" {
for_each = var.rds_cluster_parameter_group_info.rds_cluster_parameters
content {
name = parameter.value.name
value = parameter.value.value
apply_method = parameter.value.apply_method
}
}
lifecycle {
create_before_destroy = true
}
}
resource "aws_db_parameter_group" "main" {
for_each = var.rds_instance_info
name_prefix = each.key
family = var.rds_cluster_and_db_parameter_group_engine_family
description = "${each.key} parameter group"
dynamic "parameter" {
for_each = each.value.rds_db_parameters
content {
name = parameter.value.name
value = parameter.value.value
apply_method = parameter.value.apply_method
}
}
lifecycle {
create_before_destroy = true
}
}
variable "vpc_id" {
type = string
description = "vpc id"
}
variable "isolated_subnet_ids" {
type = list(string)
description = "isolated subnet ids"
}
variable "targetAccountID" {
type = string
description = "target account id"
}
variable "prefix" {
type = string
description = "prefix"
}
variable "rds_cluster_and_db_parameter_group_engine_family" {
type = string
description = "rds parameter group engine family"
}
variable "rds_outbounds_rules" {
type = map(object({
from_port = number
to_port = number
cidr_ipv4 = string
ip_protocol = string
}))
description = "rds security group rules for outbounds"
}
variable "rds_inbounds_rules" {
type = map(object({
referenced_security_group_id = string
cidr_ipv4 = string
from_port = number
to_port = number
ip_protocol = string
}))
description = "rds security group rules for inbounds"
}
variable "rds_cluster_parameter_group_info" {
type = object({
rds_cluster_parameter_group_name_prefix = string
rds_cluster_parameters = map(object({
name = string
value = string
apply_method = string
}))
})
description = "rds cluster parameter group related"
}
variable "rds_cluster_info" {
type = object({
rds_engine_version = string
availability_zones = list(string)
database_name = string
master_username = string
backup_retention_period = number
delete_automated_backups = bool
preferred_backup_window = string
preferred_maintenance_window = string
kms_key_id = string
cloudwatch_logs_exports = list(string)
apply_immediately = bool
allow_major_version_upgrade = bool
deletion_protection = bool
})
description = "rds instance parameter group related"
}
variable "rds_instance_info" {
type = map(object({
rds_instance_class = string
monitoring_interval = number
performance_insights_enabled = bool
performance_insights_retention_period = number
apply_immediately = bool
auto_minor_version_upgrade = bool
preferred_maintenance_window = string
promotion_tier = number
ca_cert_identifier = string
rds_db_parameters = map(object({
name = string
value = string
apply_method = string
}))
}))
description = "rds instance parameter group related"
}
output "aws_rds_cluster_id" {
value = aws_rds_cluster.main.id
description = "rds_cluster_id"
}
Auroraクローン作成のためのTerraformコード
次にAuroraクローンを作成するためのTerraformコードを見ていきます。
クローン元Auroraとはライフサイクルが異なると思うので、別モジュールとしています。
resource "aws_rds_cluster" "clone" {
cluster_identifier = "${var.prefix}-rds-aurora-mysql-clone"
engine = "aurora-mysql"
engine_version = var.rds_cluster_info.rds_engine_version
availability_zones = var.rds_cluster_info.availability_zones
#database_name = var.rds_cluster_info.database_name
#master_username = var.rds_cluster_info.master_username
manage_master_user_password = true
db_subnet_group_name = aws_db_subnet_group.main.name
vpc_security_group_ids = [aws_security_group.main.id]
db_cluster_parameter_group_name = aws_rds_cluster_parameter_group.main.name
backup_retention_period = var.rds_cluster_info.backup_retention_period
preferred_backup_window = var.rds_cluster_info.preferred_backup_window
delete_automated_backups = var.rds_cluster_info.delete_automated_backups
port = 3306
#storage_encrypted = true
storage_type = ""
kms_key_id = var.rds_cluster_info.kms_key_id
enabled_cloudwatch_logs_exports = var.rds_cluster_info.cloudwatch_logs_exports
preferred_maintenance_window = var.rds_cluster_info.preferred_maintenance_window
skip_final_snapshot = false
final_snapshot_identifier = "${var.prefix}-cluster-final-snapshot-${formatdate("YYYYMMDDHHmmss", timestamp())}"
apply_immediately = var.rds_cluster_info.apply_immediately
allow_major_version_upgrade = var.rds_cluster_info.allow_major_version_upgrade
deletion_protection = var.rds_cluster_info.deletion_protection
restore_to_point_in_time {
source_cluster_identifier = var.source_cluster_identifier
restore_type = "copy-on-write"
use_latest_restorable_time = true
}
lifecycle {
ignore_changes = [
# 3AZ未満が設定されている場合、RDSは自動的に3AZを割り当てようとするため必ず差分が発生してしまう。そのため無視設定とする
# https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/rds_cluster
availability_zones,
# formatdateにより毎回変わるため
final_snapshot_identifier
]
}
}
resource "aws_rds_cluster_instance" "clone" {
for_each = var.rds_instance_info
identifier = "${each.key}-clone"
cluster_identifier = aws_rds_cluster.clone.cluster_identifier
instance_class = each.value.rds_instance_class
engine = aws_rds_cluster.clone.engine
engine_version = aws_rds_cluster.clone.engine_version
db_parameter_group_name = aws_db_parameter_group.main[each.key].name
publicly_accessible = false
performance_insights_enabled = each.value.performance_insights_enabled
performance_insights_retention_period = each.value.performance_insights_retention_period
auto_minor_version_upgrade = each.value.auto_minor_version_upgrade
preferred_maintenance_window = each.value.preferred_maintenance_window
apply_immediately = each.value.apply_immediately
ca_cert_identifier = each.value.ca_cert_identifier
promotion_tier = each.value.promotion_tier
}
## security group for rds
resource "aws_security_group" "main" {
name = "${var.prefix}-sg-aurora-mysql-clone"
description = "${var.prefix}-sg-aurora-mysql-clone"
vpc_id = var.vpc_id
tags = {
Name = "${var.prefix}-sg-aurora-mysql-clone"
}
}
# security group outbound rule for rds
resource "aws_vpc_security_group_egress_rule" "main" {
for_each = var.rds_outbounds_rules
security_group_id = aws_security_group.main.id
cidr_ipv4 = each.value.cidr_ipv4
from_port = each.value.from_port
to_port = each.value.to_port
ip_protocol = each.value.ip_protocol
}
# security group inbound rule for rds
resource "aws_vpc_security_group_ingress_rule" "main" {
for_each = var.rds_inbounds_rules
security_group_id = aws_security_group.main.id
referenced_security_group_id = each.value.referenced_security_group_id
cidr_ipv4 = each.value.cidr_ipv4
from_port = each.value.from_port
to_port = each.value.to_port
ip_protocol = each.value.ip_protocol
}
## subnet group
resource "aws_db_subnet_group" "main" {
name = "${var.prefix}-sbgp-aurora-mysql-clone"
subnet_ids = var.isolated_subnet_ids
tags = {
Name = "DB subnet group"
}
}
resource "aws_rds_cluster_parameter_group" "main" {
name_prefix = var.rds_cluster_parameter_group_info.rds_cluster_parameter_group_name_prefix
family = var.rds_cluster_and_db_parameter_group_engine_family
description = "${var.rds_cluster_parameter_group_info.rds_cluster_parameter_group_name_prefix} parameter group"
dynamic "parameter" {
for_each = var.rds_cluster_parameter_group_info.rds_cluster_parameters
content {
name = parameter.value.name
value = parameter.value.value
apply_method = parameter.value.apply_method
}
}
lifecycle {
create_before_destroy = true
}
}
resource "aws_db_parameter_group" "main" {
for_each = var.rds_instance_info
name_prefix = each.key
family = var.rds_cluster_and_db_parameter_group_engine_family
description = "${each.key} parameter group"
dynamic "parameter" {
for_each = each.value.rds_db_parameters
content {
name = parameter.value.name
value = parameter.value.value
apply_method = parameter.value.apply_method
}
}
lifecycle {
create_before_destroy = true
}
}
variable "vpc_id" {
type = string
description = "vpc id"
}
variable "isolated_subnet_ids" {
type = list(string)
description = "isolated subnet ids"
}
variable "prefix" {
type = string
description = "prefix"
}
variable "rds_cluster_and_db_parameter_group_engine_family" {
type = string
description = "rds parameter group engine family"
}
variable "rds_outbounds_rules" {
type = map(object({
from_port = number
to_port = number
cidr_ipv4 = string
ip_protocol = string
}))
description = "rds security group rules for outbounds"
}
variable "rds_inbounds_rules" {
type = map(object({
referenced_security_group_id = string
cidr_ipv4 = string
from_port = number
to_port = number
ip_protocol = string
}))
description = "rds security group rules for inbounds"
}
variable "rds_cluster_parameter_group_info" {
type = object({
rds_cluster_parameter_group_name_prefix = string
rds_cluster_parameters = map(object({
name = string
value = string
apply_method = string
}))
})
description = "rds cluster parameter group related"
}
variable "rds_cluster_info" {
type = object({
rds_engine_version = string
availability_zones = list(string)
database_name = string
master_username = string
backup_retention_period = number
delete_automated_backups = bool
preferred_backup_window = string
preferred_maintenance_window = string
kms_key_id = string
cloudwatch_logs_exports = list(string)
apply_immediately = bool
allow_major_version_upgrade = bool
deletion_protection = bool
})
description = "rds instance parameter group related"
}
variable "rds_instance_info" {
type = map(object({
rds_instance_class = string
monitoring_interval = number
performance_insights_enabled = bool
performance_insights_retention_period = number
apply_immediately = bool
auto_minor_version_upgrade = bool
preferred_maintenance_window = string
promotion_tier = number
ca_cert_identifier = string
rds_db_parameters = map(object({
name = string
value = string
apply_method = string
}))
}))
description = "rds instance parameter group related"
}
variable "source_cluster_identifier" {
type = string
description = "source cluster identifier"
}
ポイント
- database_name, master_username, storage_encryptedについて
- クローンされたAuroraクラスターは、元の DB クラスターと同じ構成で作成されるため、これらのパラメータは指定しないようにしましょう
- ご参考:https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/rds_cluster#restore_to_point_in_time-argument-reference
- restore_to_point_in_time
- 一番の違いは以下のrestore_to_point_in_timeというパラメータの有無です。こちらのパラメータはドキュメント上ではポイントインタイム復元用のパラメータとされているので私はこのパラメータでクローンが作成できると気づくのに少し時間がかかりました。それぞれのパラメータの意味は以下の通りです。
- source_cluster_identifier:復元元Auroraのクラスター識別子を指定するのですが、ドキュメント上は別のAWSアカウントのクラスターから復元する場合はARNを指定と書いてあるのですが、Auroraクローンについては同一アカウントでもARNを指定する必要がありました。
- restore_type:copy-on-writeを選びます
- use_latest_restorable_time:
データベース クラスターを最新の復元可能なバックアップ時刻に復元するには、true に設定
とのこと。基本的にtrueで良いと思います。 -
restore_to_point_in_time { #source_cluster_identifier = "arn:aws:rds:ap-northeast-1:<クローン元AWSアカウントID>:cluster:rds-cluster" source_cluster_identifier = var.source_cluster_identifier restore_type = "copy-on-write" use_latest_restorable_time = true }
まとめ
Terraformで作るAuroraクローンについてお届けしました。この記事が誰かのお役に立てば幸いです。
以上、とーちでした。