Monthly Archives: July 2022

Creating AWS infrastructure with Terraform using Modules and Variables.

In previous example, we used a simple terraform code to spin up a ec2 instance in the AWS infrastructure. In that scenario, we provided VPC and Subnet ID information in the code. But lets assume that you want that to be taken care by terraform only. In this example, we will use Modules for 1) creating the VPC 2) creating the ssh key 3) creating the ec2 instances.

Also, we will use input and output variables to make the configuration more dynamic and flexible.

The terraform configuration in the parent project directory will include: main.tf, provider.tf, variables.tf and output.tf

Let’s dig into it more deeper.

  • Variables: Create variables.tf outputs.tf and provider.tf
  • variables.tf
variable "namespace" {
  description = "The project namespace to use for unique resource naming"
  default     = "terraform-test"
  type        = string
}

variable "region" {
  description = "AWS region"
  default     = "us-west-2"
  type        = string
}  
  • outputs.tf
output "public_connection_string" {
  description = "Copy/Paste/Enter - You are in the matrix"
  value       = "ssh -i ${module.ssh-key.key_name}.pem ec2-user@${module.ec2.public_ip}"
}

output "private_connection_string" {
  description = "Copy/Paste/Enter - You are in the private ec2 instance"
  value       = "ssh -i ${module.ssh-key.key_name}.pem ec2-user@${module.ec2.private_ip}"
}      
  • provider.tf
provider "aws" {
  region = var.region
}
  • Now, lets create a module directory/folder inside our main project and create networking folder. Inside this, we will create three configuration files: main, variables and outputs.
  • Networking module will create a custom VPC and define its name, Create an Internet Gateway and NAT gateway, Define CIDR block, Deploy two public and two private subnet across two AZs, Create two security group each for public and private access.

<<<<<<<<<<<<<<<<<<<<<<<<main.tf>>>>>>>>>>>>>>>>>>>>>>
// Break public and private into separate AZs
data "aws_availability_zones" "available" {}

module "vpc" {          ##This is the vpc module
  source = "terraform-aws-modules/vpc/aws"
  name                             = "${var.namespace}-vpc"
  cidr                             = "10.0.0.0/16"
  azs                              = data.aws_availability_zones.available.names
  private_subnets                  = ["10.0.1.0/24", "10.0.2.0/24"]
  public_subnets                   = ["10.0.101.0/24", "10.0.102.0/24"]
  #assign_generated_ipv6_cidr_block = true
  create_database_subnet_group     = true
  enable_nat_gateway               = true
  single_nat_gateway               = true
}

