Skip to main content

AWS Relational Database Service (RDS) Postgres: Version1 Playground VPC, Terraform Configuration

1253 words·
AWS AWS CLI Terraform RDS PostgreSQL PostgreSQL HA Cluster Virtual Private Cloud (VPC) EC2
Table of Contents
AWS-RDS-Postgres - This article is part of a series.
Part 1: This Article
GitHub Repository Available

Prerequisites
#

Install AWS CLI
#

# Update packages
sudo apt update

# Unstall zip tool
sudo apt install unzip -y

# Download AWS CLI zip file
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"

# Unzip
unzip awscliv2.zip

# Install
sudo ./aws/install
# Verify installation / check version
/usr/local/bin/aws --version

Configure AWS CLI
#

# Start AWS CLI configuration
aws configure

Install Terraform
#

# Install the HashiCorp GPG key
wget -O- https://apt.releases.hashicorp.com/gpg | gpg --dearmor | sudo tee /usr/share/keyrings/hashicorp-archive-keyring.gpg > /dev/null

# Verify the GPG key fingerprint
gpg --no-default-keyring --keyring /usr/share/keyrings/hashicorp-archive-keyring.gpg --fingerprint

# Add the official HashiCorp repository 
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list

# Install Terraform
sudo apt update && sudo apt-get install terraform
# Verify installation / check version
terraform version



Terraform RDS Postgres Stack
#

File and Folder Structure
#

The file and folder structure of the Terraform project looks like this:

aws-rds-postgres-basic-vpc
├── ec2.tf
├── outputs.tf
├── rds-postgres.tf
├── terraform.tf
├── variables.tf
└── vpc.tf

Terraform Configuration Files
#

Project Folder & Terraform Provider
#

# Create Terraform project folder
TF_PROJECT_NAME=aws-rds-postgres-basic-vpc
mkdir $TF_PROJECT_NAME && cd $TF_PROJECT_NAME

  • terraform.tf
# Terraform Provider
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

# Provider AWS Region
provider "aws" {
  alias  = "aws_region"
  region = var.aws_region
}

Variables
#

  • variables.tf
# AWS Region
variable "aws_region" {
  description = "AWS Region"
  type        = string
  default     = "us-east-1"
}

# VPC Name
variable "vpc_name" {
  description = "The name of the VPC"
  type        = string
  default     = "Example-VPC"
}


## Postgres

# Postgres User
variable "postgres_user" {
  description = "Postgres default user"
  type        = string
  default     = "postgres"
}

# Postgres Password
variable "postgres_pw" {
  description = "Postgres default user"
  type        = string
  default     = "my-secure-pw"
}


## VPC & Subnets

# VPC CIDR
variable "vpc_cidr" {
  description = "VPC CIDR block"
  type        = string
  default     = "10.10.0.0/16"
}

# Public Subnets CIDR (Loop)
variable "subnets_public_cidr" {
  type    = list(string)
  default = ["10.10.0.0/24", "10.10.1.0/24"]
}

# Public Subnets AZ (Loop)
variable "subnets_public_azs" {
  type    = list(string)
  default = ["us-east-1a", "us-east-1b"]
}


## EC2 Instances

# SSH key pair name
variable "key_name" {
  default = "us-east-1-pc-le" # Define key pair name
}

# EC2 Image ID
variable "ami_id" {
  default = "ami-0e2c8caa4b6378d8c" # Define EC2 AMI ID
}

VPC and Subnets
#

  • vpc.tf
# VPC "10.10.0.0/16"
resource "aws_vpc" "vpc" {
  provider              = aws.aws_region
  cidr_block            = var.vpc_cidr
  enable_dns_support    = true
  enable_dns_hostnames  = true

  tags = {
    Name                = "${var.vpc_name}"
    Env                 = "Production"
  }
}

