Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Production-Like AWS Environment Provisioning with Terraform

DZone's Guide to

Production-Like AWS Environment Provisioning with Terraform

Learn more about both AWS and Terraform by creating an AWS environment that you can see in-browser using this tutorial.

· Cloud Zone ·
Free Resource

Container Monitoring and Management eBook: Read about the new realities of containerization.

This blog provides a template for provisioning a full AWS infrastructure from the ground using Terraform. So just to keep things simple for this blog, we will create a simple web app which will read some data from a table and will show the result in a browser.

To achieve this simple task, we will try to create a production-ready environment in AWS using Terraform automation which will require us to set up a VPC, Network Gateway, subnets, routes, security groups, an EC2 machine with MySQL installed inside a private network, and a web app machine with Apache and its PHP module in a public subnet.

If you are interested to know the reasons for choosing Terraform for AWS automation, then read my blog on Terraform here. It is an interesting tool for Infrastructure-as-Code (IAC), written in Go language, and gives you the ability to describe a complex infrastructure through configuration scripts written in HCL.

The most important point is that you can use it to provision infrastructure across multiple IAAS providers like AWS, GCP, and Azure.

Prerequisites

There are only two prerequisites for this:

  1. Terraform – Installation instructions are covered in my previous blog here.
  2. Free account with AWS. You can register for free Tier AWS account here.

I have used Ubuntu as the OS in this tutorial but instructions should work with any operating system.

Infrastructure-as-Code

The following diagram shows the high-level architecture diagram on how infrastructure will be provisioned in AWS:

AWS VPC Setup

Building Blocks

We will be creating following configuration files to describe the infrastructure –

Variable.tf

This file contains the variables and configurable properties used in other scripts. This file also contains the secret and access keys and key name for AWS. You will have to insert them in this file before you proceed further.

However, keep in mind that it is not a good practice to put access and secret keys in a .tf file due to security risks. But for this tutorial, I am putting them in a variables.tf file to keep it simple.
A better approach would be either to use a credential file in ~/.aws/ or prompt user to enter access and secret keys in the shell/command line.

variable "region" {
    default = "eu-west-2"
}
variable "AmiLinux" {
type = "map"
    default = {
    eu-west-2 = "ami-a36f8dc4"
    eu-west-1 = "ami-ca0135b3"
    us-east-1 = "ami-14c5486b"
    }
}
variable "aws_access_key" {
    default = ""
    description = "user aws access key"
}
variable "aws_secret_key" {
    default = ""
    description = " user aws secret key"
}
variable "vpc-fullcidr" {
    default = "172.16.0.0/16"
    description = "the vpc cdir"
}
variable "Subnet-Public-AzA-CIDR" {
    default = "172.16.0.0/24"
    description = "the cidr of the subnet"
}
variable "Subnet-Private-AzA-CIDR" {
    default = "172.16.3.0/24"
    description = "the cidr of the subnet"
}
variable "key_name" {
    default = "MyAWSKey"
    description = "the ssh key to use in the EC2 machines"
}
variable "DnsZoneName" {
     default = "ShaanAWSDNS.internal"
     description = "the internal dns name"
}


Network.tf

This file does following tasks:

  • Set up the provider for AWS
  • Create a VPC
  • Set the options for internal VPC DNS resolution
provider "aws" {
    access_key = "${var.aws_access_key}"
    secret_key = "${var.aws_secret_key}"
    region = "${var.region}"
}
resource "aws_vpc" "terraformmain" {
    cidr_block = "${var.vpc-fullcidr}"
    #### this 2 true values are for use the internal vpc dns resolution
    enable_dns_support = true
    enable_dns_hostnames = true
    tags {
        Name = "My terraform vpc"
    }
}


Production Like AWS Environment Provisioning with Terraform – My Tryst with Technology

Routing.tf

This file will accomplish the following configuration:

  • attach an internet gateway to the VPC
  • define a network ACL
  • define two routing tables: one for public access, and the other one for private access

Along with this, we will also define an AWS NAT Gateway to make the database machine more secure as it will need internet access to install MySQL. AWS NAT Gateway will ensure that there are no incoming requests from outside the database. It is important, however, to deploy it in a public subnet and associate an elastic IP to it. The  depends_on attribute allows us to avoid errors and create the NAT gateway only after the internet gateway is in the available state.

# Declare the data source
data "aws_availability_zones" "available" {}

