The Startup
Published in

The Startup

How to Manage Your AWS Infrastructure With Terraform

Thanks for visiting my Medium blog. Interested in reading my latest writeups? Visit me at https://www.pidnull.io. See you there!

What is Terraform?

Hashicorp Terraform

Hashicorp Terraform is an extremely useful and flexible tool for building, changing, and versioning infrastructure safely and efficiently. It builds on the concept of Infrastructure-as-Code — managing entire infrastructures using machine-readable configuration files rather than interactive commands and configuration tools.

Terraform is one of Hashicorp’s offerings dedicated for automation. An open-source command-line version of Terraform is available for everyone through GitHub.

Terraform works well with a wide array of private cloud providers such as VMWare vSphere as well as public cloud providers such as Google Cloud Platform (GCP), Microsoft Azure, and Amazon AWS. The full list of supported providers can be found here.

In this detailed tutorial, I will walk you through the steps to start managing an AWS infrastructure using Terraform from scratch. This is how our AWS architecture would look like:

AWS Architecture

Prerequisites

Before we can start using Terraform to manage an AWS infrastructure, we need to set up the following:

IAM user

As an AWS best-practice, create an IAM user with programmatic access and the following policies attached to them via Identity and Access Management (IAM) in the AWS console:

- NetworkAdministrator
- AmazonEC2FullAccess
- AmazonS3FullAccess

If you have not created an IAM user before, here is a useful guide you can use. At the end of the IAM user creation process, you should be presented with the following page showing the access key ID and the secret access key. Please copy these values and save them somewhere (private) because we will be using them at a later step.

For this tutorial, I have created a user called terraform-user:

IAM user for Terraform

Remember: always keep these credentials confidential! Don’t worry about me, these credentials will no longer work once I publish this tutorial!

S3 Bucket

Create an S3 Bucket which will hold our terraform.tfstate file. Terraform state files are explained in a later step in this guide.

If you have not create an S3 bucket before, here is a useful guide you can use. For this tutorial, I have created an S3 bucket called terraform-s3-bucket-testing:

S3 bucket for Terraform state file

Remember: Please block ALL public access to the bucket and to all the objects within the bucket.

Installation

Now, we’re ready to install Terraform!

First, open a terminal window and create a working directory for our Terraform project. Please note that for this tutorial, I am using a macOS so you might need to use other commands which correspond to your workstation.

mkdir ~/terraform-aws
cd ~/terraform-aws

As of writing, the latest version of Terraform command-line is 0.12.29. Installing Terraform on the following platforms are supported:

  • macOS (64-bit)
  • FreeBSD (32-bit, 64-bit, and ARM)
  • Linux (32-bit, 64-bit, and ARM)
  • OpenBSD (32-bit and 64-bit)
  • Solaris (64-bit)
  • Windows (32-bit and 64-bit)

For this tutorial, I am using a macOS, so I need to download the corresponding Zip file:

wget https://releases.hashicorp.com/terraform/0.12.29/terraform_0.12.29_darwin_amd64.zip

Alternatively, you can visit the download page where you can find the latest executables for your respective platform.

Next, unzip the executable and show the terraform binary.

unzip terraform_0.12.29_darwin_amd64.zip
ls -l terraform

Move the binary to a location in your $PATH. I would suggest moving terraform to /usr/local/bin:

mv terraform /usr/local/bin

Verify that the terraform binary is available in the current $PATH:

which terraform

If /usr/local/bin/terraform is displayed in the output, then we’re all set! Run this last command to check the Terraform version:

santino:terraform-aws santino$ terraform - version
Terraform v0.12.29
santino:terraform-aws santino$

Terraform state

Terraform keeps a mapping between the real-world resources in the provider (in our case, Amazon AWS) with our local configuration. It does this by maintaining a terraform.tfstate file which by default is stored in the local working directory.

This works well if you were alone in maintaining our AWS infrastructure using Terraform. However, when working with groups, everyone who needs to execute terraform must also have a copy of the terraform.tfstate file on their local working directory.

