Terraform for AWS serverless v2 RDS cluster with Postgres
AWS RDS Cluster serverless V2
Motivation to move to serverless V2
Why you would need AWS RDS serverless v2? Serverless V2 ACUs even are twice as expensive as V1.
If you need RDS Proxy (AWS flavour of pg_bouncer) you have no choice. RDS proxy is not available for serverless V1.
And serverless V2 is really improved in terms of performance, scalability and availability.
Terraform code
Surprisingly there are no clear documentation so I am going to save you the chase and give ready to use terraform code.
RDS Cluster
resource "aws_db_subnet_group" "this" {
name = "my-serveless-v2-rds-cluster"
subnet_ids = var.subnets # your subnets
}
resource "aws_rds_cluster" "this" {
#checkov:skip=CKV_AWS_139: Deletion protection does not play well with terraform
#checkov:skip=CKV_AWS_327: Just encryption without KMS is ok
#checkov:skip=CKV_AWS_162: plain password is ok for PACS Server, we do not need IAM auth
#checkov:skip=CKV_AWS_324: we do not need DB logs
cluster_identifier = "my-serveless-v2-rds-cluster"
engine = "aurora-postgresql"
engine_mode = "provisioned"
engine_version = "13.6"
master_username = jsondecode(aws_secretsmanager_secret_version.database.secret_string)["username"]
master_password = jsondecode(aws_secretsmanager_secret_version.database.secret_string)["password"]
db_subnet_group_name = aws_db_subnet_group.this.name
vpc_security_group_ids = var.ecs_security_groups # your security groups
skip_final_snapshot = true
deletion_protection = false
storage_encrypted = true
backup_retention_period = 7
copy_tags_to_snapshot = true
db_cluster_parameter_group_name = "default.aurora-postgresql13"
database_name = "mydb"
serverlessv2_scaling_configuration {
min_capacity = 0.5
max_capacity = var.db_max_units # your setting for max ACUs
}
}
The first gotcha here is provisioned
mode. In serverless V1 it was more intuitive serverless
.
Second important thing - Postgres version. Serverless V2 works with versions 13.6 and higher.
And another unexpected thing - you cannot scale down to 0 like with serverless V1. The minimum is 0.5 ACU. This is around $45 per month.
What is the magic with jsondecode
I will explain later, but for the moment that does not matter - this is just credentials for the database.
Important secret ingredient here is serverlessv2_scaling_configuration
- this is the only way to configure serverless V2.
RDS instance
Another gotcha - for RDS serverless V2 you do need instance.
For serverless V1 you just describe your RDS cluster and that’s it.
If you do that for serverless v2 endpoints will freeze forever in status creating
.
resource "aws_rds_cluster_instance" "this" {
identifier = "my-serveless-v2-rds"
cluster_identifier = aws_rds_cluster.this.id
instance_class = "db.serverless"
engine = aws_rds_cluster.this.engine
engine_version = aws_rds_cluster.this.engine_version
auto_minor_version_upgrade = true
monitoring_interval = 5
tags = var.tags
}
This is important to use here magic db.serverless
instance class.
And in engine
and engine_version
we just refer to our cluster to be DRY and do not copy values.
It took some time to create so be patient.
Secrets
And I promised to explain jsondecode
magic.
We create random secret and place it to the AWS Secrets Manager. After that we create RDS Cluster giving this secret as password.
And on the next step from created DB Cluster we add endpoint and database name to the same secret. Thease are no secrets but it’s nice to have them in one place.
All this magic will happen automatically helps to the following code:
resource "random_password" "db" {
length = 16
special = false
}
resource "aws_secretsmanager_secret" "database" {
#checkov:skip=CKV_AWS_149: do not need KMS encryption here
name = "database"
description = "Master username and password for the database"
recovery_window_in_days = 0 # it's perfectly safe to delete and we do not need AWS delete protection
}
resource "aws_secretsmanager_secret_version" "database" {
# Store credentials before RDS cluster created
secret_id = aws_secretsmanager_secret.database.id
secret_string = jsonencode({
username = "myuser"
password = random_password.db.result
})
}
resource "aws_secretsmanager_secret_version" "database_endpoint" {
# Store endpoint after RDS cluster created
secret_id = aws_secretsmanager_secret.database.id
secret_string = jsonencode({
database = jsondecode(aws_secretsmanager_secret_version.database.secret_string)["database"]
username = jsondecode(aws_secretsmanager_secret_version.database.secret_string)["username"]
password = jsondecode(aws_secretsmanager_secret_version.database.secret_string)["password"]
endpoint = aws_rds_cluster.this.endpoint
databse = aws_rds_cluster.this.database_name
})
depends_on = [aws_rds_cluster.this]
}
All manipulations with the secrets performed by terraform so you do not need any additional permissions in IAM. But of cause user that run terraform should have access to the AWS Secrets Manager.