DEV Community

atsushi-ambo
atsushi-ambo

Posted on

Manage an RDS Instance using Terraform

I tried to manage an RDS instance using Terraform. I followed the hashicorp.com tutorial.

Table Of Contents

Prerequisites

  1. Terraform 0.14+ installed locally.
  2. An AWS account with credentials configured for Terraform.
  3. PostgreSQL 14.2+ installed.

1. Clone the sample repository

Clone the repository from Harshicorp of Github.

% git clone https://github.com/hashicorp/learn-terraform-rds.git
Cloning into 'learn-terraform-rds'...
remote: Enumerating objects: 52, done.
remote: Counting objects: 100% (42/42), done.
remote: Compressing objects: 100% (25/25), done.
remote: Total 52 (delta 21), reused 30 (delta 17), pack-reused 10
Receiving objects: 100% (52/52), 16.13 KiB | 8.07 MiB/s, done.
Resolving deltas: 100% (21/21), done.
Enter fullscreen mode Exit fullscreen mode

Change the directory to the installed repository.

cd learn-terraform-rds
Enter fullscreen mode Exit fullscreen mode

2. Check resource configuration

Use the less command to check resource configuration

% less main.tf
Enter fullscreen mode Exit fullscreen mode

Networking components

The VPC and the subnets are defined as the first resource by using the terraform-aws-vpc module.
Caution: For simplicity, the RDS instance is publicly accessible in this tutorial.

module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "2.77.0"

  name                 = "education"
  cidr                 = "10.0.0.0/16"
  azs                  = data.aws_availability_zones.available.names
  public_subnets       = ["10.0.4.0/24", "10.0.5.0/24", "10.0.6.0/24"]
  enable_dns_hostnames = true
  enable_dns_support   = true
}
Enter fullscreen mode Exit fullscreen mode

Memo:

A Terraform module is a group of Terraform resources that are packaged together in a reusable way. Modules are useful because they allow you to reuse your infrastructure configuration code, and reduce duplication of code and effort.

The terraform-aws-modules/vpc/aws module that's used in the configuration file is a module that creates an Amazon Virtual Private Cloud (VPC) in AWS. A VPC is a virtual network dedicated to your AWS account that you can use to launch AWS resources in a logically isolated manner.

The vpc module in the configuration file creates an AWS VPC with the specified cidr (network address range), public_subnets (public subnet addresses), and tags (metadata). The VPC is given a name of example-vpc. Once the module is declared, you can use the outputs of the module to connect other resources to the VPC, such as the RDS instance in the configuration file.

By using the VPC module, you can quickly create a VPC with a consistent configuration and eliminate the need to duplicate the VPC configuration across multiple Terraform projects.
Enter fullscreen mode Exit fullscreen mode

Subnet group

The next resource is an aws_db_subnet_group, which is a collection of subnets where your RDS instance can be provisioned. This subnet group uses the subnets created by the VPC module.

resource "aws_db_subnet_group" "education" {
  name       = "education"
  subnet_ids = module.vpc.public_subnets

  tags = {
    Name = "Education"
  }
}
Enter fullscreen mode Exit fullscreen mode

This subnet group resource is an optional parameter in your aws_db_instance block shown below. Otherwise, Terraform will create your RDS instances in the default VPC.

Database instance

Check the aws_db_instance configuration.

resource "aws_db_instance" "education" {
  identifier             = "education"
  instance_class         = "db.t3.micro"
  allocated_storage      = 5
  engine                 = "postgres"
  engine_version         = "14.1"
  username               = "edu"
  password               = var.db_password
  db_subnet_group_name   = aws_db_subnet_group.education.name
  vpc_security_group_ids = [aws_security_group.rds.id]
  parameter_group_name   = aws_db_parameter_group.education.name
  publicly_accessible    = true
  skip_final_snapshot    = true
}
Enter fullscreen mode Exit fullscreen mode

As I mentioned earlier, the argument of publicly_accessible is set to true, which means that this DB is accessible from the public.You can check all arguments on this page.

Parameter group

Now check the aws_db_parameter_group.

resource "aws_db_parameter_group" "education" {
  name   = "education"
  family = "postgres14"

  parameter {
    name  = "log_connections"
    value = "1"
  }
}
Enter fullscreen mode Exit fullscreen mode