An approach to solve this problem would be to commit the terraform.tfstate file to a Git repository so that everyone can work on the same file. However, this approach insecure, given that the state file contains a lot of valuable information about your AWS infrastructure. In fact, the .gitignore files in Terraform’s official git repository prevents the file from being managed by Git:

terraform.tfstate
terraform.tfstate.backup
.terraform/*

The best approach to this problem is to make use of a remote terraform state repository. This can be achieved in a few ways, but for this tutorial, we will be using an S3 bucket to store our terraform.tfstate file.

Define the provider and remote state location

Create our first .tf file using your favorite editor, which will contain details about of provider (AWS) and the S3 bucket which will store our remote state.

provider.tf

# Define AWS as our provider
provider "aws" {
region = "eu-central-1"
}
# Terraform remote state
terraform {
backend "s3" {
bucket = "terraform-s3-bucket-testing"
key = "terraform-s3-bucket-testing/terraform.tfstate"
region = "eu-central-1"
}
}

The next step is to define 2 environment variables which will contain the access key ID and the secret access key from an earlier section.

export AWS_ACCESS_KEY_ID="AKIAUN57B3YGREQNABE4"
export AWS_SECRET_ACCESS_KEY="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"

After defining our provider, the location of our S3 bucket, and the login credentials to our AWS account, we can now initialize our Terraform project using the terraform init command.

Here is a sample output:

santino:terraform-aws santino$ terraform initInitializing the backend...Successfully configured the backend "s3"! Terraform will automatically
use this backend unless the backend configuration changes.
Initializing provider plugins...
- Checking for available provider plugins...
- Downloading plugin for provider "aws" (hashicorp/aws) 2.70.0...
The following providers do not have any version constraints in configuration,
so the latest version was installed.
To prevent automatic upgrades to new major versions that may contain breaking
changes, it is recommended to add version = "..." constraints to the
corresponding provider blocks in configuration, with the constraint strings
suggested below.
* provider.aws: version = "~> 2.70"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.
santino:terraform-aws santino$

Managing the network

After successfully initializing our Terraform project, we can now go ahead and create resources in our AWS account.

We start with the network. We have to create a few network objects:

  • VPC
  • Subnets
  • Internet Gateway
  • Route Table

vpc.tf

## Demo VPC
resource "aws_vpc" "terraform-demo-vpc" {
cidr_block = "10.0.0.0/16"
instance_tenancy = "default"
enable_dns_support = "true"
enable_dns_hostnames = "true"
enable_classiclink = "false"
tags = {
Name = "terraform-demo-vpc"
}
}

subnets.tf

## Demo subnets# Web tier subnet
resource "aws_subnet" "terraform-demo-snet-web" {
vpc_id = aws_vpc.terraform-demo-vpc.id
cidr_block = "10.0.0.0/21"
map_public_ip_on_launch = "true"
availability_zone = "eu-central-1a"
tags = {
Name = "terraform-demo-snet-web"
}
}
# Application tier subnet
resource "aws_subnet" "terraform-demo-snet-app" {
vpc_id = aws_vpc.terraform-demo-vpc.id
cidr_block = "10.0.8.0/21"
map_public_ip_on_launch = "false"
availability_zone = "eu-central-1a"
tags = {
Name = "terraform-demo-snet-app"
}
}
# Database tier subnet
resource "aws_subnet" "terraform-demo-snet-db" {
vpc_id = aws_vpc.terraform-demo-vpc.id
cidr_block = "10.0.16.0/21"
map_public_ip_on_launch = "false"
availability_zone = "eu-central-1a"
tags = {
Name = "terraform-demo-snet-db"
}
}

igw.tf

## Internet Gateway
resource "aws_internet_gateway" "terraform-demo-igw" {
vpc_id = aws_vpc.terraform-demo-vpc.id
tags = {
Name = "terraform-demo-igw"
}
}

route-table.tf

## Route table
resource "aws_route_table" "terraform-demo-rtable" {
vpc_id = aws_vpc.terraform-demo-vpc.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.terraform-demo-igw.id
}
tags = {
Name = "terraform-demo-rtable"
}
}
# Route table associations
resource "aws_route_table_association" "terraform-demo-rtassoc1" {
subnet_id = aws_subnet.terraform-demo-snet-web.id
route_table_id = aws_route_table.terraform-demo-rtable.id
}
resource "aws_route_table_association" "terraform-demo-rtassoc2" {
subnet_id = aws_subnet.terraform-demo-snet-app.id
route_table_id = aws_route_table.terraform-demo-rtable.id
}
resource "aws_route_table_association" "terraform-demo-rtassoc3" {
subnet_id = aws_subnet.terraform-demo-snet-db.id
route_table_id = aws_route_table.terraform-demo-rtable.id
}

Quick explanation!

Notice that the syntax for defining a resource is as follows:

 resource "<resource type>" "<resource name>"

We can then refer to this resource when creating other resources using the <resource type>.<resource name>.<property> syntax.

For example, we defined our VPC as:

## Demo VPC
resource "aws_vpc" "terraform-demo-vpc" {
...

When creating a subnet, we need to assocate it with the VPC where it should be located in. Thus, we define this association with the vpc_id parameter and the ID of the VPC:

resource "aws_subnet" "terraform-demo-snet-web" {
vpc_id = aws_vpc.terraform-demo-vpc.id
...

Applying our changes

Use the terraform plan command to review the changes that Terraform would perform on our AWS infrastructure before applying them.

Sample output:

santino:terraform-aws santino$ terraform plan
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.
------------------------------------------------------------------------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_internet_gateway.terraform-demo-igw will be created
+ resource "aws_internet_gateway" "terraform-demo-igw" {
+ arn = (known after apply)
+ id = (known after apply)
+ owner_id = (known after apply)
+ tags = {
+ "Name" = "terraform-demo-igw"
}
+ vpc_id = (known after apply)
}
<CUT># aws_vpc.terraform-demo-vpc will be created
+ resource "aws_vpc" "terraform-demo-vpc" {
+ arn = (known after apply)
+ assign_generated_ipv6_cidr_block = false
+ cidr_block = "10.0.0.0/16"
+ default_network_acl_id = (known after apply)
+ default_route_table_id = (known after apply)
+ default_security_group_id = (known after apply)
+ dhcp_options_id = (known after apply)
+ enable_classiclink = false
+ enable_classiclink_dns_support = (known after apply)
+ enable_dns_hostnames = true
+ enable_dns_support = true
+ id = (known after apply)
+ instance_tenancy = "default"
+ ipv6_association_id = (known after apply)
+ ipv6_cidr_block = (known after apply)
+ main_route_table_id = (known after apply)
+ owner_id = (known after apply)
+ tags = {
+ "Name" = "terraform-demo-vpc"
}
}
Plan: 9 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.
santino:terraform-aws santino$

Since we are objects from scratch, the output of the terraform plan command shows that it would create 9 objects (1 VPC, 3 subnets, 1 internet gateway, 1 route table, and 3 route table associations).

To finally create the objects in AWS, run the terraform apply command. You will be given a final chance to review the changes and will be prompted whether or not you would like to proceed. After responding with yes, Terraform would then modify AWS to reflect your desired state.

Sample output:

santino:terraform-aws santino$ terraform applyAn 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_internet_gateway.terraform-demo-igw will be created
+ resource "aws_internet_gateway" "terraform-demo-igw" {
+ arn = (known after apply)
+ id = (known after apply)
+ owner_id = (known after apply)
+ tags = {
+ "Name" = "terraform-demo-igw"
}
+ vpc_id = (known after apply)
}
<CUT># aws_vpc.terraform-demo-vpc will be created
+ resource "aws_vpc" "terraform-demo-vpc" {
+ arn = (known after apply)
+ assign_generated_ipv6_cidr_block = false
+ cidr_block = "10.0.0.0/16"
+ default_network_acl_id = (known after apply)
+ default_route_table_id = (known after apply)
+ default_security_group_id = (known after apply)
+ dhcp_options_id = (known after apply)
+ enable_classiclink = false
+ enable_classiclink_dns_support = (known after apply)
+ enable_dns_hostnames = true
+ enable_dns_support = true
+ id = (known after apply)
+ instance_tenancy = "default"
+ ipv6_association_id = (known after apply)
+ ipv6_cidr_block = (known after apply)
+ main_route_table_id = (known after apply)
+ owner_id = (known after apply)
+ tags = {
+ "Name" = "terraform-demo-vpc"
}
}
Plan: 9 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: yesaws_vpc.terraform-demo-vpc: Creating...
aws_vpc.terraform-demo-vpc: Creation complete after 2s [id=vpc-03ae143c2a8e3284c]
aws_internet_gateway.terraform-demo-igw: Creating...
aws_subnet.terraform-demo-snet-app: Creating...
aws_subnet.terraform-demo-snet-db: Creating...
aws_subnet.terraform-demo-snet-web: Creating...
aws_subnet.terraform-demo-snet-app: Creation complete after 1s [id=subnet-0b6474b59034abcb0]
aws_subnet.terraform-demo-snet-db: Creation complete after 1s [id=subnet-0c5f28652ff57e71b]
aws_internet_gateway.terraform-demo-igw: Creation complete after 1s [id=igw-05519819650e3f727]
aws_route_table.terraform-demo-rtable: Creating...
aws_subnet.terraform-demo-snet-web: Creation complete after 2s [id=subnet-06bd93be3e1b13772]
aws_route_table.terraform-demo-rtable: Creation complete after 1s [id=rtb-08a56a903445b0c7c]
aws_route_table_association.terraform-demo-rtassoc3: Creating...
aws_route_table_association.terraform-demo-rtassoc2: Creating...
aws_route_table_association.terraform-demo-rtassoc1: Creating...
aws_route_table_association.terraform-demo-rtassoc1: Creation complete after 1s [id=rtbassoc-0d9d2fe02770cf4fd]
aws_route_table_association.terraform-demo-rtassoc2: Creation complete after 1s [id=rtbassoc-04e0a5bdcddd03f22]
aws_route_table_association.terraform-demo-rtassoc3: Creation complete after 1s [id=rtbassoc-0daec5cb8a9545d2a]
Apply complete! Resources: 9 added, 0 changed, 0 destroyed.
santino:terraform-aws santino$

You can now login to the AWS console to verify the objects created. Here is how my dashboard now looks like:

VPC Dashboard

The terraform.tfstate file would also be created in the S3 bucket which we have defined earlier:

S3 bucket containing the remote state file

Deploying servers

Now that our VPC is ready, we can now deploy servers.

Security groups

First, let us define 3 security groups:

  1. Security group that would allow inbound access to our web servers in the terraform-demo-snet-web subnet from the internet.
  2. Security group that would allow access to our application servers in the terraform-demo-snet-app subnet from the terraform-demo-snet-web subnet.
  3. Security group that would allow access to our database servers in the terraform-demo-snet-db subnet from the terraform-demo-snet-app subnet.

secgroups.tf

## Security groups# Web tier security group
resource "aws_security_group" "terraform-demo-secgrp-webpub" {
vpc_id = aws_vpc.terraform-demo-vpc.id
name = "terraform-demo-secgrp-webpub"
description = "Allow web traffic from the internet"
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
description = "Plain HTTP"
}
ingress{
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
description = "Secure HTTP"
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "terraform-demo-secgrp-webpub"
}
}
# Application tier security group
resource "aws_security_group" "terraform-demo-secgrp-app" {
vpc_id = aws_vpc.terraform-demo-vpc.id
name = "terraform-demo-secgrp-app"
description = "Allow traffic from the web tier"
ingress{
from_port = 8080
to_port = 8080
protocol = "tcp"
cidr_blocks = ["10.0.0.0/21"]
description = "Plain HTTP"
}
ingress{
from_port = 8443
to_port = 8443
protocol = "tcp"
cidr_blocks = ["10.0.0.0/21"]
description = "Secure HTTP"
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "terraform-demo-secgrp-app"
}
}
# Database tier security group
resource "aws_security_group" "terraform-demo-secgrp-db" {
vpc_id = aws_vpc.terraform-demo-vpc.id
name = "terraform-demo-secgrp-db"
description = "Allow traffic from the app tier"
ingress{
from_port = 5432
to_port = 5432
protocol = "tcp"
cidr_blocks = ["10.0.8.0/21"]
description = "PostgreSQL"
}
ingress{
from_port = 3306
to_port = 3306
protocol = "tcp"
cidr_blocks = ["10.0.8.0/21"]
description = "MySQL"
}
ingress{
from_port = 27017
to_port = 27017
protocol = "tcp"
cidr_blocks = ["10.0.8.0/21"]
description = "mongodb"
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "terraform-demo-secgrp-db"
}
}

Again, run the same terraform plan command earlier and review the proposed changes, then run terraform apply and respond with yes to the prompt.

Sample output:

santino:terraform-aws santino$ terraform apply
aws_vpc.terraform-demo-vpc: Refreshing state... [id=vpc-03ae143c2a8e3284c]
<CUT>
aws_route_table_association.terraform-demo-rtassoc3: Refreshing state... [id=rtbassoc-0daec5cb8a9545d2a]
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:<CUT># aws_security_group.terraform-demo-secgrp-webpub will be created
+ resource "aws_security_group" "terraform-demo-secgrp-webpub" {
+ arn = (known after apply)
+ description = "Allow web traffic from the internet"
+ egress = [
+ {
+ cidr_blocks = [
+ "0.0.0.0/0",
]
+ description = ""
+ from_port = 0
+ ipv6_cidr_blocks = []
+ prefix_list_ids = []
+ protocol = "-1"
+ security_groups = []
+ self = false
+ to_port = 0
},
]
+ id = (known after apply)
+ ingress = [
+ {
+ cidr_blocks = [
+ "0.0.0.0/0",
]
+ description = "Plain HTTP"
+ from_port = 80
+ ipv6_cidr_blocks = []
+ prefix_list_ids = []
+ protocol = "tcp"
+ security_groups = []
+ self = false
+ to_port = 80
},
+ {
+ cidr_blocks = [
+ "0.0.0.0/0",
]
+ description = "Secure HTTP"
+ from_port = 443
+ ipv6_cidr_blocks = []
+ prefix_list_ids = []
+ protocol = "tcp"
+ security_groups = []
+ self = false
+ to_port = 443
},
]
+ name = "terraform-demo-secgrp-webpub"
+ owner_id = (known after apply)
+ revoke_rules_on_delete = false
+ tags = {
+ "Name" = "terraform-demo-secgrp-webpub"
}
+ vpc_id = "vpc-03ae143c2a8e3284c"
}
Plan: 3 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: yesaws_security_group.terraform-demo-secgrp-db: Creating...
aws_security_group.terraform-demo-secgrp-app: Creating...
aws_security_group.terraform-demo-secgrp-webpub: Creating...
aws_security_group.terraform-demo-secgrp-app: Creation complete after 2s [id=sg-0d920e06fa8dd370f]
aws_security_group.terraform-demo-secgrp-webpub: Creation complete after 2s [id=sg-029d4f9c83b6fed54]
aws_security_group.terraform-demo-secgrp-db: Creation complete after 2s [id=sg-049704a710b385483]
Apply complete! Resources: 3 added, 0 changed, 0 destroyed.
santino:terraform-aws santino$

Server definitions

Finally, we can now deploy our servers!

First, we will query for the ID of the latest official Centos 7 AMI. Next, we will create an SSH key resource based on our own public key in ~/.ssh/id_rsa.pub. Lastly, we will create our web server, our application server, and 2 database servers, all in their respective subnets. We would also associate each server with the corresponding security group created earlier.

servers.tf:

## Query the  latest AMI for Centos 7
data "aws_ami" "centos7" {
owners = ["679593333241"]
most_recent = true
filter {
name = "name"
values = ["CentOS Linux 7 x86_64 HVM EBS *"]
}
filter {
name = "architecture"
values = ["x86_64"]
}
filter {
name = "root-device-type"
values = ["ebs"]
}
}
## SSH Key pair
resource "aws_key_pair" "terraform-demo-sshkey-santino" {
key_name = "terraform-demo-sshkey-santino"
public_key = file("~/.ssh/id_rsa.pub")
tags = {
Name = "terraform-demo-sshkey-santino"
}
}
### Server definitions
# Web server
resource "aws_instance" "terraform-demo-web" {
ami = data.aws_ami.centos7.id
instance_type = "t2.micro"
key_name = aws_key_pair.terraform-demo-sshkey-santino.key_name
subnet_id = aws_subnet.terraform-demo-snet-web.id
vpc_security_group_ids = [aws_security_group.terraform-demo-secgrp-webpub.id]
tags = {
Name = "terraform-demo-web"
}
}
# Application server
resource "aws_instance" "terraform-demo-app" {
ami = data.aws_ami.centos7.id
instance_type = "t2.micro"
key_name = aws_key_pair.terraform-demo-sshkey-santino.key_name
subnet_id = aws_subnet.terraform-demo-snet-app.id
vpc_security_group_ids = [aws_security_group.terraform-demo-secgrp-app.id]
tags = {
Name = "terraform-demo-app"
}
}
# Database server - Postgresql
resource "aws_instance" "terraform-demo-postgres" {
ami = data.aws_ami.centos7.id
instance_type = "t3a.medium"
key_name = aws_key_pair.terraform-demo-sshkey-santino.key_name
subnet_id = aws_subnet.terraform-demo-snet-db.id
vpc_security_group_ids = [aws_security_group.terraform-demo-secgrp-db.id]
tags = {
Name = "terraform-demo-postgres"
}
}
# Database server - MySQL
resource "aws_instance" "terraform-demo-mysql" {
ami = data.aws_ami.centos7.id
instance_type = "t3a.medium"
key_name = aws_key_pair.terraform-demo-sshkey-santino.key_name
subnet_id = aws_subnet.terraform-demo-snet-db.id
vpc_security_group_ids = [aws_security_group.terraform-demo-secgrp-db.id]
tags = {
Name = "terraform-demo-mysql"
}
}

Again, run the same terraform plan command earlier and review the proposed changes, then run terraform apply and respond with yes to the prompt.

santino:terraform-aws santino$ terraform apply
data.aws_ami.centos7: Refreshing state...
aws_vpc.terraform-demo-vpc: Refreshing state... [id=vpc-03ae143c2a8e3284c]
aws_subnet.terraform-demo-snet-app: Refreshing state... [id=subnet-0b6474b59034abcb0]
aws_internet_gateway.terraform-demo-igw: Refreshing state... [id=igw-05519819650e3f727]
aws_subnet.terraform-demo-snet-web: Refreshing state... [id=subnet-06bd93be3e1b13772]
aws_subnet.terraform-demo-snet-db: Refreshing state... [id=subnet-0c5f28652ff57e71b]
aws_security_group.terraform-demo-secgrp-db: Refreshing state... [id=sg-049704a710b385483]
aws_security_group.terraform-demo-secgrp-app: Refreshing state... [id=sg-0d920e06fa8dd370f]
aws_security_group.terraform-demo-secgrp-webpub: Refreshing state... [id=sg-029d4f9c83b6fed54]
aws_route_table.terraform-demo-rtable: Refreshing state... [id=rtb-08a56a903445b0c7c]
aws_route_table_association.terraform-demo-rtassoc3: Refreshing state... [id=rtbassoc-0daec5cb8a9545d2a]
aws_route_table_association.terraform-demo-rtassoc1: Refreshing state... [id=rtbassoc-0d9d2fe02770cf4fd]
aws_route_table_association.terraform-demo-rtassoc2: Refreshing state... [id=rtbassoc-04e0a5bdcddd03f22]
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
+ create
-/+ destroy and then create replacement
Terraform will perform the following actions:# aws_instance.terraform-demo-app will be created
+ resource "aws_instance" "terraform-demo-app" {
+ ami = "ami-0e8286b71b81c3cc1"
+ arn = (known after apply)
+ associate_public_ip_address = (known after apply)
+ availability_zone = (known after apply)
+ cpu_core_count = (known after apply)
+ cpu_threads_per_core = (known after apply)
+ get_password_data = false
+ host_id = (known after apply)
+ id = (known after apply)
+ instance_state = (known after apply)
+ instance_type = "t2.micro"
+ ipv6_address_count = (known after apply)
+ ipv6_addresses = (known after apply)
+ key_name = "terraform-demo-sshkey-santino"
+ network_interface_id = (known after apply)
+ outpost_arn = (known after apply)
+ password_data = (known after apply)
+ placement_group = (known after apply)
+ primary_network_interface_id = (known after apply)
+ private_dns = (known after apply)
+ private_ip = (known after apply)
+ public_dns = (known after apply)
+ public_ip = (known after apply)
+ security_groups = (known after apply)
+ source_dest_check = true
+ subnet_id = "subnet-0b6474b59034abcb0"
+ tags = {
+ "Name" = "terraform-demo-app"
}
+ tenancy = (known after apply)
+ volume_tags = (known after apply)
+ vpc_security_group_ids = (known after apply)
+ ebs_block_device {
+ delete_on_termination = (known after apply)
+ device_name = (known after apply)
+ encrypted = (known after apply)
+ iops = (known after apply)
+ kms_key_id = (known after apply)
+ snapshot_id = (known after apply)
+ volume_id = (known after apply)
+ volume_size = (known after apply)
+ volume_type = (known after apply)
}
+ ephemeral_block_device {
+ device_name = (known after apply)
+ no_device = (known after apply)
+ virtual_name = (known after apply)
}
+ metadata_options {
+ http_endpoint = (known after apply)
+ http_put_response_hop_limit = (known after apply)
+ http_tokens = (known after apply)
}
+ network_interface {
+ delete_on_termination = (known after apply)
+ device_index = (known after apply)
+ network_interface_id = (known after apply)
}
+ root_block_device {
+ delete_on_termination = (known after apply)
+ device_name = (known after apply)
+ encrypted = (known after apply)
+ iops = (known after apply)
+ kms_key_id = (known after apply)
+ volume_id = (known after apply)
+ volume_size = (known after apply)
+ volume_type = (known after apply)
}
}
<CUT>Plan: 7 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<CUT>
aws_instance.terraform-demo-mysql: Creation complete after 13s [id=i-029c67de608e848b5]
aws_instance.terraform-demo-web: Still creating... [20s elapsed]
aws_instance.terraform-demo-app: Still creating... [20s elapsed]
aws_instance.terraform-demo-app: Creation complete after 22s [id=i-01363d0b335a46378]
aws_instance.terraform-demo-web: Still creating... [30s elapsed]
aws_instance.terraform-demo-web: Creation complete after 32s [id=i-059f9773402b1eb34]
Apply complete! Resources: 7 added, 0 changed, 0destroyed.
santino:terraform-aws santino$

Login to the EC2 Dashboard and navigate to Instances to verify the servers created:

EC2 Dashboard

Congratulations! You have just implemented your AWS infrastructure using Terraform!

Summary

In this tutorial, we have learned about:

  • What Terraform is
  • Prerequisites to using Terraform
  • How to install Terraform
  • Remote Terraform state
  • Defining our network objects
  • Deploying our servers

Up next

  1. To learn more about all possible parameters you can configure for an AWS resource (such as aws_instance), refer to the official Terraform documentation: https://registry.terraform.io/providers/hashicorp/aws/latest
  2. All code used in this project have been published to GitHub: https://github.com/guerzon/terraform-aws-demo. Comments and pull requests are very welcome!

--

--

Get smarter at building your thing. Follow to join The Startup’s +8 million monthly readers & +768K followers.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
pidnull

Platform engineer passionate about AWS, SRE, Terraform, and Ansible