# Public Subnets (Loop)
resource "aws_subnet" "subnets_public" {
  provider                  = aws.aws_region
  count                     = length(var.subnets_public_cidr)
  vpc_id                    = aws_vpc.vpc.id
  cidr_block                = element(var.subnets_public_cidr, count.index)
  availability_zone         = element(var.subnets_public_azs, count.index)
  map_public_ip_on_launch   = true

  tags = {
    Name                    = "${var.vpc_name} Subnet-Public-${count.index + 1}"
    Env                     = "Production"
  }
}


# Internet Gateway
resource "aws_internet_gateway" "vpc_igw" {
  provider      = aws.aws_region
  vpc_id        = aws_vpc.vpc.id

  depends_on = [aws_vpc.vpc]

  tags = {
    Name        = "${var.vpc_name} IGW"
    Env         = "Production"
  }
}


## Routing

# Public Route Table
resource "aws_route_table" "public-rt" {
  provider = aws.aws_region
  vpc_id = aws_vpc.vpc.id

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.vpc_igw.id
  }

  depends_on = [
    aws_vpc.vpc,
    aws_internet_gateway.vpc_igw
    ]

  tags = {
    Name        = "${var.vpc_name} Public Route Table"
    Env         = "Production"
  }
}

# Associate Routes with Subnets
resource "aws_route_table_association" "subnets_public_ra" {
  provider       = aws.aws_region
  count          = length(var.subnets_public_cidr)
  subnet_id      = element(aws_subnet.subnets_public.*.id, count.index)
  route_table_id = aws_route_table.public-rt.id

  depends_on = [aws_route_table.public-rt]
}

RDS PostgreSQL
#

  • rds-postgres.tf
# RDS Subnet Group
resource "aws_db_subnet_group" "rds_subnet_group" {
  provider      = aws.aws_region
  name          = "main"
  subnet_ids    = [aws_subnet.subnets_public[0].id, aws_subnet.subnets_public[1].id]

  depends_on = [aws_subnet.subnets_public]

  tags = {
    Name        = "${var.vpc_name} RDS Subnet Group"
    Env         = "Production"
  }
}

# RDS PostgreSQL
resource "aws_db_instance" "rds_db" {
  provider                = aws.aws_region
  allocated_storage       = 10
  identifier              = "postgres-test2"
  db_subnet_group_name    = aws_db_subnet_group.rds_subnet_group.id
  engine                  = "postgres"
  engine_version          = "14"
  instance_class          = "db.t3.micro"
  username                = var.postgres_user
  password                = var.postgres_pw
  vpc_security_group_ids  = [aws_security_group.sg_postgres.id]
  publicly_accessible     = true
  skip_final_snapshot     = true
  multi_az                = true  # Enables Multi-AZ for HA

  depends_on = [aws_db_subnet_group.rds_subnet_group]
}

# RDS Security Group
resource "aws_security_group" "sg_postgres" {
  provider      = aws.aws_region
  name          = "SG_Allow_Postgres"
  description   = "Allow Postgres traffic"
  vpc_id        = aws_vpc.vpc.id

  ingress {
    description = "Allow Postgres"
    from_port   = 5432
    to_port     = 5432
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port        = 0
    to_port          = 0
    protocol         = "-1"
    cidr_blocks      = ["0.0.0.0/0"]
  }

  depends_on = [aws_vpc.vpc]

  tags = {
    Name = "${var.vpc_name} RDS Postgres"
    Env  = "Production"
  }
}

EC2 Instances
#

  • ec2.tf
# EC2 Instances in Public Subnets (Loop)
resource "aws_instance" "vm" {
  provider                  = aws.aws_region
  count                     = length(var.subnets_public_cidr) # Create as many instances as subnets
  ami                       = var.ami_id
  instance_type             = "t3.small"
  subnet_id                 = aws_subnet.subnets_public[count.index].id
  key_name                  = var.key_name
  vpc_security_group_ids    = [aws_security_group.sg_ec2.id]

  depends_on = [
    aws_vpc.vpc,
    aws_security_group.sg_ec2
  ]

  tags = {
    Name = "VM Public Subnet ${count.index + 1}"
    Env  = "Production"
  }
}


