AUTOMATE-INFRASTRUCTURE-WITH-IAC-USING-TERRAFORM-PART-2/4
Best practices Tagging
Tagging helps you manage your resources much more efficiently:
- Resources are much better organized in ‘virtual’ groups
- They can be easily filtered and searched from console or programmatically
- Billing team can easily generate reports and determine how much each part of infrastructure costs how much (by department, by type, by environment, etc.)
- You can easily determine resources that are not being used and take actions accordingly
- If there are different teams in the organization using the same account, tagging can help differentiate who owns which resources
Let’s add multiple tags as a default set. For example, in our terraform.tfvars file, we can define default tags.
tags = {
Enviroment = "development"
Owner-Email = "hectore@email.com"
Managed-By = "Terraform"
Billing-Account = "1234567890"
}
Now every time we need to make a change to the tags, we can do that in one single place terraform.tfvars
We need to to declare the variable tags
in variables.tf using the following format
variable "tags" {
description = "A mapping of tags to assign to all resources."
type = map(string)
default = {}
}
Now we can tag all resources using the format below
tags = merge(
var.tags,
{
Name = "<Name for the resource>"
},
)
Internet Gateways & format()
function
Create an Internet Gateway in a separate Terraform file internet_gateway.tf
We have can use format()
function to dynamically generate a unique name for a resource
resource "aws_internet_gateway" "ig" {
vpc_id = aws_vpc.main.id
tags = merge(
var.tags,
{
Name = format("%s-%s!", aws_vpc.main.id,"IG")
}
)
}
format()
format("%s-%s!", aws_vpc.main.id,"IG")
In this example the first of the%s
takes the interpolated value ofaws_vpc.main.id
while the second%s
appends a literal string IG and finally an exclamation mark is added in the end.
Info
This is useful when creating a resource with a
count
function or creating multiple resources using aloop
which requires the key-value pair to be unique
For example, each of our subnets should have a unique name in the tag section. We can accomplish this with format()
function.
tags = merge(
var.tags,
{
Name = format("PrivateSubnet-%s", count.index)
}
)
The output should look something like this
Name = PrvateSubnet-0
Name = PrvateSubnet-1
Name = PrvateSubnet-2
NAT Gateway
To create 1 NAT Gateway and 1 Elastic IP (EIP) address, we’ll use a new file called natgateway.tf. We’ll also create an Elastic IP for the NAT Gateway and introduce the use of depends_on
Documentation to ensure that the Internet Gateway resource must be available before creating the NAT Gateway.
resource "aws_eip" "nat_eip" {
vpc = true
depends_on = [aws_internet_gateway.ig]
tags = merge(
var.tags,
{
Name = format("%s-EIP", var.name)
},
)
}
resource "aws_nat_gateway" "nat" {
allocation_id = aws_eip.nat_eip.id
subnet_id = element(aws_subnet.public.*.id, 0)
depends_on = [aws_internet_gateway.ig]
tags = merge(
var.tags,
{
Name = format("%s-Nat", var.name)
},
)
}
AWS ROUTES
To create routes for both public and private subnets we generate route_tables.tf with resources aws_route_table
, aws_route
, aws_route_table_association
# create private route table
resource "aws_route_table" "private-rtb" {
vpc_id = aws_vpc.main.id
tags = merge(
var.tags,
{
Name = format("%s-private-rtb", var.name)
},
)
}
# associate all private subnets to the private route table
resource "aws_route_table_association" "private-subnets-assoc" {
count = length(aws_subnet.private[*].id)
subnet_id = element(aws_subnet.private[*].id, count.index)
route_table_id = aws_route_table.private-rtb.id
}
# create route table for the public subnets
resource "aws_route_table" "public-rtb" {
vpc_id = aws_vpc.main.id
tags = merge(
var.tags,
{
Name = format("%s-public-rtb", var.name)
},
)
}
# create route for the public route table and attach the internet gateway
resource "aws_route" "public-rtb-route" {
route_table_id = aws_route_table.public-rtb.id
destination_cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.ig.id
}
# associate all public subnets to the public route table
resource "aws_route_table_association" "public-subnets-assoc" {
count = length(aws_subnet.public[*].id)
subnet_id = element(aws_subnet.public[*].id, count.index)
route_table_id = aws_route_table.public-rtb.id
}
Now, when we run terraform plan
and terraform apply
, it should add the following resources to AWS in a multi-AZ setup:
- Our main VPC
- 2 Public subnets
- 4 Private subnets
- 1 Internet Gateway
- 1 NAT Gateway
- 1 EIP
- 2 Route tables
This concludes the Networking part of AWS set up
Compute and Access Control
AWS Identity and Access Management
We want to pass an IAM role to our EC2 instances to grant them access to specific resources. To implement this, we will add the following code to a new file named roles.tf
1. Create AssumeRole
Assume Role uses Security Token Service (STS) API that returns a set of temporary security credentials that you can use to access AWS resources that you might not normally have access to. These temporary credentials consist of an access key ID, a secret access key, and a security token. Typically, you use AssumeRole within your account or for cross-account access.
resource "aws_iam_role" "ec2_instance_role" {
name = "ec2_instance_role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Sid = ""
Principal = {
Service = "ec2.amazonaws.com"
}
},
]
})
tags = merge(
var.tags,
{
Name = "aws assume role"
},
)
}
In this code we are creating AssumeRole with AssumeRole policy. It grants to an entity, in our case it is an EC2, permissions to assume the role.
2. Create IAM policy for this role
This is where we need to define a required policy (i.e., permissions) according to our requirements. For example, allowing an IAM role to perform action describe applied to EC2 instances:
resource "aws_iam_policy" "policy" {
name = "ec2_instance_policy"
description = "A test policy"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = [
"ec2:Describe*",
]
Effect = "Allow"
Resource = "*"
},
]
})
tags = merge(
var.tags,
{
Name = "aws assume policy"
},
)
}
3. Attach the Policy to the IAM Role
This is where, we will be attaching the policy which we created above, to the role we created in the first step.
resource "aws_iam_role_policy_attachment" "test-attach" {
role = aws_iam_role.ec2_instance_role.name
policy_arn = aws_iam_policy.policy.arn
}
4. Create an Instance Profile and interpolate the IAM Role
resource "aws_iam_instance_profile" "ip" {
name = "aws_instance_profile_test"
role = aws_iam_role.ec2_instance_role.name
}
For now we are done with Identity and Management
CREATE SECURITY GROUPS
Terraform Documentation: Security Group and Security Group Rule
We will create all the security groups in a single file named security.tf
and then we will reference these security groups within each resource as needed.
security.tf
# security group for alb, to allow acess from any where for HTTP and HTTPS traffic
resource "aws_security_group" "ext-alb-sg" {
name = "ext-alb-sg"
vpc_id = aws_vpc.main.id
description = "Allow TLS inbound traffic"
ingress {
description = "HTTP"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
description = "HTTPS"
from_port = 22
to_port = 22
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"]
}
tags = merge(
var.tags,
{
Name = "ext-alb-sg"
},
)
}
# security group for bastion, to allow access into the bastion host from you IP
resource "aws_security_group" "bastion_sg" {
name = "vpc_web_sg"
vpc_id = aws_vpc.main.id
description = "Allow incoming HTTP connections."
ingress {
description = "SSH"
from_port = 22
to_port = 22
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"]
}
tags = merge(
var.tags,
{
Name = "Bastion-SG"
},
)
}
#security group for nginx reverse proxy, to allow access only from the extaernal load balancer and bastion instance
resource "aws_security_group" "nginx-sg" {
name = "nginx-sg"
vpc_id = aws_vpc.main.id
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = merge(
var.tags,
{
Name = "nginx-SG"
},
)
}
resource "aws_security_group_rule" "inbound-nginx-http" {
type = "ingress"
from_port = 443
to_port = 443
protocol = "tcp"
source_security_group_id = aws_security_group.ext-alb-sg.id
security_group_id = aws_security_group.nginx-sg.id
}
resource "aws_security_group_rule" "inbound-bastion-ssh" {
type = "ingress"
from_port = 22
to_port = 22
protocol = "tcp"
source_security_group_id = aws_security_group.bastion_sg.id
security_group_id = aws_security_group.nginx-sg.id
}
# security group for ialb, to have acces only from nginx reverser proxy server
resource "aws_security_group" "int-alb-sg" {
name = "my-alb-sg"
vpc_id = aws_vpc.main.id
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = merge(
var.tags,
{
Name = "int-alb-sg"
},
)
}
resource "aws_security_group_rule" "inbound-ialb-https" {
type = "ingress"
from_port = 443
to_port = 443
protocol = "tcp"
source_security_group_id = aws_security_group.nginx-sg.id
security_group_id = aws_security_group.int-alb-sg.id
}
# security group for webservers, to have access only from the internal load balancer and bastion instance
resource "aws_security_group" "webserver-sg" {
name = "my-asg-sg"
vpc_id = aws_vpc.main.id
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = merge(
var.tags,
{
Name = "webserver-sg"
},
)
}
resource "aws_security_group_rule" "inbound-web-https" {
type = "ingress"
from_port = 443
to_port = 443
protocol = "tcp"
source_security_group_id = aws_security_group.int-alb-sg.id
security_group_id = aws_security_group.webserver-sg.id
}
resource "aws_security_group_rule" "inbound-web-ssh" {
type = "ingress"
from_port = 22
to_port = 22
protocol = "tcp"
source_security_group_id = aws_security_group.bastion_sg.id
security_group_id = aws_security_group.webserver-sg.id
}
# security group for datalayer to alow traffic from websever on nfs and mysql port and bastiopn host on mysql port
resource "aws_security_group" "datalayer-sg" {
name = "datalayer-sg"
vpc_id = aws_vpc.main.id
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = merge(
var.tags,
{
Name = "datalayer-sg"
},
)
}
resource "aws_security_group_rule" "inbound-nfs-port" {
type = "ingress"
from_port = 2049
to_port = 2049
protocol = "tcp"
source_security_group_id = aws_security_group.webserver-sg.id
security_group_id = aws_security_group.datalayer-sg.id
}
resource "aws_security_group_rule" "inbound-mysql-bastion" {
type = "ingress"
from_port = 3306
to_port = 3306
protocol = "tcp"
source_security_group_id = aws_security_group.bastion_sg.id
security_group_id = aws_security_group.datalayer-sg.id
}
resource "aws_security_group_rule" "inbound-mysql-webserver" {
type = "ingress"
from_port = 3306
to_port = 3306
protocol = "tcp"
source_security_group_id = aws_security_group.webserver-sg.id
security_group_id = aws_security_group.datalayer-sg.id
}
Note on
aws_security_group_rule
:The
aws_security_group_rule
resource in Terraform is instrumental for detailed management of ingress and egress rules within AWS security groups. It allows the creation of granular and specific rules, separate from the main security group configuration.A key attribute of this resource is
source_security_group_id
. This attribute enables referencing another security group as the source of traffic. This is particularly useful when setting up network rules based on the source security group, rather than specific IP addresses or CIDR ranges. It simplifies configurations where instances in different security groups need to communicate with each other, ensuring dynamic and scalable security management.
CREATE CERTIFICATE FROM AMAZON CERIFICATE MANAGER
Created cert.tf file and add the following code snippets to it.
Terraform Documentation: AWS Certificate manager
cert.tf
# The entire section create a certiface, public zone, and validate the certificate using DNS method
# Create the certificate using a wildcard for all the domains created in hracompany.ga
resource "aws_acm_certificate" "hracompany" {
domain_name = "*.hracompany.ga"
validation_method = "DNS"
tags = merge(
var.tags,
{
Name = format("%s-Cert", var.name)
},
)
}
resource "aws_route53_zone" "hracompany" {
name = "hracompany.ga"
}
# calling the hosted zone
data "aws_route53_zone" "hracompany" {
name = "hracompany.ga"
private_zone = false
depends_on = [aws_route53_zone.hracompany]
}
# selecting validation method
resource "aws_route53_record" "hracompany" {
for_each = {
for dvo in aws_acm_certificate.hracompany.domain_validation_options : dvo.domain_name => {
name = dvo.resource_record_name
record = dvo.resource_record_value
type = dvo.resource_record_type
}
}
allow_overwrite = true
name = each.value.name
records = [each.value.record]
ttl = 60
type = each.value.type
zone_id = data.aws_route53_zone.hracompany.zone_id
}
# validate the certificate through DNS method
resource "aws_acm_certificate_validation" "hracompany" {
certificate_arn = aws_acm_certificate.hracompany.arn
validation_record_fqdns = [for record in aws_route53_record.hracompany : record.fqdn]
}
# create records for tooling
resource "aws_route53_record" "tooling" {
zone_id = data.aws_route53_zone.hracompany.zone_id
name = "tooling.hracompany.ga"
type = "A"
alias {
name = aws_lb.ext-alb.dns_name
zone_id = aws_lb.ext-alb.zone_id
evaluate_target_health = true
}
}
# create records for wordpress
resource "aws_route53_record" "wordpress" {
zone_id = data.aws_route53_zone.hracompany.zone_id
name = "wordpress.hracompany.ga"
type = "A"
alias {
name = aws_lb.ext-alb.dns_name
zone_id = aws_lb.ext-alb.zone_id
evaluate_target_health = true
}
}
(External) Application Load Balancer (ALB)
Internet facing
Created a file called alb.tf to create the ALB, then we create the target group and lastly we will create the listener rule.
Terraform Documentation: ALB, ALB-target, ALB-listener
We create the ALB to balance the traffic between the Instances:
resource "aws_lb" "ext-alb" {
name = "ext-alb"
internal = false
security_groups = [
aws_security_group.ext-alb-sg.id,
]
subnets = [
aws_subnet.public[0].id,
aws_subnet.public[1].id
]
tags = merge(
var.tags,
{
Name = "ACS-ext-alb"
},
)
ip_address_type = "ipv4"
load_balancer_type = "application"
}
To inform our ALB where to route traffic we need to create a Target Group to point to its targets:
resource "aws_lb_target_group" "nginx-tgt" {
health_check {
interval = 10
path = "/healthstatus"
protocol = "HTTPS"
timeout = 5
healthy_threshold = 5
unhealthy_threshold = 2
}
name = "nginx-tgt"
port = 443
protocol = "HTTPS"
target_type = "instance"
vpc_id = aws_vpc.main.id
}
Then we create a Listener for this Target Group
resource "aws_lb_listener" "nginx-listner" {
load_balancer_arn = aws_lb.ext-alb.arn
port = 443
protocol = "HTTPS"
certificate_arn = aws_acm_certificate_validation.oyindamola.certificate_arn
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.nginx-tgt.arn
}
}
Adding the following outputs to output.tf
to print them on screen
output "alb_dns_name" {
value = aws_lb.ext-alb.dns_name
}
output "alb_target_group_arn" {
value = aws_lb_target_group.nginx-tgt.arn
}
(Internal) Application Load Balancer (ALB)
For the Internal Load Balancer, we will apply the same concepts as with the external load balancer. We will achieve this by creating an alb.tf
file with the following content.
#Internal Load Balancers for webservers
#---------------------------------
resource "aws_lb" "ialb" {
name = "ialb"
internal = true
security_groups = [
aws_security_group.int-alb-sg.id,
]
subnets = [
aws_subnet.private[0].id,
aws_subnet.private[1].id
]
tags = merge(
var.tags,
{
Name = "HRA-int-alb"
},
)
ip_address_type = "ipv4"
load_balancer_type = "application"
}
To instruct our ALB on where to route traffic, we need to create a Target Group to define its targets:
# --- target group for wordpress -------
resource "aws_lb_target_group" "wordpress-tgt" {
health_check {
interval = 10
path = "/healthstatus"
protocol = "HTTPS"
timeout = 5
healthy_threshold = 5
unhealthy_threshold = 2
}
name = "wordpress-tgt"
port = 443
protocol = "HTTPS"
target_type = "instance"
vpc_id = aws_vpc.main.id
}
# --- target group for tooling -------
resource "aws_lb_target_group" "tooling-tgt" {
health_check {
interval = 10
path = "/healthstatus"
protocol = "HTTPS"
timeout = 5
healthy_threshold = 5
unhealthy_threshold = 2
}
name = "tooling-tgt"
port = 443
protocol = "HTTPS"
target_type = "instance"
vpc_id = aws_vpc.main.id
}
Then we create a Listener for this Target Group
# For this aspect a single listener was created for the wordpress which is default,
# A rule was created to route traffic to tooling when the host header changes
resource "aws_lb_listener" "web-listener" {
load_balancer_arn = aws_lb.ialb.arn
port = 443
protocol = "HTTPS"
certificate_arn = aws_acm_certificate_validation.hracompany.certificate_arn
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.wordpress-tgt.arn
}
}
# listener rule for tooling target
resource "aws_lb_listener_rule" "tooling-listener" {
listener_arn = aws_lb_listener.web-listener.arn
priority = 99
action {
type = "forward"
target_group_arn = aws_lb_target_group.tooling-tgt.arn
}
condition {
host_header {
values = ["tooling.hracompany.ga"]
}
}
}
CREATING AUTOSCALING GROUPS
In this section, we will create the Auto Scaling Group (ASG) to enable automatic scaling of the EC2 instances based on application traffic.
Before configuring an ASG, we need to create the launch template.
Considering our architecture, we require Auto Scaling Groups for bastion, nginx, wordpress, and tooling. Therefore, we will create two separate files.
asg-bastion-nginx.tf
will contain Launch Template and Auto Scaling Group for Bastion and Nginxasg-wordpress-tooling.tf
will contain Launch Template and Auto Scaling group for wordpress and tooling
Terraform Documentation: SNS-topic, SNS-notification, AutoScaling, Launch-template
Created asg-bastion-nginx.tf
with the following content:
asg-bastion-nginx.tf
#Creating SNS Topic for all the Auto Scaling Groups
resource "aws_sns_topic" "hector-sns" {
name = "Default_CloudWatch_Alarms_Topic"
}
#Creating notification for all the Auto Scaling Groups
resource "aws_autoscaling_notification" "david_notifications" {
group_names = [
aws_autoscaling_group.bastion-asg.name,
aws_autoscaling_group.nginx-asg.name,
aws_autoscaling_group.wordpress-asg.name,
aws_autoscaling_group.tooling-asg.name,
]
notifications = [
"autoscaling:EC2_INSTANCE_LAUNCH",
"autoscaling:EC2_INSTANCE_TERMINATE",
"autoscaling:EC2_INSTANCE_LAUNCH_ERROR",
"autoscaling:EC2_INSTANCE_TERMINATE_ERROR",
]
topic_arn = aws_sns_topic.david-sns.arn
}
#Launch Template for Bastion
resource "random_shuffle" "az_list" {
input = data.aws_availability_zones.available.names
}
resource "aws_launch_template" "bastion-launch-template" {
image_id = var.ami
instance_type = "t2.micro"
vpc_security_group_ids = [aws_security_group.bastion_sg.id]
iam_instance_profile {
name = aws_iam_instance_profile.ip.id
}
key_name = var.keypair
placement {
availability_zone = "random_shuffle.az_list.result"
}
lifecycle {
create_before_destroy = true
}
tag_specifications {
resource_type = "instance"
tags = merge(
var.tags,
{
Name = "bastion-launch-template"
},
)
}
user_data = filebase64("${path.module}/bastion.sh")
}
# ---- Autoscaling for bastion hosts
resource "aws_autoscaling_group" "bastion-asg" {
name = "bastion-asg"
max_size = 2
min_size = 1
health_check_grace_period = 300
health_check_type = "ELB"
desired_capacity = 1
vpc_zone_identifier = [
aws_subnet.public[0].id,
aws_subnet.public[1].id
]
launch_template {
id = aws_launch_template.bastion-launch-template.id
version = "$Latest"
}
tag {
key = "Name"
value = "bastion-launch-template"
propagate_at_launch = true
}
}
# launch template for nginx
resource "aws_launch_template" "nginx-launch-template" {
image_id = var.ami
instance_type = "t2.micro"
vpc_security_group_ids = [aws_security_group.nginx-sg.id]
iam_instance_profile {
name = aws_iam_instance_profile.ip.id
}
key_name = var.keypair
placement {
availability_zone = "random_shuffle.az_list.result"
}
lifecycle {
create_before_destroy = true
}
tag_specifications {
resource_type = "instance"
tags = merge(
var.tags,
{
Name = "nginx-launch-template"
},
)
}
user_data = filebase64("${path.module}/nginx.sh")
}
# ------ Autoscslaling group for reverse proxy nginx ---------
resource "aws_autoscaling_group" "nginx-asg" {
name = "nginx-asg"
max_size = 2
min_size = 1
health_check_grace_period = 300
health_check_type = "ELB"
desired_capacity = 1
vpc_zone_identifier = [
aws_subnet.public[0].id,
aws_subnet.public[1].id
]
launch_template {
id = aws_launch_template.nginx-launch-template.id
version = "$Latest"
}
tag {
key = "Name"
value = "nginx-launch-template"
propagate_at_launch = true
}
}
# attaching autoscaling group of nginx to external load balancer
resource "aws_autoscaling_attachment" "asg_attachment_nginx" {
autoscaling_group_name = aws_autoscaling_group.nginx-asg.id
alb_target_group_arn = aws_lb_target_group.nginx-tgt.arn
}
Created asg-wordpress-tooling.tf
with the following content:
asg-wordpress-tooling.tf
# launch template for wordpress
resource "aws_launch_template" "wordpress-launch-template" {
image_id = var.ami
instance_type = "t2.micro"
vpc_security_group_ids = [aws_security_group.webserver-sg.id]
iam_instance_profile {
name = aws_iam_instance_profile.ip.id
}
key_name = var.keypair
placement {
availability_zone = "random_shuffle.az_list.result"
}
lifecycle {
create_before_destroy = true
}
tag_specifications {
resource_type = "instance"
tags = merge(
var.tags,
{
Name = "wordpress-launch-template"
},
)
}
user_data = filebase64("${path.module}/wordpress.sh")
}
# ---- Autoscaling for wordpress application
resource "aws_autoscaling_group" "wordpress-asg" {
name = "wordpress-asg"
max_size = 2
min_size = 1
health_check_grace_period = 300
health_check_type = "ELB"
desired_capacity = 1
vpc_zone_identifier = [
aws_subnet.private[0].id,
aws_subnet.private[1].id
]
launch_template {
id = aws_launch_template.wordpress-launch-template.id
version = "$Latest"
}
tag {
key = "Name"
value = "wordpress-asg"
propagate_at_launch = true
}
}
# attaching autoscaling group of wordpress application to internal loadbalancer
resource "aws_autoscaling_attachment" "asg_attachment_wordpress" {
autoscaling_group_name = aws_autoscaling_group.wordpress-asg.id
alb_target_group_arn = aws_lb_target_group.wordpress-tgt.arn
}
# launch template for toooling
resource "aws_launch_template" "tooling-launch-template" {
image_id = var.ami
instance_type = "t2.micro"
vpc_security_group_ids = [aws_security_group.webserver-sg.id]
iam_instance_profile {
name = aws_iam_instance_profile.ip.id
}
key_name = var.keypair
placement {
availability_zone = "random_shuffle.az_list.result"
}
lifecycle {
create_before_destroy = true
}
tag_specifications {
resource_type = "instance"
tags = merge(
var.tags,
{
Name = "tooling-launch-template"
},
)
}
user_data = filebase64("${path.module}/tooling.sh")
}
# ---- Autoscaling for tooling -----
resource "aws_autoscaling_group" "tooling-asg" {
name = "tooling-asg"
max_size = 2
min_size = 1
health_check_grace_period = 300
health_check_type = "ELB"
desired_capacity = 1
vpc_zone_identifier = [
aws_subnet.private[0].id,
aws_subnet.private[1].id
]
launch_template {
id = aws_launch_template.tooling-launch-template.id
version = "$Latest"
}
tag {
key = "Name"
value = "tooling-launch-template"
propagate_at_launch = true
}
}
# attaching autoscaling group of tooling application to internal loadbalancer
resource "aws_autoscaling_attachment" "asg_attachment_tooling" {
autoscaling_group_name = aws_autoscaling_group.tooling-asg.id
alb_target_group_arn = aws_lb_target_group.tooling-tgt.arn
}
STORAGE AND DATABASE
Creating Elastic File System (EFS)
In order to create an EFS we need to create a KMS key
AWS Key Management Service (KMS) makes it easy to create and manage cryptographic keys and control their use across a wide range of AWS services and in applications.
Creating efs.tf
with the following content:
efs.tf
# create key from key management system
resource "aws_kms_key" "HRA-kms" {
description = "KMS key "
policy = <<EOF
{
"Version": "2012-10-17",
"Id": "kms-key-policy",
"Statement": [
{
"Sid": "Enable IAM User Permissions",
"Effect": "Allow",
"Principal": { "AWS": "arn:aws:iam::${var.account_no}:user/segun" },
"Action": "kms:*",
"Resource": "*"
}
]
}
EOF
}
# create key alias
resource "aws_kms_alias" "alias" {
name = "alias/kms"
target_key_id = aws_kms_key.HRA-kms.key_id
}
#Let us create EFS and it mount targets
# create Elastic file system
resource "aws_efs_file_system" "HRA-efs" {
encrypted = true
kms_key_id = aws_kms_key.HRA-kms.arn
tags = merge(
var.tags,
{
Name = "HRA-efs"
},
)
}
# set first mount target for the EFS
resource "aws_efs_mount_target" "subnet-1" {
file_system_id = aws_efs_file_system.HRA-efs.id
subnet_id = aws_subnet.private[2].id
security_groups = [aws_security_group.datalayer-sg.id]
}
# set second mount target for the EFS
resource "aws_efs_mount_target" "subnet-2" {
file_system_id = aws_efs_file_system.HRA-efs.id
subnet_id = aws_subnet.private[3].id
security_groups = [aws_security_group.datalayer-sg.id]
}
# create access point for wordpress
resource "aws_efs_access_point" "wordpress" {
file_system_id = aws_efs_file_system.HRA-efs.id
posix_user {
gid = 0
uid = 0
}
root_directory {
path = "/wordpress"
creation_info {
owner_gid = 0
owner_uid = 0
permissions = 0755
}
}
}
# create access point for tooling
resource "aws_efs_access_point" "tooling" {
file_system_id = aws_efs_file_system.HRA-efs.id
posix_user {
gid = 0
uid = 0
}
root_directory {
path = "/tooling"
creation_info {
owner_gid = 0
owner_uid = 0
permissions = 0755
}
}
}
Creating MySQL RDS
Creating rds.tf
with the following content:
# This section will create the subnet group for the RDS instance using the private subnet
resource "aws_db_subnet_group" "HRA-rds" {
name = "HRA-rds"
subnet_ids = [aws_subnet.private[2].id, aws_subnet.private[3].id]
tags = merge(
var.tags,
{
Name = "HRA-rds"
},
)
}
# create the RDS instance with the subnets group
resource "aws_db_instance" "HRA-rds" {
allocated_storage = 20
storage_type = "gp2"
engine = "mysql"
engine_version = "5.7"
instance_class = "db.t2.micro"
name = "daviddb"
username = var.master-username
password = var.master-password
parameter_group_name = "default.mysql5.7"
db_subnet_group_name = aws_db_subnet_group.HRA-rds.name
skip_final_snapshot = true
vpc_security_group_ids = [aws_security_group.datalayer-sg.id]
multi_az = "true"
}
Defining variables in variables.tf
variables.tf
variable "region" {
type = string
description = "The region to deploy resources"
}
variable "vpc_cidr" {
type = string
description = "The VPC cidr"
}
variable "enable_dns_support" {
type = bool
}
variable "enable_dns_hostnames" {
type = bool
}
variable "enable_classiclink" {
type = bool
}
variable "enable_classiclink_dns_support" {
type = bool
}
variable "preferred_number_of_public_subnets" {
type = number
description = "Number of public subnets"
}
variable "preferred_number_of_private_subnets" {
type = number
description = "Number of private subnets"
}
variable "name" {
type = string
default = "ACS"
}
variable "tags" {
description = "A mapping of tags to assign to all resources."
type = map(string)
default = {}
}
variable "ami" {
type = string
description = "AMI ID for the launch template"
}
variable "keypair" {
type = string
description = "key pair for the instances"
}
variable "account_no" {
type = number
description = "the account number"
}
variable "master-username" {
type = string
description = "RDS admin username"
}
variable "master-password" {
type = string
description = "RDS master password"
}
We need to update terraform.tfvars
to declare the values for the variables defined in our variables.tf
.
region = "us-east-1"
vpc_cidr = "10.0.0.0/16"
enable_dns_support = "true"
enable_dns_hostnames = "true"
enable_classiclink = "false"
enable_classiclink_dns_support = "false"
preferred_number_of_public_subnets = "2"
preferred_number_of_private_subnets = "4"
environment = "production"
ami = "ami-0b0af3577fe5e3532"
keypair = "devops"
# Ensure to change this to your account number
account_no = "199055125796"
db-username = "Hector"
db-password = "Hect0rRodriguez!"
tags = {
Enviroment = "production"
Owner-Email = "hectore@email.com"
Managed-By = "Terraform"
Billing-Account = "1234567890"
}
Up to this point, we have a long list of files, which is not a bad start. However, we are going to enhance our organization by implementing the concept of modules in PART3_PROJECT18_Backends_Modules.