You can specify a name for the db parameter group. Otherwise, Terraform will randomly assign a unique name. You do not need to set a custom DB parameter group. Terraform will use a default one, but the default parameter group cannot be modified later, so if you want to modify it, it is better to use a custom parameter group.
You can check all arguments on this page.

Input variables

You can check the input variable by opening variables.tf file. The DB root user password is configured in the file.

% less variables.tf
variable "db_password" {
  description = "RDS root user password"
  sensitive   = true
}
Enter fullscreen mode Exit fullscreen mode

Terraform can hide the password in the output file, but Terrafor stores it in the state file.

Output variables

You can review the output file. It shows the details of the RDS that will be created.

output "rds_hostname" {
  description = "RDS instance hostname"
  value       = aws_db_instance.education.address
  sensitive   = true
}

output "rds_port" {
  description = "RDS instance port"
  value       = aws_db_instance.education.port
  sensitive   = true
}

output "rds_username" {
  description = "RDS instance root username"
  value       = aws_db_instance.education.username
  sensitive   = true
}
Enter fullscreen mode Exit fullscreen mode

3.Provision resources

First, set the db_password variable as an environment variable.
Then, initialize the terraform configuration.

% export TF_VAR_db_password="hashicorp"
% terraform init
Initializing modules...
Downloading registry.terraform.io/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.32.0...
- Installed hashicorp/aws v3.32.0 (signed by HashiCorp)

Terraform has made some changes to the provider dependency selections recorded
in the .terraform.lock.hcl file. Review those changes and commit them to your
version control system if they represent changes you intended to make.

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.
Enter fullscreen mode Exit fullscreen mode

Next, apply the configuration. When prompted to confirm, answer Yes.
After a few minutes, the RDS creation was complete.

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

Outputs:

rds_hostname = <sensitive>
rds_port = <sensitive>
rds_username = <sensitive>
Enter fullscreen mode Exit fullscreen mode

Then you can try to connect to the RDS by a following command.

psql -h $(terraform output -raw rds_hostname) -p $(terraform output -raw rds_port) -U $(terraform output -raw rds_username) postgres
Enter fullscreen mode Exit fullscreen mode

But, I have received the following error even though I have installed PostgreSQL.

psql -h $(terraform output -raw rds_hostname) -p $(terraform output -raw rds_port) -U $(terraform output -raw rds_username) postgres
zsh: command not found: psql
Enter fullscreen mode Exit fullscreen mode

So I looked it up on Google. It seems like I need to specify the path in ~/.zshrc file.
If you get the same error, run the following command and Add the path

$ vim ~/.zshrc
export PATH="/opt/homebrew/opt/postgresql@15/bin:$PATH"
:wq!
Enter fullscreen mode Exit fullscreen mode

Then, I run the psql command again to connect.
Password is "hashicorp"
After connecting the DB, you can create a database named "hashicorp" within this instance.

postgres=> CREATE DATABASE hashicorp;
CREATE DATABASE
Enter fullscreen mode Exit fullscreen mode

You can verify that the database has been created by running the following command.

postgres-> \list
                                                 List of databases
   Name    |  Owner   | Encoding |   Collate   |    Ctype    | ICU Locale | Locale Provider |   Access privileges   
-----------+----------+----------+-------------+-------------+------------+-----------------+-----------------------
 hashicorp | edu      | UTF8     | en_US.UTF-8 | en_US.UTF-8 |            | libc            | 
 postgres  | edu      | UTF8     | en_US.UTF-8 | en_US.UTF-8 |            | libc            | 
 rdsadmin  | rdsadmin | UTF8     | en_US.UTF-8 | en_US.UTF-8 |            | libc            | rdsadmin=CTc/rdsadmin+
           |          |          |             |             |            |                 | rdstopmgr=Tc/rdsadmin
 template0 | rdsadmin | UTF8     | en_US.UTF-8 | en_US.UTF-8 |            | libc            | =c/rdsadmin          +
           |          |          |             |             |            |                 | rdsadmin=CTc/rdsadmin
 template1 | edu      | UTF8     | en_US.UTF-8 | en_US.UTF-8 |            | libc            | =c/edu               +
           |          |          |             |             |            |                 | edu=CTc/edu
(5 rows)
Enter fullscreen mode Exit fullscreen mode

You can exit by the following command.

\q
Enter fullscreen mode Exit fullscreen mode

4. Modify instance configuration

Open main.tf and change the storage size from 5 to 10GB.

