Build Ec2 VPC 1734880913
Build Ec2 VPC 1734880913
Step-01: Introduction
• terraform-aws-modules/vpc/aws
• terraform-aws-modules/security-group/aws
• terraform-aws-modules/ec2-instance/aws
• aws_eip
• null_resource
• file provisioner
• remote-exec provisioner
• local-exec provisioner
• depends_on Meta-Argument
• Create VPC with 3-Tier Architecture (Web, App and DB) - Leverage code from previous
section
• Create AWS Security Group Terraform Module and define HTTP port 80, 22 inbound rule for
entire internet access 0.0.0.0/0
o File Provisioner
o Remote-exec Provisioner
o Local-exec Provisioner
Pre-requisite
https://www.linkedin.com/in/azharsayyed1/
• Copy the following TF Config files from 06-02 section which will create a 3-Tier VPC
• c1-versions.tf
• # Terraform Block
• terraform {
• required_version = ">= 1.6" # which means any version equal & above
0.14 like 0.15, 0.16 etc and < 1.xx
• required_providers {
• aws = {
• source = "hashicorp/aws"
• version = ">= 5.0"
• }
•
• null = {
• source = "hashicorp/null"
• version = "~> 3.0"
• }
•
• }
• }
•
• # Provider Block
• provider "aws" {
• region = var.aws_region
• profile = "default"
• }
• /*
• Note-1: AWS Credentials Profile (profile = "default") configured on
your local desktop terminal
• $HOME/.aws/credentials
• */
• c2-generic-variables.tf
• # Input Variables
• # AWS Region
• variable "aws_region" {
• description = "Region in which AWS Resources to be created"
• type = string
• default = "us-east-1"
• }
• # Environment Variable
• variable "environment" {
• description = "Environment Variable used as a prefix"
• type = string
• default = "dev"
• }
https://www.linkedin.com/in/azharsayyed1/
• # Business Division
• variable "business_divsion" {
• description = "Business Division in the large organization this
Infrastructure belongs"
• type = string
• default = "SAP"
• }
• c3-local-values.tf
c4-01-vpc-variables.tf
# VPC Name
variable "vpc_name" {
type = string
default = "myvpc"
variable "vpc_cidr_block" {
type = string
default = "10.0.0.0/16"
https://www.linkedin.com/in/azharsayyed1/
# VPC Availability Zones
variable "vpc_availability_zones" {
type = list(string)
variable "vpc_public_subnets" {
type = list(string)
variable "vpc_private_subnets" {
type = list(string)
variable "vpc_database_subnets" {
type = list(string)
variable "vpc_create_database_subnet_group" {
https://www.linkedin.com/in/azharsayyed1/
description = "VPC Create Database Subnet Group"
type = bool
default = true
variable "vpc_create_database_subnet_route_table" {
type = bool
default = true
variable "vpc_enable_nat_gateway" {
type = bool
default = true
variable "vpc_single_nat_gateway" {
description = "Enable only single NAT Gateway in one Availability Zone to save costs during
our demos"
type = bool
default = true
https://www.linkedin.com/in/azharsayyed1/
• c4-02-vpc-module.tf
•
• # Create VPC Terraform Module
• module "vpc" {
• source = "terraform-aws-modules/vpc/aws"
• #version = "2.78.0"
• #version = "~> 2.78"
• version = "5.4.0"
•
• # VPC Basic Details
• name = "${local.name}-${var.vpc_name}"
• cidr = var.vpc_cidr_block
• azs = var.vpc_availability_zones
• public_subnets = var.vpc_public_subnets
• private_subnets = var.vpc_private_subnets
•
• # Database Subnets
• database_subnets = var.vpc_database_subnets
• create_database_subnet_group = var.vpc_create_database_subnet_group
• create_database_subnet_route_table =
var.vpc_create_database_subnet_route_table
• # create_database_internet_gateway_route = true
• # create_database_nat_gateway_route = true
•
• # NAT Gateways - Outbound Communication
• enable_nat_gateway = var.vpc_enable_nat_gateway
• single_nat_gateway = var.vpc_single_nat_gateway
•
• # VPC DNS Parameters
• enable_dns_hostnames = true
• enable_dns_support = true
•
• tags = local.common_tags
• vpc_tags = local.common_tags
•
• # Additional Tags to Subnets
• public_subnet_tags = {
• Type = "Public Subnets"
• }
• private_subnet_tags = {
• Type = "Private Subnets"
• }
• database_subnet_tags = {
• Type = "Private Database Subnets"
• }
• }
https://www.linkedin.com/in/azharsayyed1/
c4-03-vpc-outputs.tf
# VPC ID
output "vpc_id" {
value = module.vpc.vpc_id
output "vpc_cidr_block" {
value = module.vpc.vpc_cidr_block
output "private_subnets" {
value = module.vpc.private_subnets
output "public_subnets" {
value = module.vpc.public_subnets
output "nat_public_ips" {
description = "List of public Elastic IPs created for AWS NAT Gateway"
value = module.vpc.nat_public_ips
https://www.linkedin.com/in/azharsayyed1/
}
# VPC AZs
output "azs" {
value = module.vpc.azs
# VPC ID
output "vpc_id" {
value = module.vpc.vpc_id
output "vpc_cidr_block" {
value = module.vpc.vpc_cidr_block
output "private_subnets" {
value = module.vpc.private_subnets
output "public_subnets" {
https://www.linkedin.com/in/azharsayyed1/
description = "List of IDs of public subnets"
value = module.vpc.public_subnets
output "nat_public_ips" {
description = "List of public Elastic IPs created for AWS NAT Gateway"
value = module.vpc.nat_public_ips
# VPC AZs
output "azs" {
value = module.vpc.azs
terraform.tfvars
# Generic Variables
aws_region = "us-east-1"
environment = "stag"
business_divsion = "HR"
• vpc.auto.tfvars
• # VPC Variables
• vpc_name = "myvpc"
• vpc_cidr_block = "10.0.0.0/16"
• vpc_availability_zones = ["us-east-1a", "us-east-1b"]
• vpc_public_subnets = ["10.0.101.0/24", "10.0.102.0/24"]
• vpc_private_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
• vpc_database_subnets= ["10.0.151.0/24", "10.0.152.0/24"]
• vpc_create_database_subnet_group = true
• vpc_create_database_subnet_route_table = true
https://www.linkedin.com/in/azharsayyed1/
• vpc_enable_nat_gateway = true
• vpc_single_nat_gateway = true
• private-key/terraform-key.pem
#! /bin/bash
Step-04: Create Security Groups for Bastion Host and Private Subnet Hosts
Step-04-01: c5-01-securitygroup-variables.tf
• Place holder file for defining any Input Variables for EC2 Security Groups
Step-04-02: c5-03-securitygroup-bastionsg.tf
module "public_bastion_sg" {
source = "terraform-aws-modules/security-group/aws"
version = "3.18.0"
name = "public-bastion-sg"
https://www.linkedin.com/in/azharsayyed1/
description = "Security group with SSH port open for everybody (IPv4 CIDR), egress ports are all
world open"
vpc_id = module.vpc.vpc_id
ingress_rules = ["ssh-tcp"]
ingress_cidr_blocks = ["0.0.0.0/0"]
egress_rules = ["all-all"]
tags = local.common_tags
Step-04-03: c5-04-securitygroup-privatesg.tf
module "private_sg" {
source = "terraform-aws-modules/security-group/aws"
version = "3.18.0"
name = "private-sg"
description = "Security group with HTTP & SSH ports open for everybody (IPv4 CIDR), egress ports
are all world open"
vpc_id = module.vpc.vpc_id
ingress_cidr_blocks = ["0.0.0.0/0"]
egress_rules = ["all-all"]
tags = local.common_tags
Step-04-04: c5-02-securitygroup-outputs.tf
output "public_bastion_sg_group_id" {
value = module.public_bastion_sg.this_security_group_id
https://www.linkedin.com/in/azharsayyed1/
}
output "public_bastion_sg_group_vpc_id" {
value = module.public_bastion_sg.this_security_group_vpc_id
output "public_bastion_sg_group_name" {
value = module.public_bastion_sg.this_security_group_name
output "private_sg_group_id" {
value = module.private_sg.this_security_group_id
output "private_sg_group_vpc_id" {
value = module.private_sg.this_security_group_vpc_id
output "private_sg_group_name" {
value = module.private_sg.this_security_group_name
Step-05: c6-01-datasource-ami.tf
most_recent = true
owners = [ "amazon" ]
filter {
name = "name"
https://www.linkedin.com/in/azharsayyed1/
values = [ "amzn2-ami-hvm-*-gp2" ]
filter {
name = "root-device-type"
values = [ "ebs" ]
filter {
name = "virtualization-type"
values = [ "hvm" ]
filter {
name = "architecture"
values = [ "x86_64" ]
Step-06-01: c7-01-ec2instance-variables.tf
variable "instance_type" {
type = string
default = "t3.micro"
variable "instance_keypair" {
description = "AWS EC2 Key pair that need to be associated with EC2 Instance"
type = string
default = "terraform-key"
Step-06-02: c7-03-ec2instance-bastion.tf
https://www.linkedin.com/in/azharsayyed1/
# AWS EC2 Instance Terraform Module
# Bastion Host - EC2 Instance that will be created in VPC Public Subnet
module "ec2_public" {
source = "terraform-aws-modules/ec2-instance/aws"
version = "2.17.0"
name = "${var.environment}-BastionHost"
ami = data.aws_ami.amzlinux2.id
instance_type = var.instance_type
key_name = var.instance_keypair
subnet_id = module.vpc.public_subnets[0]
vpc_security_group_ids = [module.public_bastion_sg.this_security_group_id]
tags = local.common_tags
Step-06-03: c7-04-ec2instance-private.tf
module "ec2_private" {
source = "terraform-aws-modules/ec2-instance/aws"
version = "2.17.0"
name = "${var.environment}-vm"
ami = data.aws_ami.amzlinux2.id
instance_type = var.instance_type
user_data = file("${path.module}/apache-install.sh")
key_name = var.instance_keypair
vpc_security_group_ids = [module.private_sg.this_security_group_id]
instance_count = 3
subnet_ids = [
module.vpc.private_subnets[0],
module.vpc.private_subnets[1],
https://www.linkedin.com/in/azharsayyed1/
]
tags = local.common_tags
Step-06-04: c7-02-ec2instance-outputs.tf
output "ec2_bastion_public_instance_ids" {
value = module.ec2_public.id
output "ec2_bastion_public_ip" {
value = module.ec2_public.public_ip
output "ec2_private_instance_ids" {
value = module.ec2_private.id
output "ec2_private_ip" {
value = module.ec2_private.private_ip
depends_on = [module.ec2_public]
instance = module.ec2_public.id[0]
vpc = true
https://www.linkedin.com/in/azharsayyed1/
tags = local.common_tags
Step-08: c9-nullresource-provisioners.tf
null = {
source = "hashicorp/null"
depends_on = [module.ec2_public ]
connection {
type = "ssh"
host = aws_eip.bastion_eip.public_ip
user = "ec2-user"
password = ""
private_key = file("private-key/terraform-key.pem")
provisioner "file" {
source = "private-key/terraform-key.pem"
destination = "/tmp/terraform-key.pem"
https://www.linkedin.com/in/azharsayyed1/
}
# Using remote-exec provisioner fix the private key permissions on Bastion Host
provisioner "remote-exec" {
inline = [
provisioner "local-exec" {
command = "echo VPC created on `date` and VPC ID: ${module.vpc.vpc_id} >> creation-time-vpc-
id.txt"
working_dir = "local-exec-output-files/"
#on_failure = continue
provisioner "local-exec" {
working_dir = "local-exec-output-files/"
when = destroy
#on_failure = continue
Step-09: ec2instance.auto.tfvars
instance_type = "t3.micro"
instance_keypair = "terraform-key"
Step-10-01: c7-04-ec2instance-private.tf
• We have put depends_on so that EC2 Private Instances will not get created until all the
resources of VPC module are created
https://www.linkedin.com/in/azharsayyed1/
• why?
• VPC NAT Gateway should be created before EC2 Instances in private subnets because these
private instances has a userdata which will try to go outbound to download
the HTTPD package using YUM to install the webserver
• If Private EC2 Instances gets created first before VPC NAT Gateway provisioning of webserver
in these EC2 Instances will fail.
depends_on = [module.vpc]
Step-10-02: c8-elasticip.tf
• This elastic ip resource will explicitly wait for till the bastion EC2
instance module.ec2_public is created.
• This elastic ip resource will wait till all the VPC resources are created primarily the Internet
Gateway IGW.
Step-10-03: c9-nullresource-provisioners.tf
• This Null resource contains a file provisioner which will copy the private-key/terraform-
key.pem to Bastion Host ec2_public module created ec2 instance.
• So we added explicit dependency in terraform to have this null_resource wait till respective
EC2 instance is ready so file provisioner can copy the private-key/terraform-key.pem file
depends_on = [module.ec2_public ]
# Terraform Initialize
terraform init
# Terraform Validate
terraform validate
# Terraform Plan
terraform plan
Observation:
https://www.linkedin.com/in/azharsayyed1/
3) Review all other resources (vpc, elasticip)
# Terraform Apply
Observation:
1) VERY IMPORTANT: Primarily observe that first VPC NAT Gateway will be created and after that only
module.ec2_private related EC2 Instance will be created
curl http://<Private-Instance-1-Private-IP>
curl http://<Private-Instance-2-Private-IP>
cd /var/www/html
ls -lrta
Observation:
curl http://169.254.169.254/latest/user-data
https://www.linkedin.com/in/azharsayyed1/
cd /var/log
more cloud-init-output.log
Observation:
2) This file (cloud-init-output.log) will show you if your httpd package got installed and all your
userdata commands executed successfully or not
Step-13: Clean-Up
# Terraform Destroy
# Clean-Up
rm -rf .terraform*
rm -rf terraform.tfstate*
https://www.linkedin.com/in/azharsayyed1/