The goal at the end of the project is to deploy AWS resources using Infrasture as a code, which is declarative, easier and scalable.
The diagram is the AWS end-to-end solution for 3 tier Application
Due to the capcity of my laptop, am using AWS Cloud9 environment for the development
-
Goto
https://learn.hashicorp.com/tutorials/terraform/install-cli -
Copy the following commands to install terraform on Ubuntu Linux distro
sudo apt-get update && sudo apt-get install -y gnupg software-properties-common curl
curl -fsSL https://apt.releases.hashicorp.com/gpg | sudo apt-key add -
sudo apt-add-repository "deb [arch=amd64] https://apt.releases.hashicorp.com $(lsb_release -cs) main"
sudo apt-get update && sudo apt-get install terraform
- I created an instance profile which assume a role in order for the Cloud9 environment to have access to AWS resources
- It has both the Trust policy and Permissiom policy
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"sts:AssumeRole"
],
"Principal": {
"Service": [
"ec2.amazonaws.com"
]
}
}
]
}
-
I attached the AWS Managed policy AdministratorAccess to the IAM role
-
Attach the IAM role to the instance for the Cloud9 environment
- First confirm the installed version of terraform
terraform --version
- Create the provider file provider.tf, main.tf, variabale.tf in the PBL directory
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
}
}
}
provider "aws" {
region = "us-west-2"
}
resource "aws_vpc" "project_16" {
cidr_block = "192.168.0.0/16"
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "dev"
}
}
resource "aws_subnet" "project_16_public_subnet1" {
vpc_id = aws_vpc.project_16.id
cidr_block = "192.168.10.0/24"
map_public_ip_on_launch = true
availability_zone = "us-west-2a"
tags = {
Name = "dev-public1"
}
}
resource "aws_subnet" "project_16_public_subnet2" {
vpc_id = aws_vpc.project_16.id
cidr_block = "192.168.20.0/24"
map_public_ip_on_launch = true
availability_zone = "us-west-2b"
tags = {
Name = "dev-public2"
}
}
- Initailizing terraform files in the PBL directory
cd PBL
terraform init
- Apply
terraform planon the command line
- Use
terraform applyto deploy the resources in the main.tf file
- Use
terraform showorterraform state listto display the aws resources that was deployed using terraform
- Usse
terraform destroyorterraform destroy -auto-approveto delete and clean ip AWS resources.
- Using variable.tf to indicate some reources in the main.tf file
variable "region" {
default = "us-west-2"
}
variable "vpc_cidr" {
default = "192.168.0.0/16"
}
variable "enable_dns_support" {
default = "true"
}
variable "enable_dns_hostnames" {
default = "true"
}
variable "enable_classiclink" {
default = "false"
}
variable "enable_classiclink_dns_support" {
default = "false"
}
- To improve the deployment of the subnets in the availability in the resource blocks using Loops & Data sources
- Get list of availability zones
data "aws_availability_zones" "available" {
state = "available"
}
- To make use of this new data resource, we will need to introduce a count argument in the subnet block
resource "aws_subnet" "public" {
count = 2
vpc_id = aws_vpc.main.id
cidr_block = "172.16.1.0/24"
map_public_ip_on_launch = true
availability_zone = data.aws_availability_zones.available.names[count.index]
}
A closer look at cidrsubnet – this function works like an algorithm to dynamically create a subnet CIDR per AZ. Regardless of the number of subnets created, it takes care of the cidr value per subnet.
- The prefix parameter must be given in CIDR notation, same as for VPC.
- The newbits parameter is the number of additional bits with which to extend the prefix. For example, if given a prefix ending with /16 and a newbits value of 4, the resulting subnet address will have length /20
- The netnum parameter is a whole number that can be represented as a binary integer with no more than newbits binary digits, which will be used to populate the additional bits added to the prefix
If we cannot hard code a value we want, then we will need a way to dynamically provide the value based on some input. Since the data resource returns all the AZs within a region, it makes sense to count the number of AZs returned and pass that number to the count argument.
To do this, we can introuduce length() function, which basically determines the length of a given list, map, or string.
Since data.aws_availability_zones.available.names returns a list like ["eu-central-1a", "eu-central-1b", "eu-central-1c"] we can pass it into a lenght function and get number of the AZs. length(["eu-central-1a", "eu-central-1b", "eu-central-1c"])
resource "aws_subnet" "public" {
count = length(data.aws_availability_zones.available.names)
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.vpc_cidr, 4 , count.index)
map_public_ip_on_launch = true
availability_zone = data.aws_availability_zones.available.names[count.index]
}
What we have now, is sufficient to create the subnet resource required. But if you observe, it is not satisfying our business requirement of just 2 subnets. The length function will return number 3 to the count argument, but what we actually need is 2.
- Declare a variable to store the desired number of public subnets, and set the default value
default = 2
}
- Next, update the count argument with a condition. Terraform needs to check first if there is a desired number of subnets. Otherwise, use the data returned by the lenght function.
resource "aws_subnet" "public" {
count = var.preferred_number_of_public_subnets == null ? length(data.aws_availability_zones.available.names) : var.preferred_number_of_public_subnets
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.vpc_cidr, 4 , count.index)
map_public_ip_on_launch = true
availability_zone = data.aws_availability_zones.available.names[count.index]
}
Now lets break it down:
- The first part var.preferred_number_of_public_subnets == null checks if the value of the variable is set to null or has some value defined.
- The second part ? and length(data.aws_availability_zones.available.names) means, if the first part is true, then use this. In other words, if preferred number of public subnets is null (Or not known) then set the value to the data returned by lenght function.
- The third part : and var.preferred_number_of_public_subnets means, if the first condition is false, i.e preferred number of public subnets is not null then set the value to whatever is definied in var.preferred_number_of_public_subnets
- This enables the use of variables files in the terraform configuration, terraform.tfvars has priority than variables.tf
variable "region" {
default = "us-west-2"
}
variable "vpc_cidr" {
default = "192.168.0.0/16"
}
variable "enable_dns_support" {
default = "true"
}
variable "enable_dns_hostnames" {
default = "true"
}
variable "enable_classiclink" {
default = "false"
}
variable "enable_classiclink_dns_support" {
default = "false"
}
variable "preferred_number_of_public_subnets" {
default = 2
}
region = "us-west-2"
vpc_cidr = "192.168.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
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
}
}
}
provider "aws" {
region = var.region
}
resource "aws_vpc" "project_16" {
cidr_block = var.vpc_cidr
enable_dns_hostnames = var.enable_dns_hostnames
enable_dns_support = var.enable_dns_support
enable_classiclink = var.enable_classiclink
enable_classiclink_dns_support = var.enable_classiclink_dns_support
tags = {
Name = "dev"
}
}
data "aws_availability_zones" "available" {
state = "available"
}
resource "aws_subnet" "project_16_public_subnet" {
count = var.preferred_number_of_public_subnets == null ? length(data.aws_availability_zones.available.names) : var.preferred_number_of_public_subnets
vpc_id = aws_vpc.project_16.id
cidr_block = cidrsubnet(var.vpc_cidr, 8, count.index)
map_public_ip_on_launch = true
availability_zone = data.aws_availability_zones.available.names[count.index]
tags = {
Name = "dev-public"
}
}


















