Terraforming your AWS Infrastructure

Overview

Writing your infrastructure as though it were code is a huge step forward from the days of past. In a single step, you will be documenting your entire stack while orchestrating the creation of its infrastructure. A second, even larger benefit, is the ability to version control your infrastructure. Imagine being able to stand up an entire production or staging infrastructure in less than an hour, rather than days.

In this article, I will guide you through the process of standing up a fairly simple, but common, infrastructure hosted on Amazon’s Web Service. The diagram below illustrates how the infrastructure will look after the Terraform config is applied.

Terrafrom AWS

Terraform

Terraform is a product released by HashiCorp that abstracts away from your provider’s API layer, such as AWS in this case, allowing you to document your whole infrastructure in a JSON-like language that is easy to read and understand.

The tool itself goes well beyond Amazon AWS. There are a number of providers available, such as for VMWare, DigitalOcean, and Heroku to name a few.

Coding our Infrastructure

Install Terraform and Git

  1. Install Git.
    Debian \ Ubuntu

    sudo apt-get install git

    Redhat \ CentOS

    sudo yum install git

    Windows
    Download Git for Windows.

  2. Download Terraform from the Terraform website at https://www.terraform.io/downloads.html
  3. Extract the downloaded Terraform archive into a directory set in your PATH environment variable.
    Windows: C:\Windows\System32
    Linux: /usr/bin.

 

Set the Provider

We start by defining our provider, which in our case is AWS. You will need to set some values that allow Terraform to connect and communicate with Amazon.

  1. Create a directory to hold your Terraform files.
    mkdir -p ~/terraform/myapp
  2. Create a file with the extension “.tf”. In this example, the filename myapp.tf will be created.
  3. Open the tf file into a text editor.
  4. Add the following contents to it. We set the provider to “AWS” to signify it is for Amazon’s Web Service. We also specify an access key, secret key, and the region we will be building the infrastructure in.
    # Configure the AWS Provider
    provider "aws" {
        access_key = "${var.aws_access_key}"
        secret_key = "${var.aws_secret_key}"
        region = "us-east-1"
    }
    

 

Defining the VPC Network

  1. Define a VPC named “myapp” and set the CIDR block to 10.100.0.0/16.
    resource "aws_vpc" "myapp" {
         cidr_block = "10.100.0.0/16"
    }
    
  2. Add two subnets for our public servers. Explicitly set the availability zone to ensure the two are always created in separate ones, as we will want to ensure redundancy. Because they are both public, set the map_public_ip_on_launch to true.
    resource "aws_subnet" "public_1a" {
        vpc_id = "${aws_vpc.myapp.id}"
        cidr_block = "10.100.0.0/24"
        map_public_ip_on_launch = "true"
        availability_zone = "us-east-1a"
    
        tags {
            Name = "Public 1A"
        }
    }
    
    resource "aws_subnet" "public_1b" {
        vpc_id = "${aws_vpc.myapp.id}"
        cidr_block = "10.100.1.0/24"
        map_public_ip_on_launch = "true"
        availability_zone = "us-east-1b"
    
        tags {
            Name = "Public 1B"
        }
    }
    
  3. To allow Internet access to our servers, add an Internet Gateway to the configuration.
    resource "aws_internet_gateway" "gw" {
        vpc_id = "${aws_vpc.myapp.id}"
    
        tags {
            Name = "myapp gw"
        }
    }
    

 

Security Groups

  1. Create a new security group to allow SSH access
    resource "aws_security_group" "allow_ssh" {
      name = "allow_all"
      description = "Allow inbound SSH traffic from my IP"
      vpc_id = "${aws_vpc.myapp.id}"
    
      ingress {
          from_port = 22
          to_port = 22
          protocol = "tcp"
          cidr_blocks = ["123.123.123.123/32"]
      }
    
      tags {
        Name = "Allow SSH"
      }
    }
    
  2. Create a security group to allow web server access to the public.
    resource "aws_security_group" "web_server" {
      name = "web server"
      description = "Allow HTTP and HTTPS traffic in, browser access out."
      vpc_id = "${aws_vpc.myapp.id}"
    
      ingress {
          from_port = 80
          to_port = 80
          protocol = "tcp"
          cidr_blocks = ["0.0.0.0/0"]
      }
    
      egress {
          from_port = 1024
          to_port = 65535
          protocol = "tcp"
          cidr_blocks = ["0.0.0.0/0"]
      }
    }
    
  3. Create a security group to allow MySQL RDS Access to the web servers.
    resource "aws_security_group" "myapp_mysql_rds" {
      name = "web server"
      description = "Allow access to MySQL RDS"
      vpc_id = "${aws_vpc.myapp.id}"
    
      ingress {
          from_port = 3306
          to_port = 3306
          protocol = "tcp"
          cidr_blocks = ["${aws_instance.web01.private_ip}","${aws_instance.web02.private_ip}"]
      }
    
      egress {
          from_port = 1024
          to_port = 65535
          protocol = "tcp"
          cidr_blocks = ["0.0.0.0/0"]
      }
    }
    

 

