off to AWS we go with Terraform

By | December 11, 2020

Running terraform locally is great for local development but the real power of Terraform is when you provision your code on the cloud. This post will discuss how we can do that.

First you will need a AWS account, click on this link for information on the free tier account.

Next you need to install AWS CLI which will allow you to type AWS command in your terminal window.

Confirm that AWS CLI is installed correctly using aws --version.

aws --version
aws-cli/2.1.10 Python/3.7.4 Darwin/19.6.0 exe/x86_64 prompt/off

Set up an IAM user on AWS with admin privileges. Then create an access key for that user. Never create an access key for your route account.

Make sure you keep this key safe. You will see it only once when you create it so save the key to a csv file so that you can use it in the following steps. Then delete that file once you have put it in the configuration steps that follow. Below is an example of what your CSV file will look like.

Access key IDSecret access key
AASDFLKJADFKASDF…asdfasdfa…

To configure AWS CLI type in aws configure. Enter the access key and secret and choose your preferred aws region.

aws configure
AWS Access Key ID [None]: AASDFLKJADFKASDF...
AWS Secret Access Key [None]: asdfasdfa...
Default region name [None]: use-east-1
Default output format [None]:

The configuration process creates a file ~/.aws/credentials on MacOS. After you done using terraform delete this file and delete the credential in AWS to maintain security of your account. The file will look something like this.

I also deactivate or delete my keys in AWS when not using them. This is an extra layer of security so no one else can use them. It is very easy to change the configuration file when you want to run again.

cat ~/.aws/credentials
[default]
aws_access_key_id = AASDFLKJADFKASDF...
aws_secret_access_key = asdfasdfa...

Write configuration

The set of files used to describe infrastructure in Terraform is known as a Terraform configuration. You’ll write your first configuration now to launch a single AWS EC2 instance.

Each configuration should be in its own directory. Create a directory for the new configuration and change into it.

mkdir learn-terraform-aws-instance
cd learn-terraform-aws-instance

Create a file for the configuration code.

$ vim main.tf

Create a new file using vim main.tf then press i and paste the following Terraform configuration. Then save the file by entering esc then :wq.

Note: The example below uses an ami image that creates an Ubuntu Server with id ami-0885b1f6bd170450c. You need to confirm that image is still available in AWS and change it if needed.

#############################################################
# Define what cloud plugins for Terraform to use
#############################################################

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 2.70"
    }
  }
}

#############################################################
# Define what cloud provider we are using
#############################################################

provider "aws" {
  profile = "default"
  region  = "us-east-1"
}

#############################################################
# xxxxx
#############################################################

resource "aws_instance" "ec2-instance" { 
    ami = "ami-04b2519c83e2a7ea5" 
    instance_type = "t2.micro"
    vpc_security_group_ids = [aws_security_group.web_security.id]
    key_name = "MyEC2KeyPair"
    user_data = <<-EOF
                    #!/bin/bash
                    sudo yum update -y
                    sudo yum install nginx -y 
                    sudo service nginx start
                EOF
    tags = {
        Name = "nginx-instance",
        created-date = "22-04-2020"
    }
} 

#############################################################
# xxxxx
#############################################################

