Giter Club home page Giter Club logo

reuse-configuration-using-terraform-modules's Introduction

Reuse-Configuration-Using-Terraform-Modules

Build Status

A terraform project to automate the creation of a custom VPC infrastructure from scratch using Terraform modules.

The code is built using the following versions:

Provider Terraform
terraform-provider-aws_v4.48.0_x5 Terraform v1.3.6

Requirements

  • An IAM user with programmatic access with AmazonEC2FullAccess & AmazonRoute53FullAccess permissions attached.

Why do you use modules?

You might've created the infrastructure for all of your projects using the same code each time. If you decide to make it a module, this can be avoided.

The module files need not have to be saved locally; they can instead be stored in github or even on AmazonS3. The module can be fetched from these sources using Terraform.

In this project I have configured terraform to read the modules from my modules repository: AWS-VPC-Modules.

Use the following command to clone the repository

git clone https://github.com/sreehariskumar/Reuse-Configuration-Using-Terraform-Modules

Let's get started

First, we will look into the contents of module files.

  1. AWS-VPC-Modules/variables.tf
locals {
  subnets = length(data.aws_availability_zones.available.names)
}
variable "project" {
  default = "test"
}
variable "environment" {}
variable "vpc_cidr" {}
variable "enable_nat_gateway" {
  type    = bool
  default = true
}
  • We've created a local variable to create subnets depending on the number of availability in a particular region.
  • The environment variable and the vpc_cidr block variable are configured to fetch input from the terraform variable file.
  • The enable_nat_gateway variable is defined as a boolean function which accepts only true/false responses. We will look into this during configuration.
  1. AWS-VPC-Modules/datasourcce.tf
data "aws_availability_zones" "available" {
  state = "available"
}
  • The datasource is defined to fetch the list of availability zones that are available in the particular region.
  1. AWS-VPC-Modules/output.tf
output "vpc_id" {
  value = aws_vpc.vpc.id
}

output "public_subnets" {
  value = aws_subnet.public[*].id
}

output "private_subnets" {
  value = aws_subnet.private[*].id
}
  • Output values are defined to print the value of defined resource which can be passed as input to other resources.
  1. AWS-VPC-Modules/main.tf
resource "aws_vpc" "vpc" {
  cidr_block           = var.vpc_cidr
  instance_tenancy     = "default"
  enable_dns_support   = true
  enable_dns_hostnames = true
  tags = {
    Name = "${var.project}-${var.environment}"
  }
}

resource "aws_internet_gateway" "igw" {
  vpc_id = aws_vpc.vpc.id
  tags = {
    Name = "${var.project}-${var.environment}"
  }
}

resource "aws_subnet" "public" {
  count                   = local.subnets
  vpc_id                  = aws_vpc.vpc.id
  cidr_block              = cidrsubnet(var.vpc_cidr, 4, count.index)
  availability_zone       = data.aws_availability_zones.available.names[count.index]
  map_public_ip_on_launch = true
  tags = {
    Name = "${var.project}-${var.environment}-public${count.index + 1}"
  }
}

resource "aws_subnet" "private" {
  count                   = local.subnets
  vpc_id                  = aws_vpc.vpc.id
  cidr_block              = cidrsubnet(var.vpc_cidr, 4, "${local.subnets + count.index}")
  availability_zone       = data.aws_availability_zones.available.names[count.index]
  map_public_ip_on_launch = false
  tags = {
    Name = "${var.project}-${var.environment}-private${count.index + 1}"
  }
}

resource "aws_eip" "nat" {
  count = var.enable_nat_gateway ? 1 : 0
  vpc   = true
  tags = {
    Name = "${var.project}-${var.environment}-natgw"
  }
}

resource "aws_nat_gateway" "nat" {
  count         = var.enable_nat_gateway ? 1 : 0
  allocation_id = aws_eip.nat.0.id
  subnet_id     = aws_subnet.public[0].id
  tags = {
    Name = "${var.project}-${var.environment}"
  }
  depends_on = [aws_internet_gateway.igw]
}

