A database has a cluster and one o more instances:
DB cluster name: "${local.env}-${local.ci}-<dbname>-cluster"
DB instance name: "${local.env}-${local.ci}-<dbname>-instance-<N>", where N: {0..n} for n-1 desired instances
https://www.reddit.com/r/Terraform/comments/zrvs1x/terraform_with_aurora_serverless_v2/
Eg: strategies
<env>/main.tf
module "rds" {
source = "../modules/rds-noserverless"
### RDS
name_rds_subnet = "${local.env}-${local.ci}-rds-subnet-group"
rds_availability_zones = local.rds_availability_zones #["eu-west-1a", "eu-west-1b"]
maindb_db_prefix = "${local.env}-${local.ci}-mydb" #"${local.env}-${local.ci}-<dbname>"+"-{cluster|instance}"
engine = "aurora-postgresql"
engine_mode = "provisioned" #Serverless v2 uses "provisioned"
skip_final_snapshot = local.rds_skip_final_snapshot
enable_http_endpoint = "true"
backup_retention_period = local.rds_backup_retention_period
engine_version = "16.6"
maindb_db_name = "mydb"
maindb_master_username = "mydb"
maindb_inbound_referenced_security_group_id = aws_security_group.sg_ecs.id #Allow access from ECS
maindb_instance_class = local.rds_instance_class
n_instances = local.rds_n_instances
on_off_centralized = local.on_off_centralized
# CLOUDWATCH
log_retention_in_days = local.cloudwatch_retention_in_days
# VPC
subnets_ids = local.subnet_db_ids
vpc_id = local.vpc_id
}
modules/rds-noserverless/variables.tf
variable "subnets_ids" {}
variable "engine" {}
variable "engine_mode" {}
variable "engine_version" {}
variable "rds_availability_zones" {}
variable "backup_retention_period" {}
variable "skip_final_snapshot" {}
variable "enable_http_endpoint" {}
variable "log_retention_in_days" {}
variable "n_instances" {
description = "Number of instances to be created for the RDS cluster (at least 2 for HA)"
type = number
nullable = false
validation {
condition = var.n_instances >= 1
error_message = "There must be at least 1 DB Cluster Instance"
}
}
variable "name_rds_subnet" {}
variable "on_off_centralized" {
description = "Allow to control the switching on and off of the environment in a centralized way? {'no', 'yes'}"
type = string
nullable = false
validation {
condition = can(regex("^(no|yes)$", var.on_off_centralized))
error_message = "The on_off_centralized value must be one of {'no', 'yes'}"
}
}
variable "vpc_id" {
description = "The ID of the VPC where the RDS cluster will be created"
type = string
nullable = false
}
## maindb ##
variable "maindb_db_prefix" {}
variable "maindb_db_name" {}
variable "maindb_instance_class" {}
variable "maindb_master_username" {}
variable "maindb_inbound_referenced_security_group_id" {
description = "The source security group that is referenced in the rule"
type = string
nullable = false
}
modules/rds-noserverless/rds.tf
locals {
env = data.aws_default_tags.dt.tags.env
ci = data.aws_default_tags.dt.tags.ci
}
data "aws_default_tags" "dt" {
}
################################################################################
# SSM
################################################################################
resource "aws_ssm_parameter" "db_host" {
name = "/${local.env}/${local.ci}/terraform/db_host" # (Required)
type = "String" # (Required)
description = "maindb DB. Hostname of writer instance [Value managed by terraform]" # (Optional)
value = aws_rds_cluster.maindb.endpoint # (Optional) writer
}
resource "aws_ssm_parameter" "db_name" {
# (Required)
name = "/${local.env}/${local.ci}/terraform/db_name"
type = "String"
# (Optional)
description = "maindb DB. Name [Value managed by terraform]" # (Optional)
value = aws_rds_cluster.maindb.database_name
}
resource "aws_ssm_parameter" "db_pass" {
# lifecycle {
# ignore_changes = [value] Do not ignore value, controlled at the random_password keepers
# }
name = "/${local.env}/${local.ci}/terraform/db_pass" # (Required)
type = "SecureString" # (Required)
description = "Master password for db [Value managed by terraform]" # (Optional)
value = random_password.db_pass.result # (Optional)
}
#RDS: At least 8 printable ASCII characters. Can't contain any of the following symbols: / ' " @
resource "random_password" "db_pass" {
length = 16
special = true
override_special = "!#$%&*()-_=+[]{}<>:?"
keepers = { #Arbitrary map of values that, when changed, will trigger recreation of resource
version = "v1"
}
}
resource "aws_ssm_parameter" "db_port" {
name = "/${local.env}/${local.ci}/terraform/db_port"
type = "String"
description = "maindb DB. Port [Value managed by terraform]" # (Optional)
value = aws_rds_cluster.maindb.port
}
resource "aws_ssm_parameter" "db_user" {
name = "/${local.env}/${local.ci}/terraform/db_user"
type = "String"
description = "maindb DB. Username [Value managed by terraform]" # (Optional)
value = aws_rds_cluster.maindb.master_username
}
################################################################################
# VPC > Security Groups
################################################################################
## maindb - SG for controlling ingress to the database
resource "aws_security_group" "sg_db_maindb" {
lifecycle {
create_before_destroy = true
ignore_changes = [name]
}
description = "Allow RDS inbound/outbound connections"
name_prefix = "${local.env}-${local.ci}-rds-maindb-"
#revoke_rules_on_delete = true
tags = {
Name = "${local.env}-${local.ci}-rds-maindb-"
}
vpc_id = var.vpc_id
}
# maindb ingress. Allow access from ECS
resource "aws_vpc_security_group_ingress_rule" "maindb_inbound" {
#cidr_ipv4 #(Optional) The source IPv4 CIDR range
#cidr_ipv6 #(Optional) The source IPv6 CIDR range
description = "Allows inbound traffic from the referenced SG"
from_port = 5432 #(Optional) The start of port range for the TCP and UDP protocols, or an ICMP/ICMPv6 type
ip_protocol = "tcp" #Use -1 to specify all protocols
#prefix_list_id= #(Optional) The ID of the source prefix list
referenced_security_group_id = var.maindb_inbound_referenced_security_group_id #(Optional) The source security group that is referenced in the rule
security_group_id = aws_security_group.sg_db_maindb.id
#tags= #(Optional) A map of tags to assign to the resource
to_port = 5432 # (Optional) The end of port range for the TCP and UDP protocols, or an ICMP/ICMPv6 code.
}
# (IPv4) maindb egress: updates & patches and replication & clustering?
resource "aws_vpc_security_group_egress_rule" "maindb_outbound4" {
security_group_id = aws_security_group.sg_db_maindb.id
description = "Allow all outbound traffic (IPv4)"
cidr_ipv4 = "0.0.0.0/0"
#from_port = 0
ip_protocol = "-1"
#to_port = 0
tags = { Name = "Out IPv4" }
}
# (IPv6) maindb egress: updates & patches and replication & clustering?
resource "aws_vpc_security_group_egress_rule" "maindb_outbound6" {
security_group_id = aws_security_group.sg_db_maindb.id
description = "Allow all outbound traffic (IPv6)"
cidr_ipv6 = "::/0"
#from_port = 0
ip_protocol = "-1"
#to_port = 0
tags = { Name = "Out IPv6" }
}
################################################################################
# RDS subnet group
################################################################################
resource "aws_db_subnet_group" "rds_subnet_group" {
name = var.name_rds_subnet
description = "RDS subnet group"
subnet_ids = var.subnets_ids
}
# ########################################################
# maindb - RDS Aurora Cluster (no Serverless) #### ####
resource "aws_rds_cluster" "maindb" {
depends_on = [aws_cloudwatch_log_group.maindb_rds_cluster_log]
lifecycle {
ignore_changes = [
availability_zones,
engine_version #Comment line to update database version
]
}
allow_major_version_upgrade = true #(Optional) allow major engine version upgrades when changing engine versions
apply_immediately = true #(Optional) whether any cluster modifications are applied immediately, or during the next maintenance window
availability_zones = var.rds_availability_zones
backup_retention_period = var.backup_retention_period
cluster_identifier = "${var.maindb_db_prefix}-cluster"
copy_tags_to_snapshot = true
database_name = var.maindb_db_name
db_subnet_group_name = aws_db_subnet_group.rds_subnet_group.name #NOTE: This must match the db_subnet_group_name specified on every aws_rds_cluster_instance in the cluster
delete_automated_backups = false #(Optional) Specifies whether to remove automated backups immediately after the DB cluster is deleted
deletion_protection = true #(Optional) The database can't be deleted when this value is set
enable_http_endpoint = var.enable_http_endpoint #(Optional) Enable HTTP endpoint (data API)
enable_local_write_forwarding = false
#DOC enabled_cloudwatch_logs_exports = ["postgresql"] creates log group "/aws/rds/cluster/${cluster_identifier}/postgresql"
enabled_cloudwatch_logs_exports = ["postgresql"] #(Optional) Set of log types to export to cloudwatch. If omitted, no logs will be exported. The following log types are supported: audit, error, general, slowquery, postgresql
engine_mode = var.engine_mode # Serverless v2 requires "provisioned"
engine_version = var.engine_version
engine = var.engine
final_snapshot_identifier = "${var.maindb_db_prefix}-cluster-final-snapshot" #(Optional) Name of your final DB snapshot when this DB cluster is deleted. If omitted, no final snapshot will be made
master_password = aws_ssm_parameter.db_pass.value
master_username = var.maindb_master_username
skip_final_snapshot = var.skip_final_snapshot #(Optional) Determines whether a final DB snapshot is created before the DB cluster is deleted. If true is specified, no DB snapshot is created. If false is specified, a DB snapshot is created before the DB cluster is deleted, using the value from final_snapshot_identifier
tags = {
Schedule = var.on_off_centralized
}
vpc_security_group_ids = [aws_security_group.sg_db_maindb.id]
}
#======================================================================================================
#==== maindb - Cluster log ===========================================================================
# DOC. 'resource already exists' issue > https://github.com/hashicorp/terraform-provider-aws/issues/5348
# CloudWatch log group for RDS cluster (In aws_rds_cluster > enabled_cloudwatch_logs_exports = ["postgresql"])
# (In aws_rds_cluster > depends_on = [aws_cloudwatch_log_group.rds_cluster_log])
resource "aws_cloudwatch_log_group" "maindb_rds_cluster_log" {
name = "/aws/rds/cluster/${var.maindb_db_prefix}-cluster/postgresql"
retention_in_days = var.log_retention_in_days
}
# maindb - RDS DB Cluster Instances #### ####
resource "aws_rds_cluster_instance" "maindb_instances" {
count = var.n_instances #One writer + N reader instances (N >= 0), according to needed performance
apply_immediately = true
auto_minor_version_upgrade = true
#availability_zone #(Optional, Computed, Forces new resource) EC2 Availability Zone that the DB instance is created in.
#ca_cert_identifier
cluster_identifier = aws_rds_cluster.maindb.id
copy_tags_to_snapshot = true
#custom_iam_instance_profile
#db_parameter_group_name
#db_subnet_group_name
engine_version = aws_rds_cluster.maindb.engine_version
engine = aws_rds_cluster.maindb.engine
identifier_prefix = "${substr("${var.maindb_db_prefix}-instance", 0, 36)}-" #(Optional, Forces new resource) Creates a unique identifier beginning with the specified prefix. Max. 63 chars
instance_class = var.maindb_instance_class #For Aurora Serverless v2 use db.serverless
#monitoring_interval
#monitoring_role_arn
#performance_insights_enabled
#performance_insights_kms_key_id
#performance_insights_retention_period
#preferred_backup_window
#preferred_maintenance_window
promotion_tier = 0 #(Optional) Default 0. Failover Priority setting on instance level
publicly_accessible = false
# tags = {
# Schedule = var.on_off_centralized #DOC. It seems it's not needed at the instance level
# }
}
Eg: gsm
variables.tf
variable "n_instances" {
description = "Number of instances to be created for the RDS cluster (at least 2 for HA)"
type = number
nullable = false
validation {
condition = var.n_instances >= 1
error_message = "The on_off_centralized value must be one of {'no', 'yes'}"
}
}
rds.tf
locals {
env = data.aws_default_tags.dt.tags.env
ci = data.aws_default_tags.dt.tags.ci
}
data "aws_default_tags" "dt" {
}
resource "aws_db_subnet_group" "rds_subnet_group" {
name = var.name_rds_subnet
description = "RDS subnet group"
subnet_ids = var.subnets_ids
}
#=====================================================================================
#==== db1 db cluster log =============================================================
# DOC. 'resource already exists' issue > https://github.com/hashicorp/terraform-provider-aws/issues/5348
# CloudWatch log group for RDS cluster (In aws_rds_cluster > enabled_cloudwatch_logs_exports = ["postgresql"])
# (In aws_rds_cluster > depends_on = [aws_cloudwatch_log_group.rds_cluster_log])
resource "aws_cloudwatch_log_group" "db1_rds_cluster_log" {
name = "/aws/rds/cluster/${var.db1_cluster_identifier}/postgresql"
retention_in_days = var.log_retention_in_days
}
# db1 RDS DB Cluster (Serverless v2) #### ####
# - Query Editor (Data API) w/ PostgeSQL Serverless v2 > https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/Concepts.Aurora_Fea_Regions_DB-eng.Feature.Data_API.html#Concepts.Aurora_Fea_Regions_DB-eng.Feature.Data_API.apg
resource "aws_rds_cluster" "db1_regional" {
depends_on = [aws_cloudwatch_log_group.db1_rds_cluster_log]
lifecycle {
ignore_changes = [
availability_zones,
engine_version #Comment for updating DBMS version
]
}
allow_major_version_upgrade = true #(Optional) allow major engine version upgrades when changing engine versions
apply_immediately = true #(Optional) whether any cluster modifications are applied immediately, or during the next maintenance window
availability_zones = var.rds_availability_zones
cluster_identifier = var.db1_cluster_identifier
backup_retention_period = var.backup_retention_period
copy_tags_to_snapshot = true
database_name = var.db1_db_name
db_subnet_group_name = aws_db_subnet_group.rds_subnet_group.name #NOTE: This must match the db_subnet_group_name specified on every aws_rds_cluster_instance in the cluster
delete_automated_backups = false #(Optional) Specifies whether to remove automated backups immediately after the DB cluster is deleted
deletion_protection = true #(Optional) The database can't be deleted when this value is set
enable_http_endpoint = var.enable_http_endpoint
enable_local_write_forwarding = false
#DOC enabled_cloudwatch_logs_exports = ["postgresql"] creates log group "/aws/rds/cluster/${cluster_identifier}/postgresql"
enabled_cloudwatch_logs_exports = ["postgresql"] #(Optional) Set of log types to export to cloudwatch. If omitted, no logs will be exported. The following log types are supported: audit, error, general, slowquery, postgresql
engine_mode = var.engine_mode #"provisioned" for Serverles v2
engine_version = var.engine_version
engine = var.engine
final_snapshot_identifier = "${var.db1_cluster_identifier}-final-snapshot" #(Optional) Name of your final DB snapshot when this DB cluster is deleted. If omitted, no final snapshot will be made
master_username = var.db1_master_user
master_password = var.db1_master_pass
serverlessv2_scaling_configuration {
max_capacity = var.rds_cluster_max_capacity #Detaults to 16
min_capacity = var.rds_cluster_min_capacity #Aurora Serverless v2 PostgreSQL values are 0 to 256 in increments of 0.5
}
skip_final_snapshot = var.skip_final_snapshot #s(Optional) Determines whether a final DB snapshot is created before the DB cluster is deleted. If true is specified, no DB snapshot is created. If false is specified, a DB snapshot is created before the DB cluster is deleted, using the value from final_snapshot_identifier
tags = {
Schedule = var.on_off_centralized
}
vpc_security_group_ids = [var.db1_sg_id]
}
# gms instances. RDS DB Cluster Instances (Serverless v2) #### ####
resource "aws_rds_cluster_instance" "db1_instances" {
count = var.n_instances #One writer + N reader instances (N >= 0), according to needed performance
apply_immediately = true
auto_minor_version_upgrade = true
#availability_zone #(Optional, Computed, Forces new resource) EC2 Availability Zone that the DB instance is created in.
#ca_cert_identifier
cluster_identifier = aws_rds_cluster.db1_regional.id
copy_tags_to_snapshot = true
#custom_iam_instance_profile
#db_parameter_group_name
#db_subnet_group_name
engine_version = aws_rds_cluster.db1_regional.engine_version
engine = aws_rds_cluster.db1_regional.engine
identifier_prefix = "${substr("${local.env}-${local.ci}-${var.db1_db_name}-instance", 0, 36)}-" #(Optional, Forces new resource) Creates a unique identifier beginning with the specified prefix. Conflicts with identifier
#identifier = "${local.env}-${local.ci}-instance-${count.index}" #(Optional, Forces new resource) Identifier for the RDS instance, if omitted, Terraform will assign a random, unique identifier.
instance_class = "db.serverless"
#monitoring_interval
#monitoring_role_arn
#performance_insights_enabled
#performance_insights_kms_key_id
#performance_insights_retention_period
#preferred_backup_window
#preferred_maintenance_window
promotion_tier = 1
publicly_accessible = false
# tags = {
# Schedule = var.on_off_centralized # It seems it's not needed at the instance level
# }
}
With Terraform "This is not possible. AWS clear states, you should do snapshot first. Restore on provisioned instance, upgrade to 14.x and then you can add a server less V2 instance reader . you can fail over it if you like to have it like writer. Then you can import in Terraform. But you need to have two resources, cluster and at least 1 instance (in your case V2)."
10) In RDS > Databases
[Modify] DB cluster identifier, add suffix "-v1", eg:
"env-ci-cluster-v1"
[Continue]
"Apply immediatly"
[Modify cluster]
Wait until 'Status' changes to 'Availale'
20) Amazon RDS, select DB
[Actions >Take snapshot]
Choose Snapshot type : "DB cluster"
DB cluster: eg: env-ci-cluster
Snapshot name: v1-TICKET-N
[Take snapshot]
Wait until it finishes. Refresh the snapshots view from time to time.
30) Choose the snapshot & "Actions>Restore snapshot"
Capacity type: Provisioned
Avaiable versions: default major version 13, eg: 13.12
DB instance identifier: "env-ci-instance-1"
DB instance class: Serverless v2
Virtual Private cloud (VPC): <the same one where the original database was>
DB subnet group: env-ci-rds-subnet-group
VPC Security Group: env-ci-rds-sg (leave only this one, remove 'default' if was automatically added)
[Restore DB cluster] (might take ~15 minutes)
40) [Refresh] until the DB cluster changes to 'Available'
Note: Database doesn't need to be ready, only the cluster!
50) Select the cluster & [Modify]
DB engine version: Choose the default for major version 13, eg: 13.12
DB cluster identifier: env-ci-cluster
[Continue]
"Apply immediatly"
[Modify cluster] (might take ~20 minutes)
[60] Once the cluster and database have status 'Available', select the DATABASE
and choose [Modify]
DB instance class: Serverless v2
[Continue] button
Apply immediately
[Modify DB instance] button
Wait for status change to 'Available'
Now that the database is already Serverless v2, lets remove the v1 cluster resource from terraform and import the v2 cluster and instance.
a) Remove the Serverless v1 RDS cluster
#resource "aws_rds_cluster" "default"
b) Add the Serverless v2 RDS Cluster
Note: Use the same cluster id, so that Terraform is able to atomatically refresh the new cluster
# RDS DB Cluster (Serverless v2) #### ####
# - Query Editor (Data API) w/ PostgeSQL Serverless v2 > https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/Concepts.Aurora_Fea_Regions_DB-eng.Feature.Data_API.html#Concepts.Aurora_Fea_Regions_DB-eng.Feature.Data_API.apg
resource "aws_rds_cluster" "default" {
lifecycle {
ignore_changes = [
availability_zones,
engine_version #Comment line for upgrading DB version
]
}
allow_major_version_upgrade = true #(Optional) allow major engine version upgrades when changing engine versions
apply_immediately = true #(Optional) whether any cluster modifications are applied immediately, or during the next maintenance window
availability_zones = var.rds_availability_zones
cluster_identifier = var.cluster_identifier
backup_retention_period = var.backup_retention_period
copy_tags_to_snapshot = true
database_name = var.database_name
db_subnet_group_name = aws_db_subnet_group.rds_subnet_group.name #NOTE: This must match the db_subnet_group_name specified on every aws_rds_cluster_instance in the cluster
delete_automated_backups = false #(Optional) Specifies whether to remove automated backups immediately after the DB cluster is deleted
deletion_protection = true #(Optional) The database can't be deleted when this value is set
enable_http_endpoint = var.enable_http_endpoint
enable_local_write_forwarding = false
enabled_cloudwatch_logs_exports = ["postgresql"]
engine_mode = var.engine_mode #"provisioned" for Serverless v2
engine_version = var.engine_version
engine = var.engine
final_snapshot_identifier ="${var.mydb_cluster_identifier}-final-snapshot"
master_username = var.master_username
master_password = var.cluster_master_password
serverlessv2_scaling_configuration {
max_capacity = var.rds_cluster_max_capacity #Detaults to 16. 1 to 128 in increments of 0.5
min_capacity = var.rds_cluster_min_capacity #0.5 to 128 in increments of 0.5
}
skip_final_snapshot = var.skip_final_snapshot
tags = {
Schedule = var.on_off_centralized
}
vpc_security_group_ids = [var.sg_database_id]
}
# CloudWatch log group for RDS cluster (enabled_cloudwatch_logs_exports = ["postgresql"])
resource "aws_cloudwatch_log_group" "cluster_log" {
name = "/aws/rds/cluster/${aws_rds_cluster.regional.cluster_identifier}/postgresql"
retention_in_days = var.log_retention_in_days
}
c) Add at least one Serverless v2 RDS Cluster instance
# RDS DB Cluster Instance (Serverless v2) #### ####
resource "aws_rds_cluster_instance" "mydb_instances" {
count = 2 #One writer + N reader instances (N >= 0), according to needed performance
apply_immediately = true
auto_minor_version_upgrade = true
#availability_zone #(Optional, Computed, Forces new resource) EC2 Availability Zone that the DB instance is created in.
#ca_cert_identifier
cluster_identifier = aws_rds_cluster.default.id
copy_tags_to_snapshot = true
#custom_iam_instance_profile
#db_parameter_group_name
#db_subnet_group_name
engine_version = aws_rds_cluster.default.engine_version
engine = aws_rds_cluster.default.engine
identifier_prefix="${substr("${local.env}-${local.ci}-${var.db1_db_name}-instance", 0, 36)}-" #(Optional, Forces new resource) Creates a unique identifier beginning with the specified prefix. Conflicts with identifier
#identifier = "${local.env}-${local.ci}-instance-${count.index}" #(Optional, Forces new resource) Identifier for the RDS instance, if omitted, Terraform will assign a random, unique identifier.
instance_class = "db.serverless"
#monitoring_interval
#monitoring_role_arn
#performance_insights_enabled
#performance_insights_kms_key_id
#performance_insights_retention_period
#preferred_backup_window
#preferred_maintenance_window
promotion_tier = 1
publicly_accessible = false
#tags #(Optional) Map of tags to assign to the instance. If configured with a provider default_tags configuration block present, tags with matching keys will overwrite those defined at the provider-level.
}
d) Import the Serverless v2 instance(s) into the terraform aws_rds_cluster_instance resource(s)
In main.tf, add the block:
import {
id = "${local.env}-${local.ci}-instance-1"
to = module.rds.aws_rds_cluster_instance.instance_1
}