resource "aws_security_group" "web_security" {
    name = "web-security"

  ingress {
     from_port = 22
     to_port = 22
     protocol = "tcp" 
     cidr_blocks = ["0.0.0.0/0"]
  }
  ingress {
     from_port = 80
     to_port = 80
     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 is a complete configuration that Terraform is ready to apply.

The terraform block describes what providers are used. In our case it is AWS. Providers are cloud companies like AWS, Google Cloud and Azure. The Terraform block tells Terraform what plugins to install so that it can communicate with that provider.

The provider block configures AWS itself. In our case profile = "default" tells Terraform to use your AWS credential we set up previously and region = "us-east-1" sets the region.

The resource block defines what infrastructure resources we will be using in this case t2.micro in using ami image ami-830c94e3.

Initialize the directory

When you create a new configuration you need to initialize the directory with terraform init. This will install all the plugins necessary to drive the provider you selected. In this case it will install all the plugins for AWS in a hidden directory in the learn-terraform-aws-instance directory.

terraform init

Initializing the backend...

Initializing provider plugins...
- Finding hashicorp/aws versions matching "~> 2.70"...
- Installing hashicorp/aws v2.70.0...
- Installed hashicorp/aws v2.70.0 (signed by HashiCorp)

Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

Format and validate

It’g good practice to format and validate your code before creating your infrastructure. You can do that by running the following.

terraform fmt
terraform validate

Success! The configuration is valid.

Format and validate

The terraform plan command is used to create an execution plan. This execution plan will show everything that will be done. Its a good step before terraform init. You are not starting anything.

terraform plan
aws_security_group.web_security: Refreshing state... [id=sg-084d93406724f6ce0]

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # aws_instance.ec2-instance will be created
  + resource "aws_instance" "ec2-instance" {
      + ami                          = "ami-04d29b6f966df1537"
      ...
      + get_password_data            = false
      ...
      + instance_type                = "t2.micro"
      ...
      + key_name                     = "MyEC2KeyPair"
      ...
      + source_dest_check            = true
      ...
      + tags                         = {
          + "Name"         = "nginx-instance"
          + "created-date" = "22-04-2020"
        }
      ...
      + user_data                    = "13c33d5399498dd535c26b4ff90b0759053a19ea"
      ...
      + vpc_security_group_ids       = [
          + "sg-084d93406724f6ce0",
        ]

      + ebs_block_device {
          ...
        }

      + ephemeral_block_device {
          ...
        }

      + metadata_options {
          ...
        }

      + network_interface {
          ...
        }

      + root_block_device {
          ...
        }
    }

Plan: 1 to add, 0 to change, 0 to destroy.

------------------------------------------------------------------------

Note: You didn't specify an "-out" parameter to save this plan, so Terraform
can't guarantee that exactly these actions will be performed if
"terraform apply" is subsequently run.

Ready, set, go, let’s create infrastructure!

To create your infrastructure enter terraform apply. You will need to confirm with yes. After that it will indicate progress and creating complete. Pretty cool stuff. After it has done that check it out in your AWS account.

terraform plan
terraform apply

...

Plan: 1 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

aws_instance.example: Creating...
aws_instance.example: Still creating... [10s elapsed]
aws_instance.example: Still creating... [20s elapsed]
aws_instance.example: Still creating... [30s elapsed]
aws_instance.example: Creation complete after 32s [id=i-0d191436299eabe3a]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

Explore your running EC2 instance

When you applied your configuration, Terraform wrote data into a file called terraform.tfstate. This file now contains the IDs and properties of the resources Terraform created so that it can manage or destroy those resources going forward.

You must save your state file securely and distribute it only to trusted team members who need to manage your infrastructure. In production, we recommend storing your state remotely. Remote stage storage enables collaboration using Terraform but is beyond the scope of this tutorial.

Inspect the current state using terraform show.

$ terraform show
# aws_instance.example:
  - resource "aws_instance" "example" {
      - ami                          = "ami-0885b1f6bd170450c" -> null
      - arn                          = "arn:aws:ec2:us-east-1:355163483861:instance/i-0d191436299eabe3a" -> null
      - associate_public_ip_address  = true -> null
      - availability_zone            = "us-east-1b" -> null
      ...
    }
}

Terminate your infrastructure

As quickly as we create our infrastructure we can destroy it using terraform destroy which uses the information in the  terraform.tfstate. file.

terraform destroy

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  - destroy

Terraform will perform the following actions:

  # aws_instance.example will be destroyed
  - resource "aws_instance" "example" {
      - ami                          = "ami-04d29b6f966df1537" -> null
      - arn                          = "arn:aws:ec2:us-east-1:355163483861:instance/i-0d191436299eabe3a" -> null
      - associate_public_ip_address  = true -> null
      - availability_zone            = "us-east-1b" -> null
      ...
    }

Plan: 0 to add, 0 to change, 1 to destroy.

Do you really want to destroy all resources?
  Terraform will destroy all your managed infrastructure, as shown above.
  There is no undo. Only 'yes' will be accepted to confirm.

  Enter a value: yes

aws_instance.example: Destroying... [id=i-0d191436299eabe3a]
aws_instance.example: Still destroying... [id=i-0d191436299eabe3a, 10s elapsed]
aws_instance.example: Still destroying... [id=i-0d191436299eabe3a, 20s elapsed]
aws_instance.example: Still destroying... [id=i-0d191436299eabe3a, 30s elapsed]
aws_instance.example: Destruction complete after 30s

Destroy complete! Resources: 1 destroyed.

So we created and destroyed real infrastructure on AWS using a very small config file and simple commands. All from the comfort of our local terminal how cool it that!