resource "aws_route_table" "public" {
  vpc_id = aws_vpc.vpc.id
  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.igw.id
  }
  tags = {
    Name = "${var.project}-${var.environment}-public"
  }
}

resource "aws_route_table" "private" {
  vpc_id = aws_vpc.vpc.id
  tags = {
    Name = "${var.project}-${var.environment}-private"
  }
}

resource "aws_route" "enable_nat" {
  count                  = var.enable_nat_gateway ? 1 : 0
  route_table_id         = aws_route_table.private.id
  destination_cidr_block = "0.0.0.0/0"
  nat_gateway_id         = aws_nat_gateway.nat.0.id
  depends_on             = [aws_route_table.private]
}

resource "aws_route_table_association" "public" {
  count          = local.subnets
  subnet_id      = aws_subnet.public[count.index].id
  route_table_id = aws_route_table.public.id
}

resource "aws_route_table_association" "private" {
  count          = local.subnets
  subnet_id      = aws_subnet.private[count.index].id
  route_table_id = aws_route_table.private.id
}
  • Create a VPC with public subnets and private subnet one for each availablility zone in the region
  • Create an Internet Gateway for public access
  • Creation of Elastic IP & NAT Gateway
  • Adding public route for public access through Internet Gateway
  • Adding private route for public access thought NAT Gateway
  • Associate public route tables with public subnets
  • Associate private route tables with private subnets

Creation of Public & Private Subnets

resource "aws_subnet" "public" {
  count                   = local.subnets
  vpc_id                  = aws_vpc.vpc.id
  cidr_block              = cidrsubnet(var.vpc_cidr, 4, count.index)
  availability_zone       = data.aws_availability_zones.available.names[count.index]
  map_public_ip_on_launch = true
  tags = {
    Name = "${var.project}-${var.environment}-public${count.index + 1}"
  }
}
  • The meta argument count will create one public subnets for each availability zone. This is achieved by meta argument combining with the locals variable we've defined earlier in the variables.tf file.

  • The subnetting is calculated automatically with cidrsubnet() function.

  • Identification of each subnet block is achieved using the count.index block

  • The public subnets will be mapped with public ip using the map_public_ip_on_launch argument.

  • Similarly, the private subnets are created.

  • Public ip addresses will not be available for private subnets.

resource "aws_eip" "nat" {
  count = var.enable_nat_gateway ? 1 : 0
  vpc   = true
  tags = {
    Name = "${var.project}-${var.environment}-natgw"
  }
}
  • Understanding this part is tricky.
  • Here, you could see that the variable enable_nat_gateway is defined as a IF condition.
  • We may need to consider another file from the root directory: prod.tfvars
cidr_vpc      = "172.16.0.0/16"  
instance_type = "t2.micro"  
environment   = "prod"  
instance_ami  = "ami-0cca134ec43cf708f"  
enable_nat_gateway = true
  • A tfvars file is used to create multiple environments for the same project by defining different values for the variables.
  • We could see that the enable_nat_gateway argument is defined as true
  • Also consider the main.tf file from the root directory:
module "vpc" {
  source      = "github.com/sreehariskumar/AWS-VPC-Modules"
  project     = var.project
  environment = var.environment
  vpc_cidr    = var.vpc_cidr
  enable_nat_gateway = var.enable_nat_gateway 
}
  • Here we could see that the enable_nat_gateway argument depends on the variable var.enable_nat_gateway
  • If the enable_nat_gateway argument is defined true, then:

Count meta argument will be = 1 if var.enable_nat_gateway equal to “true” else >count =0. Moreover, count=1 means resource.aws_eip will run 1 time and one elastic ip will be created.

  • If the enable_nat_gateway argument is defined true, then: elastic IP will not be created.

The same concept is used for the creation of NAT Gateway creation.

resource "aws_nat_gateway" "nat" {
  count         = var.enable_nat_gateway ? 1 : 0
  allocation_id = aws_eip.nat.0.id
  subnet_id     = aws_subnet.public[0].id
  tags = {
    Name = "${var.project}-${var.environment}"
  }
  depends_on = [aws_internet_gateway.igw]
}
  • The aws_nat_gateway resource is executed only if the variable *enable_nat_gateway is set to true, which will create the NAT gateway.
