23-June-25
Learn Provisioning Infrastructure Using Terraform on AWS for EC2 and RDS.
Daily Quest #7: RDS & Serverless Foundations
Amazon RDS (Relational Database Service) is a web service that makes it easier to setup, operate, and scale a relational database in the AWS Cloud. AWS have feature to run code without provisioning and managing server. It's callled Lambda for serverless services. So today i learn to provisioning infrastructure App Server on EC2, MySQL RDS, and reporting using lambda write result an S3 Bucket
Reference :
- https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Welcome.html
- https://docs.aws.amazon.com/lambda/latest/dg/welcome.html
Skenario : Build VPC and IAM mastery by standing up a private RDS database and a serverless reporting Lambda—glued together with least-privilege IAM.
All source code stored in :
- https://github.com/ngurah-bagus-trisna/aws-vpc-iaac
- Create
provider.tf
This file store about provider/extention for resources needed. In this i'm using provider from hashicorp/aws
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
# Configure the AWS Provider
provider "aws" {
region = "ap-southeast-1"
}
- Create
variable.tffor mapping variable and data type for value stored on variable.
variable "vpc_cidr" {
type = string
}
variable "subnet" {
type = map(object({
subnet_range = string
availability_zone = string
type = string
}))
}
variable "natgw_name" {
type = string
}
variable "route_tables" {
type = map(
object({
cidr_source = string
route_destination = string
})
)
}
variable "db_credentials" {
type = object({
username = string
password = string
})
sensitive = true
}
variable "instance" {
type = map(object({
ami = string
instance_type = string
subnet = string
}))
}
- Create
vpc.tffile. This file contain VPC (Virtual Private Cloud) infrasturcture resource want to create. Resources i want to create :
- VPC with subnet
10.0.0.0/16 - 1 Public subnet, with range ip
10.0.1.0/24 - 3 Private subnet, with range ip
10.0.2.0/24, 10.0.3.0/24, 10.0.4.0/24with different availibilty zone - 1 AWS Nat Gateway for Private Subnet
- 1 AWS ElasticIP for eggress Nat Gateway
- 1 AWS Internet Gateway, for public subnet
- 2 Routing, each for public subnet and private subnet
- 2 Security group, one for ingress port
22, and second to ingressrds/database subnet
resource "aws_vpc" "nb-chatgpt-vpc" {
cidr_block = var.vpc_cidr
tags = {
"Name" = "nb-chatgpt-vpc"
}
}
resource "aws_subnet" "nb-subnet" {
depends_on = [aws_vpc.nb-chatgpt-vpc]
vpc_id = aws_vpc.nb-chatgpt-vpc.id
for_each = var.subnet
cidr_block = each.value.subnet_range
availability_zone = each.value.availability_zone
map_public_ip_on_launch = each.key == "public-net" ? true : false
tags = {
"Name" = each.key
"Type" = each.value.type
}
}
resource "aws_internet_gateway" "nb-inet-gw" {
depends_on = [aws_vpc.nb-chatgpt-vpc]
vpc_id = aws_vpc.nb-chatgpt-vpc.id
tags = {
Name = "nb-inet-gw"
}
}
resource "aws_eip" "nb-eip-nat-gw" {
depends_on = [aws_internet_gateway.nb-inet-gw]
tags = {
"Name" = "nb-eip-nat-gw"
}
}
resource "aws_nat_gateway" "nb-nat-gw" {
depends_on = [aws_eip.nb-eip-nat-gw]
allocation_id = aws_eip.nb-eip-nat-gw.id
subnet_id = aws_subnet.nb-subnet["public-net"].id
connectivity_type = "public"
tags = {
"Name" : var.natgw_name
}
}
resource "aws_route_table" "public" {
vpc_id = aws_vpc.nb-chatgpt-vpc.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.nb-inet-gw.id
}
tags = {
Name = "public-rt"
}
}
resource "aws_route_table_association" "public" {
subnet_id = aws_subnet.nb-subnet["public-net"].id
route_table_id = aws_route_table.public.id
}
resource "aws_route_table" "private" {
vpc_id = aws_vpc.nb-chatgpt-vpc.id
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.nb-nat-gw.id
}
tags = {
Name = "private-rt"
}
}
resource "aws_route_table_association" "private" {
for_each = {
for key, subnet in var.subnet :
key => subnet
if subnet.type == "private"
}
subnet_id = aws_subnet.nb-subnet[each.key].id
route_table_id = aws_route_table.private.id
}
resource "aws_security_group" "web-sg" {
depends_on = [aws_subnet.nb-subnet]
name = "web-sg"
description = "Security group to allow access port 22"
vpc_id = aws_vpc.nb-chatgpt-vpc.id
tags = {
"Name" : "web-server-sg"
}
}
resource "aws_vpc_security_group_ingress_rule" "allow-access-ssh" {
depends_on = [aws_security_group.web-sg]
security_group_id = aws_security_group.web-sg.id
cidr_ipv4 = "0.0.0.0/0"
to_port = 22
from_port = 22
ip_protocol = "tcp"
}
- Create
rds.tffor RDS / Database infrastructure.
First i create resource aws_db_subnet_group for mapping subnet allowed access from rds, and tell what subnet using for instance_db. Second i create security group for accessing rds from private subnet. Last i created resource aws_db_instance for provisioning one RDS
resource "aws_db_subnet_group" "nb-db-subnet" {
depends_on = [aws_subnet.nb-subnet]
name = "nb-db-subnet"
subnet_ids = [
for key, subnet in var.subnet : aws_subnet.nb-subnet[key].id
if subnet.type == "private"
]
tags = {
"Name" = "Private DB Subnet Group"
}
}
resource "aws_security_group" "rds-sg" {
depends_on = [aws_subnet.nb-subnet]
name = "rds-sg"
description = "Security group to allow access rds-subnet from private subnets"
vpc_id = aws_vpc.nb-chatgpt-vpc.id
tags = {
Name = "rds-server-sg"
}
}
resource "aws_vpc_security_group_ingress_rule" "allow-access-rds" {
depends_on = [aws_security_group.rds-sg]
security_group_id = aws_security_group.rds-sg.id
cidr_ipv4 = aws_subnet.nb-subnet["private-net-1"].cidr_block
from_port = 3306
to_port = 3306
ip_protocol = "tcp"
}
resource "aws_db_instance" "nb-db" {
depends_on = [aws_security_group.rds-sg, aws_vpc_security_group_ingress_rule.allow-access-rds]
allocated_storage = 10
db_name = "nbdb"
engine = "mysql"
instance_class = "db.t3.micro"
username = var.db_credentials.username
password = var.db_credentials.password
publicly_accessible = false
vpc_security_group_ids = [aws_security_group.rds-sg.id]
db_subnet_group_name = aws_db_subnet_group.nb-db-subnet.name
skip_final_snapshot = true
}
- Create EC2 Instance for public-web
First create aws_network_interface for instance created. Then provisioning instance with aws_instance resources.
resource "aws_network_interface" "instance-interface" {
depends_on = [aws_subnet.nb-subnet]
for_each = var.instance
subnet_id = aws_subnet.nb-subnet[each.value.subnet].id
security_groups = [aws_security_group.web-sg.id]
tags = {
"Name" = "interface ${each.key}"
}
}
resource "aws_instance" "nb-instance" {
for_each = var.instance
depends_on = [aws_network_interface.instance-interface]
ami = each.value.ami
instance_type = each.value.instance_type
key_name = "nb-key"
network_interface {
network_interface_id = aws_network_interface.instance-interface[each.key].id
device_index = 0
}
tags = {
"Name" = "Instance - ${each.key}"
}
}
- Create value file called
dev.tfvarsfor storing all value needed in terraform file
vpc_cidr = "10.0.0.0/16"
subnet = {
"public-net" = {
subnet_range = "10.0.1.0/24"
availability_zone = "ap-southeast-1a"
type = "public"
},
"private-net-1" = {
subnet_range = "10.0.2.0/24"
availability_zone = "ap-southeast-1c"
type = "private"
},
"private-net-2" = {
subnet_range = "10.0.3.0/24"
availability_zone = "ap-southeast-1b"
type = "private"
},
"private-net-3" = {
subnet_range = "10.0.4.0/24"
availability_zone = "ap-southeast-1a"
type = "private"
}
}
natgw_name = "nb-natgw"
route_tables = {
"private" = {
cidr_source = "0.0.0.0/0"
route_destination = "nat"
},
"public" = {
cidr_source = "0.0.0.0/0"
route_destination = "igw"
}
}
instance = {
"web-public" = {
ami = "ami-02c7683e4ca3ebf58"
instance_type = "t2.micro"
subnet = "public-net"
}
}
Result
For today, i only test to hit RDS from private instance.
# installing mysql-client
sudo apt install mysql-client
# testing to login rds
mysql -h <endpoint-rds> -u <user> -p
Result, can connect to rds from private subnet.
