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
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.