resource "aws_db_instance" "education" {
   name                   = "education"
   instance_class         = "db.t3.micro"
-  allocated_storage      = 5
+  allocated_storage      = 10
##...
}
Enter fullscreen mode Exit fullscreen mode

Before you apply the change, you will need to add the apply_immediately argument to set it to true so that the RDS can be restarted.

resource "aws_db_instance" "education_replica" {
   name                   = "education-replica"
   identifier             = "education-replica"
   replicate_source_db    = aws_db_instance.education.identifier
   instance_class         = "db.t3.micro"
 + apply_immediately      = true
   publicly_accessible    = true
   skip_final_snapshot    = true
   vpc_security_group_ids = [aws_security_group.rds.id]
   parameter_group_name   = aws_db_parameter_group.education.name
}
Enter fullscreen mode Exit fullscreen mode

Then, apply the change.

% terraform apply
data.aws_availability_zones.available: Reading...
module.vpc.aws_vpc.this[0]: Refreshing state... [id=vpc-0b256089f0091780c]
aws_db_parameter_group.education: Refreshing state... [id=education]
data.aws_availability_zones.available: Read complete after 0s [id=us-east-2]
aws_security_group.rds: Refreshing state... [id=sg-014424185901691c4]
module.vpc.aws_internet_gateway.this[0]: Refreshing state... [id=igw-0719c56f100ace0d7]
module.vpc.aws_route_table.public[0]: Refreshing state... [id=rtb-03d7c847b9a295364]
module.vpc.aws_subnet.public[1]: Refreshing state... [id=subnet-008ab5f5d5587f486]
module.vpc.aws_subnet.public[0]: Refreshing state... [id=subnet-062890da2dae35429]
module.vpc.aws_subnet.public[2]: Refreshing state... [id=subnet-02cb916ed21e23d79]
module.vpc.aws_route.public_internet_gateway[0]: Refreshing state... [id=r-rtb-03d7c847b9a2953641080289494]
module.vpc.aws_route_table_association.public[1]: Refreshing state... [id=rtbassoc-03ce6f881ca509cf7]
module.vpc.aws_route_table_association.public[0]: Refreshing state... [id=rtbassoc-0f0f18b8c334690d4]
module.vpc.aws_route_table_association.public[2]: Refreshing state... [id=rtbassoc-02bb355ff4c6d01f1]
aws_db_subnet_group.education: Refreshing state... [id=education]
aws_db_instance.education: Refreshing state... [id=education]

Terraform used the selected providers to generate the following execution plan. Resource
actions are indicated with the following symbols:
  ~ update in-place

Terraform will perform the following actions:

  # aws_db_instance.education will be updated in-place
  ~ resource "aws_db_instance" "education" {
      ~ allocated_storage                     = 5 -> 10
        id                                    = "education"
        name                                  = ""
        tags                                  = {}
        # (49 unchanged attributes hidden)
    }

Plan: 0 to add, 1 to change, 0 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

aws_db_instance.education: Modifying... [id=education]
aws_db_instance.education: Still modifying... [id=education, 10s elapsed]
aws_db_instance.education: Still modifying... [id=education, 20s elapsed]
aws_db_instance.education: Still modifying... [id=education, 30s elapsed]
aws_db_instance.education: Modifications complete after 33s [id=education]

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

Outputs:

rds_hostname = <sensitive>
rds_port = <sensitive>
rds_username = <sensitive>
Enter fullscreen mode Exit fullscreen mode

After completing the change, you can verify that it was applied correctly.

% terraform plan
data.aws_availability_zones.available: Reading...
aws_db_parameter_group.education: Refreshing state... [id=education]
module.vpc.aws_vpc.this[0]: Refreshing state... [id=vpc-0b256089f0091780c]
data.aws_availability_zones.available: Read complete after 1s [id=us-east-2]
aws_security_group.rds: Refreshing state... [id=sg-014424185901691c4]
module.vpc.aws_internet_gateway.this[0]: Refreshing state... [id=igw-0719c56f100ace0d7]
module.vpc.aws_subnet.public[0]: Refreshing state... [id=subnet-062890da2dae35429]
module.vpc.aws_subnet.public[2]: Refreshing state... [id=subnet-02cb916ed21e23d79]
module.vpc.aws_subnet.public[1]: Refreshing state... [id=subnet-008ab5f5d5587f486]
module.vpc.aws_route_table.public[0]: Refreshing state... [id=rtb-03d7c847b9a295364]
module.vpc.aws_route.public_internet_gateway[0]: Refreshing state... [id=r-rtb-03d7c847b9a2953641080289494]
module.vpc.aws_route_table_association.public[0]: Refreshing state... [id=rtbassoc-0f0f18b8c334690d4]
module.vpc.aws_route_table_association.public[1]: Refreshing state... [id=rtbassoc-03ce6f881ca509cf7]
module.vpc.aws_route_table_association.public[2]: Refreshing state... [id=rtbassoc-02bb355ff4c6d01f1]
aws_db_subnet_group.education: Refreshing state... [id=education]
aws_db_instance.education: Refreshing state... [id=education]

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.
Enter fullscreen mode Exit fullscreen mode

