Terraform
Upgrade RDS major version
Terraform enables you to manage your Amazon Relational Database Service (RDS) instances over their lifecycle. Using Terraform's built-in lifecycle arguments, you can manage the dependency and upgrade ordering for tightly coupled resources like RDS instances and their parameter groups.
In this tutorial, you will perform a major version upgrade on your RDS instance using Terraform and review how Terraform can handle dependency ordering for your resource management.
Prerequisites
This tutorial assumes that you are familiar with the standard Terraform workflow. If you are new to Terraform, complete the Get Started tutorials first.
For this tutorial, you will need:
- Terraform v0.15+ installed locally
- an AWS account with credentials configured for Terraform
- the AWS CLI
- psql
Clone example repository
Clone the example repository for this tutorial, which contains configuration for an RDS instance and parameter group.
$ git clone https://github.com/hashicorp/learn-terraform-rds-upgrade.git
Change into the repository directory.
$ cd learn-terraform-rds-upgrade
Review configuration
Open main.tf
in your code editor to review the resources you will provision.
This configuration defines:
- an AWS VPC to provision your RDS instance in
- an RDS subnet group, which designates a collection of subnets for RDS placement
- a security group that will allow access to your RDS instance on port
5432
- an RDS parameter group
- an
aws_db_instance
, initially configured with Postgres 15
The aws_db_parameter_group
resource's family
attribute corresponds to the major version of your aws_db_instance
. In this
case, the parameter group family is postgres15
, so the RDS engine version is
Postgres v15.
main.tf
resource "aws_db_parameter_group" "education" {
name_prefix = "education"
family = "postgres15"
parameter {
name = "log_connections"
value = "1"
}
}
While you could use the default AWS parameter groups, it is best practice to maintain a custom one for your RDS instance. You cannot modify the parameters on the default parameter groups maintained by AWS. If you plan on changing an RDS setting in the future, you will need to create a new parameter group anyway.
The configuration also declares input variables for the AWS region and database
password in the variables.tf
file, and output values to use to connect to the
RDS instance in the outputs.tf
file.
Create an RDS instance
In your terminal, initialize the Terraform configuration to install the modules and providers used.
$ terraform init
Initializing modules...
Downloading terraform-aws-modules/vpc/aws 2.77.0 for vpc...
- vpc in .terraform/modules/vpc
Initializing the backend...
Initializing provider plugins...
- Reusing previous version of hashicorp/aws from the dependency lock file
- Installing hashicorp/aws v3.56.0...
- Installed hashicorp/aws v3.56.0 (signed by HashiCorp)
Terraform has been successfully initialized!
You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.
If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
Your configuration requires the master database password as an input variable. To avoid committing sensitive values to version control, we recommend you set the variable either using a command line flag or an environment variable.
Create an environment variable for the password.
$ export TF_VAR_db_password="education123"
Next, apply your configuration to create your resources. Enter yes
when
prompted to confirm the operation. Note that it can take up to 10 minutes to
create an RDS instance.
$ terraform apply
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
##...
Plan: 15 to add, 0 to change, 0 to destroy.
Changes to Outputs:
+ random_pet_name = (known after apply)
+ rds_hostname = (sensitive value)
+ rds_port = (sensitive value)
+ rds_username = (sensitive value)
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value: yes
##...
Apply complete! Resources: 15 added, 0 changed, 0 destroyed.
Outputs:
random_pet_name = "mammal"
rds_hostname = <sensitive>
rds_port = <sensitive>
rds_username = <sensitive>
Seed database with mock data
Next, connect to the database and seed it. The coffees.sql
file in the
repository contains commands that will populate your database with mock data
about HashiCorp-themed coffee beverages.
psql
can access your password using thr PGPASSWORD
environment variable.
Set your Postgres password as an environment variable.
$ export PGPASSWORD=$TF_VAR_db_password
Then, execute the script.
$ psql -h $(terraform output -raw rds_hostname) -U $(terraform output -raw rds_username) postgres -f coffees.sql
Connect to your database to inspect your records.
$ psql -h $(terraform output -raw rds_hostname) -U $(terraform output -raw rds_username) postgres
psql (16.3, server 15.5)
SSL connection (protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384, compression: off)
Type "help" for help.
postgres=>
In the psql prompt, list all of the coffees in your database.
$ select * from coffees;
1 | Packer Spiced Latte | Packed with goodness to spice up your images | | 350 | /packer.png | 2021-08-30 00:00:00 | 2021-08-30 00:00:00 |
2 | Vaulatte | Nothing gives you a safe and secure feeling like a Vaulatte | | 200 | /vault.png | 2021-08-30 00:00:00 | 2021-08-30 00:00:00 |
3 | Nomadicano | Drink one today and you will want to schedule another | | 150 | /nomad.png | 2021-08-30 00:00:00 | 2021-08-30 00:00:00 |
4 | Terraspresso | Nothing kickstarts your day like a provision of Terraspresso | | 150 | /terraform.png | 2021-08-30 00:00:00 | 2021-08-30 00:00:00 |
5 | Vagrante espresso | Stdin is not a tty | | 200 | /vagrant.png | 2021-08-30 00:00:00 | 2021-08-30 00:00:00 |
6 | Connectaccino | Discover the wonders of our meshy service | | 250 | /consul.png | 2021-08-30 00:00:00 | 2021-08-30 00:00:00 |
Type exit
to exit psql.
Update configuration
When managing an RDS instance and a parameter group, you need to bump the engine version and the parameter group family in lockstep for a major version upgrade. Changing the parameter group family is a destructive change, while you can change the database version in-place.
In this Terraform configuration, the aws_db_instance
resource references the
aws_db_parameter_group
, creating an implicit dependency
between the two. As a result, Terraform would first try to upgrade the
parameter group, but would error out because the destructive update would
attempt to remove a parameter group associated with a running RDS instance.
Terraform offers lifecycle meta-arguments to help you manage more complex
resource dependencies such as this one. In this case, you will use the
create_before_destroy
argument to ensure that Terraform provisions the new
parameter group and upgrades your RDS instance before destroying the original
parameter group.
In your main.tf
file, make the following changes:
In the aws_rds_parameter_group
resource definition, update the family
argument to postgres16
and add the lifecycle
argument, as shown below.
main.tf
resource "aws_db_parameter_group" "education" {
name_prefix = "education"
family = "postgres16"
parameter {
name = "log_connections"
value = "1"
}
lifecycle {
create_before_destroy = true
}
}
Update the version
argument for the aws_db_instance
resource to 16
.
main.tf
resource "aws_db_instance" "education" {
identifier = "${random_pet.name.id}education"
instance_class = "db.t3.micro"
allocated_storage = 10
apply_immediately = true
engine = "postgres"
engine_version = "16"
##...
Take an RDS snapshot
Before performing an upgrade, be sure to create a backup of your data. Backing up your data is a good practice when performing operations on your databases, so that you have a point of recovery in the event of data loss or error.
Use the AWS CLI to create an RDS snapshot of your instance.
$ aws rds create-db-snapshot --region us-east-2 --db-snapshot-identifier pre-upgrade-backup-$(terraform output -raw random_pet_name) --db-instance-identifier $(terraform output -raw random_pet_name)education
{
"DBSnapshot": {
"DBSnapshotIdentifier": "pre-upgrade-backup-mammal",
"DBInstanceIdentifier": "mammaleducation",
"Engine": "postgres",
"AllocatedStorage": 10,
"Status": "creating",
"Port": 5432,
"AvailabilityZone": "us-east-2c",
"VpcId": "vpc-0370f405f39ea5b65",
"InstanceCreateTime": "2021-09-02T21:27:25.556000+00:00",
"MasterUsername": "edu",
"EngineVersion": "12.7",
"LicenseModel": "postgresql-license",
"SnapshotType": "manual",
"OptionGroupName": "default:postgres-12",
"PercentProgress": 0,
"StorageType": "gp2",
"Encrypted": false,
"DBSnapshotArn": "arn:aws:rds:us-east-2:561656980159:snapshot:pre-upgrade-backup-mammal",
"IAMDatabaseAuthenticationEnabled": false,
"ProcessorFeatures": [],
"DbiResourceId": "db-XPVVU4IUKTUNZGVCRYYEMW42NM",
"TagList": []
}
}
Navigate to the AWS console to verify that your snapshot exists. Wait until the status is "Available" before moving on to the next section.
Upgrade RDS instance
In your terminal, apply your configuration changes to replace the parameter
group and upgrade the engine version of your RDS instance. Enter yes
when
prompted to approve the operation.
$ terraform apply
##...
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
~ update in-place
+/- create replacement and then destroy
Terraform will perform the following actions:
# aws_db_instance.education will be updated in-place
~ resource "aws_db_instance" "education" {
~ engine_version = "15" -> "16"
id = "db-RBX4IELIDWELBC3JXRPYXKANHU"
~ parameter_group_name = "halibut-education20240510184235754100000001" -> (known after apply)
tags = {}
# (52 unchanged attributes hidden)
}
# aws_db_parameter_group.education must be replaced
/- resource "aws_db_parameter_group" "education" {
~ arn = "arn:aws:rds:us-east-2:561656980159:pg:halibut-education20240510184235754100000001" -> (known after apply)
~ family = "postgres15" -> "postgres16" # forces replacement
~ id = "halibut-education20240510184235754100000001" -> (known after apply)
~ name = "halibut-education20240510184235754100000001" -> (known after apply)
- tags = {} -> null
# (3 unchanged attributes hidden)
# (1 unchanged block hidden)
}
Plan: 1 to add, 1 to change, 1 to destroy.
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value: yes
##...
Apply complete! Resources: 1 added, 1 changed, 1 destroyed.
Outputs:
rds_hostname = <sensitive>
rds_port = <sensitive>
rds_username = <sensitive>
Note
This upgrade may take up to 20 minutes.
Verify upgrade
Verify that the RDS instance is using Postgres 16.
$ psql -h $(terraform output -raw rds_hostname) -U $(terraform output -raw rds_username) postgres -c "SELECT version()"
version
---------------------------------------------------------------------------------------------------------
PostgreSQL 16.1 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 7.3.1 20180712 (Red Hat 7.3.1-12), 64-bit
(1 row)
Destroy infrastructure
Use the AWS CLI to delete the RDS snapshot you created earlier.
$ aws rds delete-db-snapshot --region us-east-2 --db-snapshot-identifier pre-upgrade-backup-$(terraform output -raw random_pet_name)
Once you have completed the tutorial, destroy your infrastructure to avoid
incurring unnecessary costs. Type yes
when prompted to confirm the operation.
$ terraform destroy
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
- destroy
Terraform will perform the following actions:
##...
Plan: 0 to add, 0 to change, 15 to destroy.
Changes to Outputs:
- random_pet_name = "mammal" -> null
- rds_hostname = (sensitive value)
- rds_port = (sensitive value)
- rds_username = (sensitive value)
Do you really want to destroy all resources?
Terraform will destroy all your managed infrastructure, as shown above.
There is no undo. Only 'yes' will be accepted to confirm.
Enter a value: yes
##...
Destroy complete! Resources: 15 destroyed.
Next steps
In this tutorial, you learned how you can use Terraform's lifecycle arguments to manage a major version upgrade of your RDS instances. To learn more about the concepts used in this configuration, review the following tutorials:
- Learn about other lifecycle meta-arguments Terraform supports
- Learn more about navigating common errors and troubleshooting Terraform