DEV Community

Lucas Barret
Lucas Barret

Posted on • Edited on

Terraforming AWS RDS : A practical guide with Postgres

I have recently written a series of articles about PostgreSQL and introduced storytelling. The writing with storytelling was fun, and I will write more articles like this.

Nevertheless, I want to return to the basics and rewrite something with only the technical aspects. Please do not hesitate to give me any feedback about what kind of article you prefer. :)

In this article, we will dive into AWS basics and aim to end up with an entire terraform file to deploy a single availability zone RDS database that is available publicly.

Create an accessible network

To create our database accessible to the public, we will need to create a network first. This will require several components, first a VPC, then an Internet Gateway, subnetworks, and everything sprinkled with a routing table.

Create a VPC

First thing first, we need to configure a VPC. VPC is an acronym for Virtual Private Cloud. This virtual private network has a range of network addresses isolated from other networks. These addresses are not accessible outside your VPC, for now, without any extra component.

A VPC is located in a region and can serve each availability zone of this region. If you want to dig more about VPC, you can go to the AWS documentation

You could use Terraform when you want to deploy a VPC and need your architecture to be auditable, testable, and repeatable.
This is what a terraform could look like for deploying a VPC.

 resource "aws_vpc" "main" {
  cidr_block       = "10.0.0.0/16"
  instance_tenancy = "default"
  tags = {
    Name = "main"
  }
}
Enter fullscreen mode Exit fullscreen mode

With this code, you will deploy a VPC with a range of addresses from 10.0.0.0 to 10.0.255.255, representing 65,536 addresses.

If you want to avoid computation of the number of addresses by head or hand, there is this cidr calculator, but this is only an example; plenty is out there!

We put the instance_tenancy = default; this enables us to work with any RDS database. The other possible value for this is dedicated. But it would be best to ensure your database is approved to work with the dedicated mode.

Divide ut regnes

If we want to create any instances of AWS resources, we must create a subnetwork. In our case, we will create two subnetworks in our VPC.

variable "subnets_cidr" {
  type    = list(string)
  default = ["10.0.1.0/24", "10.0.2.0/24"]
}

variable "azs" {
  type    = list(string)
  default = ["us-east-1a", "us-east-1b"]
}

resource "aws_subnet" "public" {
  count                   = length(var.subnets_cidr)
  vpc_id                  = aws_vpc.main.id
  cidr_block              = element(var.subnets_cidr, count.index)
  availability_zone       = element(var.azs, count.index)
  map_public_ip_on_launch = true
  tags = {
    Name = "Subnet-${count.index + 1}"
  }
}
Enter fullscreen mode Exit fullscreen mode

Excellent, we have created two subnetworks. These two subnetworks have 255 addresses each. These subnets are each in a different availability zone. The first one is in us-east-1a and has the subrange 10.0.1.0/24, and the second one is in us-east-1b and has the subrange 10.0.2.0/24. (If you want to train, put in a comment: What is the number of addresses available in these two subnetworks)

The element(var.name) iterates over the value of a list and assign one value, but we need the count = length(var.subnets_cidr) to declare a loop.

Routing towards the subnet

If we want the ingress and egress traffic to be well redirected, we must create a route table and associate this route table with our subnetworks.

In Terraform, this is special; you have a resource that is available to associate your route table with your subnetworks.
But this is not an AWS resource; this is an abstraction.

You cannot create a route table association object anywhere in AWS. Indeed when you create your route table directly, you can modify it to what subnetworks it is attached to.

resource "aws_route_table" "public-rt" {
  vpc_id = aws_vpc.main.id

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

  tags = {
    Name = "public-rt"
  }
}

resource "aws_route_table_association" "a" {
  count          = length(var.subnets_cidr)
  subnet_id      = element(aws_subnet.public.*.id, count.index)
  route_table_id = aws_route_table.public-rt.id
}
Enter fullscreen mode Exit fullscreen mode

Time to deal with serious things

Here we go, time to deal with the heart of this article: the database.

DB subnet groups

To deploy our database, we need two resources. The first one is the aws_db_instance as you can imagine. But we need an aws_db_subnet_group.

What are these db subnet groups? Let me explain!

A DB subnet group contains several subnets. AWS RDS chooses a subnet from the DB subnet group, gives the database an IP address, and affects it to an availability zone.

resource "aws_db_subnet_group" "default" {
  name       = "main"
  subnet_ids = [aws_subnet.public[0].id, aws_subnet.public[1].id]

  tags = {
    Name = "My DB subnet group"
  }
}
Enter fullscreen mode Exit fullscreen mode

When building our db subnet group, we must give it at least two subnets for the subnets ids, as we have seen above.

Security

Security should be one of your top concerns; controlling traffic in and out is necessary. In particular, in the case of publicly accessible database

We need to describe what traffic can go in and what cannot. This is managed by the aws_security_group in Terraform.

In this resource, you will describe the port you allow access to, the protocol on that port, and the IP address entitled. You will do the same for the traffic that goes out.

resource "aws_security_group" "allow_postgres_traffic" {
  name        = "allow_postgres"
  description = "Allow Postgres traffic"
  vpc_id      = aws_vpc.main.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"]
    ipv6_cidr_blocks = ["::/0"]
  }

  tags = {
    Name = "allow postgres"
  }
}
Enter fullscreen mode Exit fullscreen mode

In that case, there is no such protection. Indeed, we authorize any IP address to access port 5432. Of course, this has to be tuned to your needs, with more restrictions.

Almost there

Now we have to deploy our database. This is straightforward, and the attributes speak for themselves. We want to deploy a t2.micro in the main VPC. This database should be publicly accessible within the security group deployed before.

resource "aws_db_instance" "db-test1" {
  allocated_storage      = 10
  identifier             = "postgres-test2"
  db_subnet_group_name   = aws_db_subnet_group.default.id
  engine                 = "postgres"
  engine_version         = "14"
  instance_class         = "db.t3.micro"
  username               = "postgres"
  password               = "postgres"
  vpc_security_group_ids = [aws_security_group. allow_postgres_traffic.id]
  publicly_accessible    = true
  skip_final_snapshot    = true
}
Enter fullscreen mode Exit fullscreen mode

Deploy it

Thanks for following along with me. Through this article, we have seen how to deploy a publicly accessible database on AWS.

To do so, we need to deploy a variety of resources:

  • A VPC to ensure we have a Private virtual network for our resources.
  • Subnets to divide our Private Network; this is needed if we want to deploy a multi-AZ architecture, now or in the future.
  • A DB subnet group That links the subnet and a database
  • A routing table that will route the external traffic inward and

I have created a gist where the whole file is available if you want to test what we have built here.

Keep in Touch

On Twitter : @yet_anotherDev

On Linkedin : Lucas Barret

Top comments (0)