resource "aws_route" "enable_nat" {
  count                  = var.enable_nat_gateway ? 1 : 0
  route_table_id         = aws_route_table.private.id
  destination_cidr_block = "0.0.0.0/0"
  nat_gateway_id         = aws_nat_gateway.nat.0.id
  depends_on             = [aws_route_table.private]
}
  • You could see that the above resource is configured to run only if the variable enable_nat_gateway is set to true.
  • To provide public access to the private subnet, a route entry should be added for traffic to pass through the NAT gateway.

Now, we will look into the contents of project files.

module "vpc" {
  source             = "github.com/sreehariskumar/AWS-VPC-Modules"
  project            = var.project
  environment        = var.environment
  vpc_cidr           = var.vpc_cidr
  enable_nat_gateway = var.enable_nat_gateway
}


resource "aws_ec2_managed_prefix_list" "prefix_list" {
  name           = "${var.project}-${var.environment}-prefixlist"
  address_family = "IPv4"
  max_entries    = length(var.public_ips)
  dynamic "entry" {
    for_each = var.public_ips
    iterator = ip
    content {
      cidr = ip.value
    }
  }

  tags = {
    Name = "${var.project}-${var.environment}-prefixlist"
  }
}



resource "aws_security_group" "bastion" {
  name_prefix = "${var.project}-${var.environment}-bastion-"
  description = "Allow 22 from prefixlist"
  vpc_id      = module.vpc.vpc_id

  ingress {
    from_port       = var.bastion_ports
    to_port         = var.bastion_ports
    protocol        = "tcp"
    prefix_list_ids = [aws_ec2_managed_prefix_list.prefix_list.id]
  }

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

  lifecycle {
    create_before_destroy = true
  }

  tags = {
    Name = "${var.project}-${var.environment}-bastion"
  }
}



resource "aws_security_group" "backend" {
  name_prefix = "${var.project}-${var.environment}-backend-"
  description = "Allow 22 from bastion & 3306 access from frontend"
  vpc_id      = module.vpc.vpc_id

  ingress {
    from_port       = var.backend_ports
    to_port         = var.backend_ports
    protocol        = "tcp"
    security_groups = [aws_security_group.frontend.id]
  }

  ingress {
    from_port       = var.bastion_ports
    to_port         = var.bastion_ports
    protocol        = "tcp"
    cidr_blocks     = var.ssh_to_backend == true ? ["0.0.0.0/0"] : null
    security_groups = [aws_security_group.bastion.id]
  }

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

  lifecycle {
    create_before_destroy = true
  }

  tags = {
    Name = "${var.project}-${var.environment}-backend"
  }
}



resource "aws_security_group" "frontend" {
  name_prefix = "${var.project}-${var.environment}-frontend-"
  description = "allow 22 from bastion & frontend_ports traffic"
  vpc_id      = module.vpc.vpc_id

  dynamic "ingress" {
    for_each = toset(var.frontend_ports)
    iterator = port
    content {
      from_port        = port.value
      to_port          = port.value
      protocol         = "tcp"
      cidr_blocks      = ["0.0.0.0/0"]
      ipv6_cidr_blocks = ["::/0"]
    }
  }

  ingress {
    from_port       = var.bastion_ports
    to_port         = var.bastion_ports
    protocol        = "tcp"
    cidr_blocks     = var.ssh_to_frontend == true ? ["0.0.0.0/0"] : null
    security_groups = [aws_security_group.bastion.id]
  }

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

  tags = {
    "Name" = "${var.project}-${var.environment}-frontend"
  }
}


resource "local_file" "backend" {
  filename = "backend.txt"
  content  = data.template_file.backend.rendered
}

resource "local_file" "frontend" {
  filename = "frontend.txt"
  content  = data.template_file.frontend.rendered
}



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