/* EXTERNAL NETWORK , IG, ROUTE TABLE */
resource "aws_internet_gateway" "gw" {
    vpc_id = "${aws_vpc.terraformmain.id}"
    tags {
        Name = "internet gw terraform generated"
    }
}
resource "aws_network_acl" "all" {
    vpc_id = "${aws_vpc.terraformmain.id}"
    egress {
        protocol = "-1"
        rule_no = 2
        action = "allow"
        cidr_block = "0.0.0.0/0"
        from_port = 0
        to_port = 0
    }
    ingress {
         protocol = "-1"
         rule_no = 1
         action = "allow"
         cidr_block = "0.0.0.0/0"
         from_port = 0
         to_port = 0
    }
    tags {
         Name = "open acl"
    }
}
resource "aws_route_table" "public" {
    vpc_id = "${aws_vpc.terraformmain.id}"
    tags {
        Name = "Public"
    }
    route {
        cidr_block = "0.0.0.0/0"
        gateway_id = "${aws_internet_gateway.gw.id}"
    }
}
resource "aws_route_table" "private" {
    vpc_id = "${aws_vpc.terraformmain.id}"
    tags {
       Name = "Private"
    }
route {
    cidr_block = "0.0.0.0/0"
    nat_gateway_id = "${aws_nat_gateway.PublicAZA.id}"
    }
}
resource "aws_eip" "forNat" {
    vpc = true
}
    resource "aws_nat_gateway" "PublicAZA" {
    allocation_id = "${aws_eip.forNat.id}"
    subnet_id = "${aws_subnet.PublicAZA.id}"
   depends_on = ["aws_internet_gateway.gw"]
}


Production Like AWS Environment Provisioning with Terraform – My Tryst with Technology

Subnets.tf

resource "aws_subnet" "PublicAZA" {
    vpc_id = "${aws_vpc.terraformmain.id}"
    cidr_block = "${var.Subnet-Public-AzA-CIDR}"
    tags {
        Name = "PublicAZA"
    }
    availability_zone = "${data.aws_availability_zones.available.names[0]}"
}
resource "aws_route_table_association" "PublicAZA" {
    subnet_id = "${aws_subnet.PublicAZA.id}"
    route_table_id = "${aws_route_table.public.id}"
}
resource "aws_subnet" "PrivateAZA" {
    vpc_id = "${aws_vpc.terraformmain.id}"
    cidr_block = "${var.Subnet-Private-AzA-CIDR}"
    tags {
        Name = "PublicAZB"
    }
    availability_zone = "${data.aws_availability_zones.available.names[1]}"
}
resource "aws_route_table_association" "PrivateAZA" {
    subnet_id = "${aws_subnet.PrivateAZA.id}"
    route_table_id = "${aws_route_table.private.id}"
}


There are two subnets associated with the respective routes: a public and a private.

DNS-and-DHCP.tf

This file will realize three tasks:

  • Create private Route53 DNS zone
  • Association with VPC
  • Create the DNS record for database
resource "aws_vpc_dhcp_options" "shaandhcp" {
    domain_name = "${var.DnsZoneName}"
    domain_name_servers = ["AmazonProvidedDNS"]
    tags {
        Name = "My internal name"
    }
}

resource "aws_vpc_dhcp_options_association" "dns_resolver" {
    vpc_id = "${aws_vpc.terraformmain.id}"
    dhcp_options_id = "${aws_vpc_dhcp_options.shaandhcp.id}"
}

/* DNS PART ZONE AND RECORDS */
resource "aws_route53_zone" "main" {
    name = "${var.DnsZoneName}"
    vpc_id = "${aws_vpc.terraformmain.id}"
    comment = "Managed by terraform"
}

resource "aws_route53_record" "database" {
    zone_id = "${aws_route53_zone.main.zone_id}"
    name = "mydatabase.${var.DnsZoneName}"
    type = "A"
    ttl = "300"
    records = ["${aws_instance.database.private_ip}"]
}


It is important to note that the database DNS record depends on the private IP of the EC2 database machine. This machine will be allocated during the database creation.

Securitygroups.tf

resource "aws_security_group" "WebApp" {
    name = "WebApp"
    tags {
       Name = "WebApp"
    }
   description = "ONLY HTTP CONNECTION INBOUND"
    vpc_id = "${aws_vpc.terraformmain.id}"

    ingress {
        from_port = 80
        to_port = 80
        protocol = "TCP"
        cidr_blocks = ["0.0.0.0/0"]
    }
    ingress {
        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"]
    }
}

resource "aws_security_group" "MySQLDB" {
    name = "MySQLDB"
     tags {
         Name = "MySQLDB"
    }
    description = "ONLY tcp CONNECTION INBOUND"
     vpc_id = "${aws_vpc.terraformmain.id}"
     ingress {
         from_port = 3306
         to_port = 3306
         protocol = "TCP"
         security_groups = ["${aws_security_group.WebApp.id}"]
    }
    ingress {
        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"]
    }
}

This file will create two security groups: one for the web application, and another for the database. As mentioned earlier both will have the outbound (egress) rule to have internet access for yum to install the Apache and MySQL servers, but the connection to the MySQL port will be allowed only from instances that belong to the webapp security group.

EC2.tf

