Terraform to deploy your Infrastructure
Introduction:
In this article, we will see how can we deploy our IT infrastructure
using Terraform. Before we get into the procedure and steps, let’s
first check the need of using Terraform for such task.
Why do we need Terraform?
I already mentioned in my earlier article, that Terraform can be
blended with almost all providers – be it AWS, Azure, Google Cloud,
VmWare etc. So, for obvious reason, it will give you more flexibility
when you deploy your infrastructure with Terraform. May be you have
chosen AWS for a specific part of your application. You can choose
Azure or Google cloud for other part of your same application easily.
You can manage your infrastructure from a single place or repository.
So, Terraform give you the privilege to think for other cloud
providers when your application or website is already in RUN mode.
Secondly, It
centralize the infrastructural components. You don’t need to remember
where you have deployed your A component or B component – which
Availability Zone, Which VPC, which messaging queue and which cloud
provider did you choose. Because, all of those are stored in a
repository in the form of Terraform file.
Third, at any point
if you want to go back to the previous step of deployment, it is
possible as Terraform keep state file for each and every change.
In a lucid word, you
are versioning your infrastructure in a single place that does not
care about the cloud provider you’ve chosen. That’s the beauty or
the main reason for opting Terraform to deploy your infrastructure.
I hope, it make
sense now to use Terraform for deploying your IT infrastructure.
Create/Frame a commonly used case:
We will create a simple application in AWS using Terraform. We believe, it will assist many users
who wants to use Terraform for deploying their infrastructure in AWS. For
simplicity, we will restrict our discussion within AWS.
When you want to deploy any application to AWS, it is a mandate to make a planning prior to actual deployment. This will help you to formulate the Terraform code.
So, let's start.
I. Define provider
First, we need to define the provider. Because, Terraform needs to know where to connect and how to connect. Here, we will be defining for AWS.
There are 3 different ways to authenticate with AWS.
- Static credentials.
- Placing credentials in environmental variables
- Using shared credential file.
We prefer to set it via shared credential file.
Create a file name called "provider.tf" and paste the below contents:
# Configure the AWS Provider
provider "aws" {
region = "us-east-1"
shared_credentials_file = "~/.aws/credentials"
profile = "demo"
}
data "aws_availability_zones" "available" {}
Be careful about the profile value. It should match with the value that you put in AWS credentials and config file.
II. Declare variables
Now, we will first declare the variable that we need. Create a file called tfvars.tf and insert the below content.
variable "public_key_path" {
default = "/home/user/.ssh/id_rsa.pub"
}
variable "key_name" {
default = "terraform"
}
variable "aws_region" {
description = "AWS region to launch servers."
default = "us-east-1"
}
# Amazon Linux
variable "aws_amis" {
default = {
us-east-1 = "ami-bb5642ac"
}
}
variable "dnszonename" {
default = "aws.techreader.net"
description = "Internal DNS name"
}
variable "vpc_cidr" {
default = "10.0.0.0/20"
description = "VPC CIDR range"
}
variable "public_subnet_a" {
default = "10.0.0.0/24"
description = "Public subnet AZ A"
}
variable "public_subnet_b" {
default = "10.0.4.0/24"
description = "Public subnet AZ B"
}
variable "public_subnet_c" {
default = "10.0.8.0/24"
description = "Public subnet AZ C"
}
variable "private_subnet_a" {
default = "10.0.1.0/24"
description = "Private subnet AZ A"
}
variable "private_subnet_b" {
default = "10.0.5.0/24"
description = "Private subnet AZ B"
}
variable "private_subnet_c" {
default = "10.0.9.0/24"
description = "Private subnet AZ C"
}
*user will be your ssh user where you generated ssh keys. If you have not done yet, you need to fork ssh-keygen -t rsa and follow the instructions.
III. Create VPC stuffs
In this part, we will be defining the basic network. So, we will create VPC, Subnets, Internet Gateway, Route tables, NAT gateways at this step.
Sample Terraform configuration (vpc.tf) will look like below:
# Create a VPC
resource "aws_vpc" "default" {
cidr_block = "${var.vpc_cidr}"
enable_dns_support = true
enable_dns_hostnames = true
tags = {
Name = "VPC"
}
}
# Create Public subnets
resource "aws_subnet" "PublicSubnetA" {
vpc_id = "${aws_vpc.default.id}"
cidr_block = "${var.public_subnet_a}"
tags = {
Name = "Public Subnet A"
}
availability_zone = "${data.aws_availability_zones.available.names[0]}"
}
resource "aws_subnet" "PublicSubnetB" {
vpc_id = "${aws_vpc.default.id}"
cidr_block = "${var.public_subnet_b}"
tags = {
Name = "Public Subnet B"
}
availability_zone = "${data.aws_availability_zones.available.names[1]}"
}
resource "aws_subnet" "PublicSubnetC" {
vpc_id = "${aws_vpc.default.id}"
cidr_block = "${var.public_subnet_c}"
tags = {
Name = "Public Subnet C"
}
availability_zone = "${data.aws_availability_zones.available.names[2]}"
}
# Create Pravate subnets
resource "aws_subnet" "PrivateSubnetA" {
vpc_id = "${aws_vpc.default.id}"
cidr_block = "${var.private_subnet_a}"
tags = {
Name = "Private Subnet A"
}
availability_zone = "${data.aws_availability_zones.available.names[0]}"
}
resource "aws_subnet" "PrivateSubnetB" {
vpc_id = "${aws_vpc.default.id}"
cidr_block = "${var.private_subnet_b}"
tags = {
Name = "Private Subnet B"
}
availability_zone = "${data.aws_availability_zones.available.names[1]}"
}
resource "aws_subnet" "PrivateSubnetC" {
vpc_id = "${aws_vpc.default.id}"
cidr_block = "${var.private_subnet_c}"
tags = {
Name = "Private Subnet C"
}
availability_zone = "${data.aws_availability_zones.available.names[2]}"
}
# Route table management
resource "aws_route_table_association" "PublicSubnetA" {
subnet_id = "${aws_subnet.PublicSubnetA.id}"
route_table_id = "${aws_route_table.public_route_a.id}"
}
resource "aws_route_table_association" "PublicSubnetB" {
subnet_id = "${aws_subnet.PublicSubnetB.id}"
route_table_id = "${aws_route_table.public_route_b.id}"
}
resource "aws_route_table_association" "PublicSubnetC" {
subnet_id = "${aws_subnet.PublicSubnetC.id}"
route_table_id = "${aws_route_table.public_route_c.id}"
}
resource "aws_route_table_association" "PrivateSubnetA" {
subnet_id = "${aws_subnet.PrivateSubnetA.id}"
route_table_id = "${aws_route_table.private_route_a.id}"
}
resource "aws_route_table_association" "PrivateSubnetB" {
subnet_id = "${aws_subnet.PrivateSubnetB.id}"
route_table_id = "${aws_route_table.private_route_b.id}"
}
resource "aws_route_table_association" "PrivateSubnetC" {
subnet_id = "${aws_subnet.PrivateSubnetC.id}"
route_table_id = "${aws_route_table.private_route_c.id}"
}
#Internet Gateway
resource "aws_internet_gateway" "gw" {
vpc_id = "${aws_vpc.default.id}"
tags = {
Name = "Internet Gateway"
}
}
# NAT gateways
resource "aws_eip" "natgw_a" {
vpc = true
}
resource "aws_eip" "natgw_b" {
vpc = true
}
resource "aws_eip" "natgw_c" {
vpc = true
}
resource "aws_nat_gateway" "public_nat_a" {
allocation_id = "${aws_eip.natgw_a.id}"
subnet_id = "${aws_subnet.PublicSubnetA.id}"
depends_on = ["aws_internet_gateway.gw"]
}
resource "aws_nat_gateway" "public_nat_b" {
allocation_id = "${aws_eip.natgw_b.id}"
subnet_id = "${aws_subnet.PublicSubnetB.id}"
depends_on = ["aws_internet_gateway.gw"]
}
resource "aws_nat_gateway" "public_nat_c" {
allocation_id = "${aws_eip.natgw_c.id}"
subnet_id = "${aws_subnet.PublicSubnetC.id}"
depends_on = ["aws_internet_gateway.gw"]
}
# Preparing ACL
resource "aws_network_acl" "all" {
vpc_id = "${aws_vpc.default.id}"
egress {
protocol = "-1"
rule_no = 2
action = "allow"
cidr_block = "0.0.0.0/0"
from_port = 0
to_port = 0
}
ingress {
protocol = "-1"
rule_no = 1
action = "allow"
cidr_block = "0.0.0.0/0"
from_port = 0
to_port = 0
}
tags = {
Name = "open acl"
}
}
# Associations
resource "aws_route_table" "public_route_a" {
vpc_id = "${aws_vpc.default.id}"
tags = {
Name = "Public Route A"
}
route {
cidr_block = "0.0.0.0/0"
gateway_id = "${aws_internet_gateway.gw.id}"
}
}
resource "aws_route_table" "public_route_b" {
vpc_id = "${aws_vpc.default.id}"
tags = {
Name = "Public Route B"
}
route {
cidr_block = "0.0.0.0/0"
gateway_id = "${aws_internet_gateway.gw.id}"
}
}
resource "aws_route_table" "public_route_c" {
vpc_id = "${aws_vpc.default.id}"
tags = {
Name = "Public Route C"
}
route {
cidr_block = "0.0.0.0/0"
gateway_id = "${aws_internet_gateway.gw.id}"
}
}
resource "aws_route_table" "private_route_a" {
vpc_id = "${aws_vpc.default.id}"
tags = {
Name = "Private Route A"
}
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = "${aws_nat_gateway.public_nat_a.id}"
}
}
resource "aws_route_table" "private_route_b" {
vpc_id = "${aws_vpc.default.id}"
tags = {
Name = "Private Route B"
}
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = "${aws_nat_gateway.public_nat_b.id}"
}
}
resource "aws_route_table" "private_route_c" {
vpc_id = "${aws_vpc.default.id}"
tags = {
Name = "Private Route C"
}
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = "${aws_nat_gateway.public_nat_c.id}"
}
}
IV. Create ec2 artifacts
In this section, we will create our desired ec2 instances and attach them to respective AZ
The Terraform config(ec2.tf) will look like below:
resource "aws_key_pair" "auth" {
key_name = "${var.key_name}"
public_key = "${file(var.public_key_path)}"
}
resource "aws_launch_configuration" "autoscale_launch" {
image_id = "${lookup(var.aws_amis, var.aws_region)}"
instance_type = "t2.micro"
security_groups = ["${aws_security_group.sec_web.id}"]
key_name = "${aws_key_pair.auth.id}"
user_data = <<-EOF
#!/bin/bash
sudo yum -y update
sudo yum -y install nginx
EOF
lifecycle {
create_before_destroy = true
}
}
resource "aws_autoscaling_group" "autoscale_group" {
launch_configuration = "${aws_launch_configuration.autoscale_launch.id}"
vpc_zone_identifier = ["${aws_subnet.PrivateSubnetA.id}","${aws_subnet.PrivateSubnetB.id}","${aws_subnet.PrivateSubnetC.id}"]
load_balancers = ["${aws_lb.alb.name}"]
min_size = 3
max_size = 3
tag {
key = "Name"
value = "autoscale"
propagate_at_launch = true
}
}
# Security Groups
resource "aws_security_group" "sec_web" {
name = "sec_web"
description = "Used for autoscale group"
vpc_id = "${aws_vpc.default.id}"
# HTTP access from anywhere
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
# outbound internet access
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
lifecycle {
create_before_destroy = true
}
}
resource "aws_security_group" "sec_lb" {
name = "sec_elb"
vpc_id = "${aws_vpc.default.id}"
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_lb" "alb" {
name = "alb"
subnets = ["${aws_subnet.PublicSubnetA.id}","${aws_subnet.PublicSubnetB.id}","${aws_subnet.PublicSubnetC.id}"]
security_groups = ["${aws_security_group.sec_lb.id}"]
internal = false
idle_timeout = 60
tags = {
Name = "alb"
}
}
# ALB target definition
resource "aws_lb_target_group" "alb_target_group" {
name = "alb-target-group"
port = "80"
protocol = "HTTP"
vpc_id = "${aws_vpc.default.id}"
tags = {
name = "alb_target_group"
}
stickiness {
type = "lb_cookie"
cookie_duration = 1800
enabled = true
}
health_check {
healthy_threshold = 3
unhealthy_threshold = 10
timeout = 5
interval = 10
path = "/"
port = 80
}
}
resource "aws_autoscaling_attachment" "alb_autoscale" {
alb_target_group_arn = "${aws_lb_target_group.alb_target_group.arn}"
autoscaling_group_name = "${aws_autoscaling_group.autoscale_group.id}"
}
resource "aws_lb_listener" "alb_listener" {
load_balancer_arn = "${aws_lb.alb.arn}"
port = 80
protocol = "HTTP"
default_action {
target_group_arn = "${aws_lb_target_group.alb_target_group.arn}"
type = "forward"
}
}
After creating the four Terraform files, you need to run below Terraform commands to create your desired AWS infrastructure:
>> terraform init
Sample output will look like:
Initializing modules...
Downloading terraform-aws-modules/vpc/aws 2.9.0 for vpc...
- vpc in .terraform/modules/vpc/terraform-aws-modules-terraform-aws-vpc-b51422b
Initializing the backend...
Initializing provider plugins...
- Checking for available provider plugins...
- Downloading plugin for provider "aws" (terraform-providers/aws) 2.24.0...
The following providers do not have any version constraints in configuration,
so the latest version was installed.
To prevent automatic upgrades to new major versions that may contain breaking
changes, it is recommended to add version = "..." constraints to the
corresponding provider blocks in configuration, with the constraint strings
suggested below.
* provider.aws: version = "~> 2.24"
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.
>> terraform validate
Success! The configuration is valid.
>> terraform plan
While forking the above command, you will find that all your required resources are provisioned. You will also see the number of artifacts that will be created, modified or destroyed.
Last few lines of the output will look like below:
Plan: 36 to add, 0 to change, 0 to destroy.
------------------------------------------------------------------------
Note: You didn't specify an "-out" parameter to save this plan, so Terraform
can't guarantee that exactly these actions will be performed if
"terraform apply" is subsequently run.
>> terraform apply
This will indeed create all the resource in AWS.
>> (Optional) terraform destroy
This command will destroy all artifacts that you have created in AWS.
You are now done.
You have now successfully deployed your simple infrastructure into
AWS cloud using Terraform.
Note: The state file
MUST be kept in a safe place, may be in github or s3 in case of AWS.
Takeout from this article:
So, let’s
summarize once more what have you learned from this article:
We see the benefits
of using Terraform while deploying the infrastructure.
We have learned how
to create the planning before deploying Terraform.
We now know how to
deploy infrastructure using Terraform.
Future scope:
We will add
versioning of the Terraform codes in github.
We will also see how
a infrastructure change request can be performed in a smooth way
using a single click.
Conclusion:
I hope that this
article will assist at least some of young Terraform users while deploying their IT
infrastructure into AWS. Please feel free to comment or provide your
feedback. Your feedback is extremely valuable to us. In case if you want to contact me, please use "Contact us" page.
Comments
Post a Comment