resource "aws_key_pair" "ssh_key" {
  key_name   = "${var.project}-${var.environment}"
  public_key = tls_private_key.key.public_key_openssh
  provisioner "local-exec" {
    command = "echo '${tls_private_key.key.private_key_pem}' > ./mykey.pem ; chmod 400 ./mykey.pem"
  }
  provisioner "local-exec" {
    when    = destroy
    command = "rm -rf ./mysshkey.pem"
  }

  tags = {
    "Name" = "${var.project}-${var.environment}"
  }
}


resource "aws_instance" "bastion" {
  ami                         = var.instance_ami
  instance_type               = var.instance_type
  key_name                    = aws_key_pair.ssh_key.key_name
  associate_public_ip_address = true
  subnet_id                   = module.vpc.public_subnets.0
  vpc_security_group_ids      = [aws_security_group.bastion.id]
  user_data                   = file("bastion.sh")
  user_data_replace_on_change = true

  tags = {
    Name = "${var.project}-${var.environment}-bastion"
  }
}



resource "aws_instance" "frontend" {
  ami                         = var.instance_ami
  instance_type               = var.instance_type
  key_name                    = aws_key_pair.ssh_key.key_name
  associate_public_ip_address = true
  subnet_id                   = module.vpc.public_subnets.1
  vpc_security_group_ids      = [aws_security_group.frontend.id]
  user_data                   = data.template_file.frontend.rendered
  user_data_replace_on_change = true
  depends_on                  = [aws_instance.backend]

  tags = {
    Name = "${var.project}-${var.environment}-frontend"
  }
}



resource "aws_instance" "backend" {
  ami                         = var.instance_ami
  instance_type               = var.instance_type
  key_name                    = aws_key_pair.ssh_key.key_name
  associate_public_ip_address = false
  subnet_id                   = module.vpc.private_subnets.0
  vpc_security_group_ids      = [aws_security_group.backend.id]
  user_data                   = data.template_file.backend.rendered
  user_data_replace_on_change = true
  depends_on                  = [module.vpc]

  tags = {
    Name = "${var.project}-${var.environment}-backend"
  }
}



resource "aws_route53_zone" "private" {
  name = var.private_domain
  vpc {
    vpc_id = module.vpc.vpc_id
  }
}


resource "aws_route53_record" "db" {
  zone_id = aws_route53_zone.private.zone_id
  name    = "db.${var.private_domain}"
  type    = "A"
  ttl     = 300
  records = [aws_instance.backend.private_ip]
}


create a record in public hosted zone to access the wp site

resource "aws_route53_record" "wordpress" {
  zone_id = data.aws_route53_zone.mydomain.zone_id
  name    = "wordpress.${var.public_domain}"
  type    = "A"
  ttl     = 300
  records = [aws_instance.frontend.public_ip]
}
  • Modules: The module block is used to fetch a module from a GitHub repository. In this case, we are fetching the AWS VPC modules from the specified repository.

  • Resources: The resource block is used to define an AWS resource. In this file, we are defining AWS resources such as security groups, managed prefix lists, SSH key pairs, etc. Each resource has a unique identifier called name, and Terraform will use this identifier to track the resource and manage its lifecycle.

  • Variables: The variable block is used to define input variables that can be passed to the Terraform configuration. These input variables can be used to customize the behavior of the configuration. For example, in this file, we are using variables to define the project name, environment, VPC CIDR, public IPs, backend ports, frontend ports, SSH access to frontend and backend, etc.

  • Outputs: The output block is used to define output variables that can be used to export data from the Terraform configuration. These output variables can be used to pass information between Terraform configurations.

This configuration part is explained in detail in one of my earlier projects:

Run the following commands

cd Reuse-Configuration-Using-Terraform-Modules
terraform init
terraform validate
terraform plan
terraform apply


Conclusion

With this project you've discovered how to use Terraform modules to automate the creation of infrastructure in this post. Now that you know how to use Terraform, you should feel confident in automating your AWS infrastructure.

reuse-configuration-using-terraform-modules's People

Contributors

sreehariskumar avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.