# Security Group for SSH Access and Ping
resource "aws_security_group" "sg_ec2" {
  provider = aws.aws_region
  name        = "SG"
  description = "Security group for SSH access and ping"
  vpc_id      = aws_vpc.vpc.id

  ingress {
    description = "Allow SSH from anywhere"
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    description = "Allow ping"
    from_port   = 8
    to_port     = -1
    protocol    = "icmp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    description = "Allow all outbound traffic"
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  depends_on = [aws_vpc.vpc]

  tags = {
    Name = "SG"
    Env  = "Production"
  }
}

Output
#

  • outputs.tf
# RDS Postgres DNS Endpoints
output "rds_endpoint" {
  value = aws_db_instance.rds_db.endpoint
}


# EC2 Public IPs
output "EC2_Public_IPs" {
  description = "Public IPs of the EC2 instances"
  value       = aws_instance.vm[*].public_ip
}



Configuration Deployment
#

Initialize Terraform Project
#

This will download and install the AWS Terraform provider defined in the terraform.tf file with “hashicorp/aws”, as well as setting up the configuration files in the project directory.

# Initialize the Terraform project
terraform init

Validate Configuration Files
#

# Validates the syntax and structure of Terraform configuration files
terraform validate

# Shell output:
Success! The configuration is valid.

Plan the Deployment
#

# Dry run / preview changes before applying them
terraform plan

Apply the Configuration
#

Note that it takes a while for the setup to complete:
aws_db_instance.rds_db: Creation complete after 13m1s [id=db-MKHTUOXIM667QBROAJ5I7L6GOM]

# Create network stack
terraform apply -auto-approve

# Shell output:
Apply complete! Resources: 13 added, 0 changed, 0 destroyed.

Outputs:

EC2_Public_IPs = [
  "34.228.231.45",
  "3.235.232.96",
]
rds_endpoint = "postgres-test2.cpyy8k66ctzh.us-east-1.rds.amazonaws.com:5432"

RDS Endpoint:

  • RDS provides a single DNS endpoint regardless of whether the database is deployed in a Single-AZ or Multi-AZ configuration

  • This endpoint automatically resolves to the primary instance (writer) in your RDS setup.



Verify PostgreSQL Connectivity
#

SSH Into EC2 Instance
#

# SSH into EC2 instance 1
ssh -i /home/ubuntu/.ssh/us-east-1-pc-le.pem ubuntu@34.228.231.45

# SSH into EC2 instance 2
ssh -i /home/ubuntu/.ssh/us-east-1-pc-le.pem ubuntu@3.235.232.96

Install Postgres Client
#

# Install the "postgresql-common" package, used for the helper scripts
sudo apt install postgresql-common -y

# Add the PostgreSQL (PGDG) repository for latest PostgreSQL versions (bypass installation prompt)
yes "" | sudo /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh

# Install Postgres client
sudo apt install postgresql-client -y

Connect to PostgreSQL Server
#

# Connect to PostgreSQL
psql -h postgres-test2.cpyy8k66ctzh.us-east-1.rds.amazonaws.com -U postgres -d postgres

# Shell output
Password for user postgres: # Enter PW
  • -U postgres Username to authenticate with the PostgreSQL database

  • -d postgres Database name to connect to

# List Postgres version:
SELECT version();

# Shell output:
                                                 version
----------------------------------------------------------------------------------------------------------
 PostgreSQL 14.12 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 7.3.1 20180712 (Red Hat 7.3.1-17), 64-bit
(1 row)
# Exit Postgres
\q



Links #

# GitHub Repository
https://github.com/jueklu/terraform-aws-rds-postgres
AWS-RDS-Postgres - This article is part of a series.
Part 1: This Article