Terraform for AWS — Beginners
Terraform for AWS- Beginners is an attempt to quickly explain how to use Terraform for provisioning AWS basic resources for beginners. It is however recommend to have some understanding on AWS Services and some hands-on, try simple examples from my Blogs before trying out on Terraform code.
By definition Terraform is a tool to provision, maintain and manage infrastructure via Infrastructure as code (IaC). There are many other open source tools like Ansible, Chef, Puppet, etc which could nearly do more or less of what Terraform does.
Why is Terraform so popular?
Lets get started.
I have carried out this tutorial on windows-power shell.
By default when you download Terraform, it is a zip file format for any OS.
Terraform Download
At the time writing , terraform version downloaded — v0.12.18.
Depending upon OS-Machine download the executable and place in a location. If you are using windows then configure the environment path as below.
Terraform Environment Variable Setting
Once you have placed and set the environment path, the first command you can run and confirm is terraform version.
Terraform version
You can also refer here for installing Terrafom from hashicorp for different OS’s.
Before getting into building the AWS infrastructure, we will discuss on some of Terraform Basic commands,
In this post you will be provisioning below services @ AWS using Terraform,
Below is a simple representation of the same.
Terraform on AWS
All Terraform file/code should have extension .tf.
Any .tfvars file extension by convention should have keys or passwords or secrets and this should not be checked-in / pushed to source control.
Make sure all your files are placed under same directory/folder for terraform to pick and process.
Have your AWS user’s Access Key and Secret Access key downloaded and stored locally. Create a new file secret.tfvars and assign them to terraform variable as below. Note this file should not be uploaded to github so Add it to .gitignore.
AWS_ACCESS_KEY = “XXXXXXXXXXXXXXXXXXXX”
AWS_SECRET_KEY = “ZZZZZZZZZZZZZZZZZZZZ/XXXXXXXXX”
Alternatively you can export the access key and secret key before running the terraform command as below,
export AWS_ACCESS_KEY_ID= XXXXX
export AWS_SECRET_ACCESS_KEY= XXXXX
The code can start by first identifying the Cloud Provider, in your case it is AWS. You can have this coded in a separate file, called as provider.tf or you can have it in your main.tf. In this example most of the code is written inside main.tf as a convention. Entire code set can be found from my github repository.
Git repositoy : Source Code
#Version of the terraform terraform { required_version = “>= 0.12” } #Define the cloud provider here provider “aws” { region = var.AWS_REGION }
#Version of the terraform
terraform {
required_version = “>= 0.12”
}
#Define the cloud provider here
provider “aws” {
region = var.AWS_REGION
}
Note here var.AWS_REGION is variable stored in a separate file called vars.tf. By convention most of the variable with values or default can be coded under vars.tf. The value of region is coded as below inside vars.tf
variable “AWS_REGION” {
default = “us-east-1”
}
Here you are specifying the provider as ‘AWS‘ and setting the default region to ‘us-east-1‘.
Once you have coded above, you are good to run your first command on terraform which is terraform init.
This is the first command that should be run. This will initialize values of cloud provider, in your case AWS and download the required code to interact between Terraform code and AWS Api’s. The required files will be downloaded and placed under a folder by name ‘.terraform’.
During anytime of your coding, you can issue another terraform command called ‘terraform plan’ to see what your code is going to provision.
./terraform plan
This will show the entire list of resource and number resource that will be created or changed or destroyed.
Create a VPC with ip range of “10.0.0.0/16”. When you create VPC under terraform every single service you need to manually code it, nothing will be defaulted.
# Define VPC resource “aws_vpc” “main-terraform-vpc” { cidr_block = “${var.VPC_CIDR}” instance_tenancy = “default” enable_dns_support = “true” enable_dns_hostnames = “true” enable_classiclink = “false” tags = { Name = “main-terraform-vpc” } }
# Define VPC
resource “aws_vpc” “main-terraform-vpc” {
cidr_block = “${var.VPC_CIDR}”
instance_tenancy = “default”
enable_dns_support = “true”
enable_dns_hostnames = “true”
enable_classiclink = “false”
tags = {
Name = “main-terraform-vpc”
}
}
Above code creates VPC with name ‘main-terrraform-vpc’ with IP from var.VPC_CIDR written inside vars.tf file as below,
variable “VPC_CIDR” {
default = “10.0.0.0/16”
}
# Subnets
resource “aws_subnet” “main-public-1” {
vpc_id = aws_vpc.main-terrraform-vpc.id
cidr_block = “${var.PUB_CIDR}”
map_public_ip_on_launch = “true”
availability_zone = “us-east-1a”
tags = {
Name = “main-public-1”
}
}
resource “aws_subnet” “main-private-1” {
vpc_id = aws_vpc.main-terrraform-vpc.id
cidr_block = var.PVT_CIDR
map_public_ip_on_launch = “false”
availability_zone = “us-east-1a”
tags = {
Name = “main-private-1”
}
}
Subnets IP address will be referred from the vars.tf file as below,
variable “PUB_CIDR” {
default = “10.0.1.0/24”
}
variable “PVT_CIDR” {
default = “10.0.4.0/24”
}
Create an internet gateway and attach to the above VPC.
# Internet GW
resource “aws_internet_gateway” “main-igw” {
vpc_id = aws_vpc.main-terrraform-vpc.id
tags = {
Name = “main-igw”
}
}
Here the VPC id will be automatically derived from aws_vpc.main-terrraform-vpc.id at run time or during the actual creation of services.
Create the route tables and attach it with the above internet gateway and VPC.
# route tables
resource “aws_route_table” “main-public” {
vpc_id = aws_vpc.main-terrraform-vpc.id
route {
cidr_block = “0.0.0.0/0”
gateway_id = aws_internet_gateway.main-igw.id
}
tags = {
Name = “main-public-1”
}
}
Now this Route table need ot be associated to Public subnet as below,
# route associations public
resource “aws_route_table_association” “main-public-1-a” {
subnet_id = aws_subnet.main-public-1.id
route_table_id = aws_route_table.main-public.id
}
Create two security groups;
1. Public Security group for application server.
2. Private security group for RDS.
You can have a seprate file called securitygroup.tf or write the entire security group code inside main.tf. I have used a separate file called securitygroup.tf .
Public Security Group code as below,
Code:
resource “aws_security_group” “public-sg” { vpc_id = aws_vpc.main-terrraform-vpc.id name = “public-sg” description = “security group that allows ssh and all egress traffic” egress { from_port = 0 to_port = 0 protocol = “-1” cidr_blocks = [“0.0.0.0/0”] } ingress { from_port = 22 to_port = 22 protocol = “tcp” cidr_blocks = [“0.0.0.0/0”] } tags = { Name = “public-sg” } }
resource “aws_security_group” “public-sg” {
vpc_id = aws_vpc.main-terrraform-vpc.id
name = “public-sg”
description = “security group that allows ssh and all egress traffic”
egress {
from_port = 0
to_port = 0
protocol = “-1”
cidr_blocks = [“0.0.0.0/0”]
}
ingress {
from_port = 22
to_port = 22
protocol = “tcp”
cidr_blocks = [“0.0.0.0/0”]
}
tags = {
Name = “public-sg”
}
}
Below is the code for Private security group,
resource “aws_security_group” “rds-sg” {
vpc_id = aws_vpc.main-terrraform-vpc.id
name = “rds-sg”
description = “security group that allows RDS Secure access”
egress {
from_port = 0
to_port = 0
protocol = “-1”
cidr_blocks = [“0.0.0.0/0”]
}
ingress {
from_port = 3306
to_port = 3306
protocol = “tcp”
# cidr_blocks = [“0.0.0.0/0”] incase if you struggle to access RDS, remove below line and uncomment this line.
security_groups = [aws_security_group.public-sg.id] # allowing access from our example instance
}
tags = {
Name = “my-rds-sg”
}
}
Now we have all in set to create the instances.
Before creating instance, create a public key to launch the ec2 instance as below,
Run the below script to generate locally,
ssh-keygen
sshkey gen
Before creating EC2 instance, map the above generated public key in the EC2 creation code. Below code is added inside main.tf
#AWS Keypair to connect to aws ec2
resource “aws_key_pair” “mykeypair” {
key_name = “mykeypair”
public_key = file(var.PATH_TO_PUBLIC_KEY)
}
Here ‘file’ code will look for the mentioned file name in the specified directory if not current directory the retrieve the value of it.
var.PATH_TO_PUBLIC_KEY is assigned inside vars.tf file with the above generated ssh public key.
variable “PATH_TO_PUBLIC_KEY” {
default = “mykey.pub”
}
Now you can create the EC2 instance with above created vpc , public subnet , public security group and the keypair.
Create a new file called instance.tf for provisioning ec2 and RDS resources.
resource “aws_instance” “terr-ec2-instance” {
ami = var.AMIS[var.AWS_REGION]
instance_type = “t2.micro”
# the VPC subnet to be attached
subnet_id = aws_subnet.main-public-1.id
# the security group to be attached
vpc_security_group_ids = [aws_security_group.public-sg.id]
# the public SSH key ssh to access the server
key_name = aws_key_pair.mykeypair.key_name
}
Create the Mysql RDS instance as below,
Note: password is hardcode here, just for ease. You can also store in a separate tfvars file and refer the variable here.
resource “aws_db_instance” “my-test-sql” {
instance_class = var.DB_INSTANCE_CLASS
engine = “mysql”
engine_version = “5.7”
multi_az = “false”
storage_type = “gp2”
allocated_storage = 20
name = “mytestrds”
username = “admin”
password = “admin123”
apply_immediately = “true”
backup_retention_period = 10
backup_window = “09:46–10:16”
# db_subnet_group_name = “${aws_db_subnet_group.my-rds-db-subnet.name}”
# vpc_security_group_ids = [aws_security_group.rds-sg.id]
skip_final_snapshot = true
}
By now you should have coded all that required to setup an vpc and to launch EC2 & RDS. You should have the following terraform source code files,
Now you are ready to run the terraform scripts.
Terraform init should be run first, which will initialize the values of cloud provider, in your case AWS and dowload the required code to interact between Terraform and AWS Api’s. The required files will be placed under a directory called as ‘.terraform’
Terraform init
Run Terraform Plan to see list of actions that would be performed by your code.
terraform plan 1
Terraform plan 2
By end of Terraform plan you can see the list of resources to added (+) , number of resource to be destroyed (-) and number of resources to be updated. In your case it will be ’13 to add’ and .
You can run ‘terraform graph’ to see the order of resource creation.
terraform graph
You copy paste this terraform graph output in “http://dreampuf.github.io/GraphvizOnline/” to see in pictorial representation.
Terraform Graph
Once you have reviewed your plan, now you can go ahead and provision the infrastructure by running ‘terraform apply’ command. Since we are provisioning RDS, it might take more than 10 minutes to provision the entire infrastructure. Once terraform apply command is run without any errors, you can find a new file created by name ‘terraform.tfstate’ which will hold the state of each resource which got created. This should be saved forever properly till the resources are destroyed and try not to manipulate this file. Then on everytime we run plan or apply, this file will be referenced to compare against the already provisioned resources.
Terraform Apply
Now log into AWS console and check for the resoruces.
VPC:
Terraform AWS VPC
Subnets
Terraform AWS Subnets
Terraform AWS Route Table
Terraform AWS Ig
Terraform AWS Security Group
Terraform AWS EC2
Terraform AWS RDS
Now that you have verified all your resource from AWS console. We will make a small change to the Terraform code and see how it works.
Change is to add a ‘Tag’ name to your EC2 instance. Remember there was no Tag name attached to your EC2 instance.
Below is the code for the same,
resource “aws_instance” “terr-ec2-instance” { ami = var.AMIS[var.AWS_REGION] instance_type = “t2.micro” # the VPC subnet to be attached subnet_id = aws_subnet.main-public-1.id # the security group to be attached vpc_security_group_ids = [aws_security_group.public-sg.id] # the public SSH key ssh to access the server key_name = aws_key_pair.mykeypair.key_name tags = { Name = “HelloTerraform” } }
resource “aws_instance” “terr-ec2-instance” {
ami = var.AMIS[var.AWS_REGION]
instance_type = “t2.micro”
# the VPC subnet to be attached
subnet_id = aws_subnet.main-public-1.id
# the security group to be attached
vpc_security_group_ids = [aws_security_group.public-sg.id]
# the public SSH key ssh to access the server
key_name = aws_key_pair.mykeypair.key_name
tags = {
Name = “HelloTerraform”
}
}
Run Terraform plan, it will clearly say ‘1 to change’ as below,
Terraform Plan Change
Now Run Terraform apply,
Terraform AWS EC2 change
It is a good idea to clean up the infrastructure as provisioned by Terraform using Terraform destroy command. It is very simple and easy to clean up using script as the state of each resource is stored inside terraform.tfstate file .
Terraform AWS destroy
Once destoryed, it is not possible to reverse the action.
By now you should have got a basic idea about Terraform in creating AWS service. There is a lot of which you have implicitly learnt here like terraform variables. You have mostly used here complex data types like list and map. I planning to write a separate post on terraform variables.
Few Terraform Advance Topics which i will also try to cover in my next post on ‘Terraform on AWS for Beginners’ like,