Create the EC2 Instances

With the network infrastructure in place, it’s time to start defining our EC2 instances.

  1. Create two instances for the web service, with one in each public subnet defined above. The instances will both be t1.micro, and they will both run Ubuntu 14.04 LTS.
    resource "aws_instance" "web01" {
        ami = "ami-408c7f28"
        instance_type = "t1.micro"
        subnet_id = "${aws_subnet.public_1a.id}"
        vpc_security_group_ids = ["${aws_security_group.web_server.id}","${aws_security_group.allow_ssh.id}"]
        key_name = "myapp keypair"
        tags {
            Name = "web01"
        }
    }
    
    resource "aws_instance" "web02" {
        ami = "ami-408c7f28"
        instance_type = "t1.micro"
        subnet_id = "${aws_subnet.public_1b.id}"
        vpc_security_group_ids = ["${aws_security_group.web_server.id}","${aws_security_group.allow_ssh.id}"]
        key_name = "myapp keypair"
        tags {
            Name = "web02"
        }
    }
    

 

Add the ELB for Load Balancing

  1. Define an ELB to attaches to the two public subnets, add both web EC2 instances, and set an HTTP listener.
    resource "aws_elb" "web-elb" {
      name = "web-elb"
      availability_zones = ["us-east-1a", "us-east-1b"]
    
      listener {
        instance_port = 80
        instance_protocol = "http"
        lb_port = 80
        lb_protocol = "http"
      }
    
    
      health_check {
        healthy_threshold = 2
        unhealthy_threshold = 2
        timeout = 3
        target = "HTTP:80/"
        interval = 30
      }
    
      instances = ["${aws_instance.web01.id}","${aws_instance.web02.id}"]
    
      cross_zone_load_balancing = true
      idle_timeout = 400
      connection_draining = true
      connection_draining_timeout = 400
    
      tags {
        Name = "Web ELB"
      }
    }
    

 

Define the MySQL RDS Instance

    1. Create the DB Subnet Group.
      resource "aws_db_subnet_group" "myapp-db" {
          name = "main"
          description = "Our main group of subnets"
          subnet_ids = ["${aws_subnet.public-1a.id}", "${aws_subnet.public-1b.id}"]
          tags {
              Name = "MyApp DB subnet group"
          }
      }
      
      

 

  1. Create the RDS Instance.
    resource "aws_db_instance" "web-rds-01" {
        identifier = "myappdb-rds"
        allocated_storage = 10
        engine = "mysql"
        engine_version = "5.6.17"
        instance_class = "db.t1.micro"
        name = "myappdb"
        username = "foo"
        password = "bar"
        vpc_security_group_ids = ["${aws_security_group.myapp_mysql_rds.id"]
        db_subnet_group_name = "${aws_db_subnet_group.myapp-db.id}"
        parameter_group_name = "default.mysql5.6"
    }

 

Version Control Your Files

With the base infrastructure defined our files should be added to version control. Using Git, initialize the repository and commit the changes.

  1. If your text file is still open, save it and then close it.
  2. Initialize the repository.
    git init .
  3. Add the “.tf” to the repository.
    git add myapp.tf
  4. Create the initial commit for your infrastructure.
    git commit -m "Initial commit"
  5. Our infrastructure is now under version control and ready to be deployed for the first time!

 

Managing your Infrastructure

Deploy Your Infrastructure

Run the following command to deploy your infrastructure using Terraform.

terraform apply

Destroy Your Infrastructure

Run the following command to delete all resources created by Terraform.

terraform destroy