5. Create a Read Replica

Open main.tf file and add the following block.

resource "aws_db_instance" "education_replica" {
   name                   = "education-replica"
   identifier             = "education-replica"
   replicate_source_db    = aws_db_instance.education.identifier
   instance_class         = "db.t3.micro"
   apply_immediately      = true
   publicly_accessible    = true
   skip_final_snapshot    = true
   vpc_security_group_ids = [aws_security_group.rds.id]
   parameter_group_name   = aws_db_parameter_group.education.name
}
Enter fullscreen mode Exit fullscreen mode

And add this argument in the primary instance block.

resource "aws_db_instance" "education" {
    name                      = "education"
    instance_class            = "db.t3.micro"
    allocated_storage         = 10
+   backup_retention_period   = 1
##...
}
Enter fullscreen mode Exit fullscreen mode

Add this block into outputs.tf file.

output "rds_replica_connection_parameters" {
  description = "RDS replica instance connection parameters"
  value       = "-h ${aws_db_instance.education_replica.address} -p ${aws_db_instance.education_replica.port} -U ${aws_db_instance.education_replica.username} postgres"
}
Enter fullscreen mode Exit fullscreen mode

Then apply the change with "terraform apply".
You will see the following message at the bottom of the screen.

aws_db_instance.education_replica: Creation complete after 11m47s [id=education-replica]

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

Outputs:

rds_hostname = <sensitive>
rds_port = <sensitive>
rds_replica_connection_parameters = "-h education-replica.cl6o1zzqtrp1.us-east-2.rds.amazonaws.com -p 5432 -U edu postgres"
Enter fullscreen mode Exit fullscreen mode

Now you can connect to the read replica.

% psql $(terraform output -raw rds_replica_connection_parameters)
Password for user edu: 
Enter fullscreen mode Exit fullscreen mode

List database to verify that the database has actually been created.

postgres=> \list
                                                 List of databases
   Name    |  Owner   | Encoding |   Collate   |    Ctype    | ICU Locale | Locale Provider |   Access privileges   
-----------+----------+----------+-------------+-------------+------------+-----------------+-----------------------
 hashicorp | edu      | UTF8     | en_US.UTF-8 | en_US.UTF-8 |            | libc            | 
 postgres  | edu      | UTF8     | en_US.UTF-8 | en_US.UTF-8 |            | libc            | 
 rdsadmin  | rdsadmin | UTF8     | en_US.UTF-8 | en_US.UTF-8 |            | libc            | rdsadmin=CTc/rdsadmin+
           |          |          |             |             |            |                 | rdstopmgr=Tc/rdsadmin
 template0 | rdsadmin | UTF8     | en_US.UTF-8 | en_US.UTF-8 |            | libc            | =c/rdsadmin          +
           |          |          |             |             |            |                 | rdsadmin=CTc/rdsadmin
 template1 | edu      | UTF8     | en_US.UTF-8 | en_US.UTF-8 |            | libc            | =c/edu               +
           |          |          |             |             |            |                 | edu=CTc/edu
(5 rows)
Enter fullscreen mode Exit fullscreen mode

6. Clean up infrastructure

You can delete the infrastructure by this command.

% terraform destroy 
....
module.vpc.aws_vpc.this[0]: Destroying... [id=vpc-0b256089f0091780c]
module.vpc.aws_vpc.this[0]: Destruction complete after 1s

Destroy complete! Resources: 15 destroyed.
Enter fullscreen mode Exit fullscreen mode

Well, this is the end of this tutorial.
I'm getting used to Terraform, so I'll keep learning.
You may refer to this site to review the argument of aws RDS.

Top comments (0)