This is the configuration for creating the EC2 machines in AWS with the userdata scripts for installing and setting up the web server and MySQL database.

resource "aws_instance" "phpapp" {
  ami           = "${lookup(var.AmiLinux, var.region)}"
  instance_type = "t2.micro"
  associate_public_ip_address = "true"
  subnet_id = "${aws_subnet.PublicAZA.id}"
  vpc_security_group_ids = ["${aws_security_group.WebApp.id}"]
  key_name = "${var.key_name}"
  tags {
        Name = "My Web App"
  }
  user_data = <<HEREDOC
  #!/bin/bash
  yum update -y
  yum install -y httpd24 php56 php56-mysqlnd
  service httpd start
  chkconfig httpd on
  echo "<?php" >> /var/www/html/myApp.php
  echo "\$conn = new mysqli('mydatabase.ShaanAWSDNS.internal', 'root', 'secret', 'test');" >> /var/www/html/myApp.php
  echo "\$sql = 'SELECT * FROM Employees'; " >> /var/www/html/myApp.php
  echo "\$result = \$conn->query(\$sql); " >>  /var/www/html/myApp.php
  echo "while(\$row = \$result->fetch_assoc()) { echo 'the value is: ' . \$row['NAME'],  \$row['ADDRESS'];} " >> /var/www/html/myApp.php
  echo "\$conn->close(); " >> /var/www/html/myApp.php
  echo "?>" >> /var/www/html/myApp.php
HEREDOC
}

resource "aws_instance" "database" {
  ami           = "${lookup(var.AmiLinux, var.region)}"
  instance_type = "t2.micro"
  associate_public_ip_address = "false"
  subnet_id = "${aws_subnet.PrivateAZA.id}"
  vpc_security_group_ids = ["${aws_security_group.MySQLDB.id}"]
  key_name = "${var.key_name}"
  tags {
        Name = "sql database"
  }
  user_data = <<HEREDOC
  #!/bin/bash
  sleep 180
  yum update -y
  yum install -y mysql55-server
  service mysqld start
  /usr/bin/mysqladmin -u root password 'secret'
  mysql -u root -psecret -e "create user 'root'@'%' identified by 'secret';" mysql
  mysql -u root -psecret -e 'CREATE TABLE Employees (ID int(11) NOT NULL AUTO_INCREMENT, NAME varchar(45) DEFAULT NULL, ADDRESS varchar(255) DEFAULT NULL, PRIMARY KEY (ID));' test
  mysql -u root -psecret -e "INSERT INTO Employees (NAME, ADDRESS) values ('JOHN', 'LONDON UK') ;" test
HEREDOC
}


Web App EC2 Instance:

It is placed in the public subnet so that it can be accessed from browser using port 80. The user data performs the following actions:

  • yum update
  • installs Apache web server and its PHP module
  • starts Apache and sets the config to start it automatically on boot
  • using the echo command to place a PHP file in the /var/www/html directory and reads the value  from the table created inside the database in another EC2 instance.

MySQL DB EC2 Instance:

It is placed in the private subnet and has its security group. The user data performs the following actions:

  • yum update
  • installs the MySQL server and runs it
  • configures the root user to grant access from other machines
  • creates a table in the database and adds one record to the table

Provision it with Terraform

There are three commands which are pretty much required to provision the infrastructure using Terraform.

  •  terraform init 

The first command isterraform initwhich will install the AWS plugin for Terraform and will successfully initialize it.

Terraform Init

  •  terraform plan 

If you would like to review what actions terraform will perform in AWS before actually doing it then you can runterraform plan command which will check your changes before you unleash them onto the world.

  •  terraform apply

The third and most important command isterraform apply which will apply the changes and provision the environment in AWS.

Terraform Apply


It will take few minutes for the environment to provision but once completed you can go to the AWS web console and verify that VPC, EC2 instances, network gateway, and subnets are created properly.

Take the public IP of the web app EC2 instance and verify that you are able to access it in the browser and see the results from the table there.

AWS Terraform - verify

Clean up the Infrastructure Provisioned in AWS

While developing scripts and testing them, you may need to destroy the infrastructure created in AWS. This can be accomplished with a single command  ‘terraform destroy’and you don’t have to worry about deleting/terminating each and every service in AWS manually –

Production Like AWS Environment Provisioning with Terraform – My Tryst with Technology

Terraform Destroy


Wrapping Up

The scripts used in this article are also available in GitHub.

This was a quick tutorial to show how to provision infrastructure in AWS using Terraform. Even though this was a two-tier set up, you can use the concepts to set up a multi-stack infrastructure with various components. You can also make changes as suggested in my post to keep the AWS access key and secret keys to avoid any security issues.

The Essentials of Container Monitoring: Learn the 4 principles of application containerization. Download Now.

Topics:
aws ,terraform ,iac ,environment setup ,aws vpc ,cloud ,cloud security

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}