// Security Group to allow SSH connections from anywhere
resource "aws_security_group" "allow_ssh_pub" {
  name        = "${var.namespace}-allow_ssh"
  description = "Allow SSH inbound traffic"
  vpc_id      = module.vpc.vpc_id

  ingress {
    description = "SSH from the internet"
    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 = {
    Name = "${var.namespace}-allow_ssh_pub"
  }
}

// SG to onlly allow SSH connections from VPC public subnets
resource "aws_security_group" "allow_ssh_priv" {
  name        = "${var.namespace}-allow_ssh_priv"
  description = "Allow SSH inbound traffic"
  vpc_id      = module.vpc.vpc_id

  ingress {
    description = "SSH only from internal VPC clients"
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["10.0.0.0/16"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "${var.namespace}-allow_ssh_priv"
  }
}


<<<<<<<<<variables.tf>>>>>>>>>>>>>>>>>>>>
variable "namespace" {
  type = string
}

<<<<<<<<<output.tf>>>>>>>>>>>>>>>>>>>>>>>>>
output "vpc" {
  value = module.vpc
}

output "sg_pub_id" {
  value = aws_security_group.allow_ssh_pub.id
}

output "sg_priv_id" {
  value = aws_security_group.allow_ssh_priv.id
}
  • EC2 Module: Create a t2.micro instance in the public and private subnet. Also, copy the ssh key from your local system to the instance and apply necessary file permissions to it.
<<<<<<<<<<<<<<<<<<<<<<<<main.tf>>>>>>>>>>>>>>>>>>>>>>

// Create aws_ami filter to pick up the ami available in your region
data "aws_ami" "amazon-linux-2" {
  most_recent = true
  owners      = ["amazon"]

  filter {
    name   = "name"
    values = ["amzn2-ami-hvm*"]
  }
}

// Configure the EC2 instance in a public subnet
resource "aws_instance" "ec2_public" {
  ami                         = data.aws_ami.amazon-linux-2.id
  associate_public_ip_address = true
  instance_type               = "t2.micro"
  key_name                    = var.key_name
  subnet_id                   = var.vpc.public_subnets[0]
  vpc_security_group_ids      = [var.sg_pub_id]

  tags = {
    "Name" = "${var.namespace}-EC2-PUBLIC"
  }

  # Copies the ssh key file to home dir
  provisioner "file" {
    source      = "./${var.key_name}.pem"
    destination = "/home/ec2-user/${var.key_name}.pem"

    connection {
      type        = "ssh"
      user        = "ec2-user"
      private_key = file("${var.key_name}.pem")
      host        = self.public_ip
    }
  }
  
  //chmod key 400 on EC2 instance
  provisioner "remote-exec" {
    inline = ["chmod 400 ~/${var.key_name}.pem"]

    connection {
      type        = "ssh"
      user        = "ec2-user"
      private_key = file("${var.key_name}.pem")
      host        = self.public_ip
    }

  }

}

// Configure the EC2 instance in a private subnet
resource "aws_instance" "ec2_private" {
  ami                         = data.aws_ami.amazon-linux-2.id
  associate_public_ip_address = false
  instance_type               = "t2.micro"
  key_name                    = var.key_name
  subnet_id                   = var.vpc.private_subnets[1]
  vpc_security_group_ids      = [var.sg_priv_id]

  tags = {
    "Name" = "${var.namespace}-EC2-PRIVATE"
  }

}


<<<<<<<<<variables.tf>>>>>>>>>>>>>>>>>>>>
variable "namespace" {
  type = string
}

variable "vpc" {
  type = any
}

variable key_name {
  type = string
}

variable "sg_pub_id" {
  type = any
}

variable "sg_priv_id" {
  type = any
}

<<<<<<<<<output.tf>>>>>>>>>>>>>>>>>>>>>>>>>
output "public_ip" {
  value = aws_instance.ec2_public.public_ip
}

output "private_ip" {
  value = aws_instance.ec2_private.private_ip
}
  • SSH-KEY Module: Create a SSH-Key pair
<<<<<<<<<<<<<<<<<<<<<<<<main.tf>>>>>>>>>>>>>>>>>>>>>>

// Generate the SSH keypair that we’ll use to configure the EC2 instance. 
// After that, write the private key to a local file and upload the public key to AWS

resource "tls_private_key" "key" {
  algorithm = "RSA"
}

resource "local_file" "private_key" {
  filename          = "${var.namespace}-key.pem"
  sensitive_content = tls_private_key.key.private_key_pem
  file_permission   = "0400"
}

resource "aws_key_pair" "key_pair" {
  key_name   = "${var.namespace}-key"
  public_key = tls_private_key.key.public_key_openssh
}


<<<<<<<<<variables.tf>>>>>>>>>>>>>>>>>>>>
variable "namespace" {
  type = string
}


<<<<<<<<<output.tf>>>>>>>>>>>>>>>>>>>>>>>>>
output "ssh_keypair" {
  value = tls_private_key.key.private_key_pem
}


output "key_name" {
  value = aws_key_pair.key_pair.key_name

}

Terraform init: It will initialize the working directory and will install modules from github (make sure you have git installed)

terraform apply to provision VPC, EC2 and ssh-key pair in AWS

VPC
Two EC2 instances
Security Group

You can ssh to the ec2 instances using the above key-pair which we have received from the output variable.

So, this time we have created resources in our AWS infrastructure by adding modules, input and output variables in the terraform configuration.