Terraform create EC2 Instance on AWS



This guide will help you to Create your first AWS EC2 using terraform.

When it comes to IAC(Infrastructure As Code) Terraform is always the first choice of DevOps although there are many alternatives available in the market such as Ansible, Chef, Puppet, SaltStack, CloudFormation but due the fact that -

  1. Terraform is really easy install
  2. Terraform has very good API documentation
  3. It is widely adopted in the DevOps community
  4. Great support for a popular cloud service provider such as Google Cloud Platform, AWS.

(To know more on How to setup Virtual Machine on Google Cloud - Click Here)



Prerequisite

  1. The only prerequisite is - You must install Terraform before jumping to Google Cloud Setup.

Table of Content

  1. Setup AWS Account
  2. Generate Access keys (access key ID and secret access key)
  3. Create your first Terraform infrastructure (main.tf)
  4. terraform commands
  5. Setup up a custom startup script for an Amazon Elastic Compute Cloud (EC2)
  6. Copy and Execute the script from local machine to remote EC2 instance using file and remote-exec provisioner

1. Setup AWS Account

As our aim of this article to setup an AWS EC2 instance the first step would be to create an AWS account.

If you are a beginner and want to learn the Terraform then AWS provides you free tier - 12 months or 750 Hours/month, where you can experiment.

1.1 Sign up for AWS account

Goto https://aws.amazon.com/ and click on Complete sign-up

AWS Sing up page

You need to choose AMAZON EC2 Free Tier. This tier is sufficient enough for learning purposes.

AWS EC2 Free tier

Amazon will ask for your Credit card number to complete the sign-up process, AWS will debit around 1$ so that they can verify your card details. Amazon will refund the amount after the authorization.

1.2 After Signup LogIn as ROOT user

After signup, you need to log in as a ROOT user for your AWS account.

AWS console login as root user

Once you login into your AWS account you should see a dashboard, I know the dashboard can be little overwhelming for the first time user but do not worry we are gonna take one item at a time.

AWS dashboard after login



2.Generate Access keys (access key ID and secret access key)

Terraform installed on your Desktop/Laptop needs to communicate with AWS and to make this communication terraform needs to be authenticated.

For authentication, we need to generate Access Keys (access key ID and secret access key). These access keys can be used for making - programmatic calls to AWS from the AWS CLI, Tools for PowerShell, AWS SDKs, or direct AWS API calls.

  1. Goto My Security Credentials

AWS security credentials

  1. On Your Security Credentials page click on Access keys (access key ID and secret access key)

AWS access key create new access key

  1. Click on Create New Access key

  2. Copy the Access Key ID and Secret Access Key (Note:- You can view the Secret Access Key only once, so make sure to copy it.)

AWS access key id and secret access key generated



3. Create your first Terraform infrastructure (main.tf)

Before we start writing terraform script, the first thing to learn over here is - "You need to save your configuration with .tf extension"

As Terraform is developed by HashiCorp, so we use HCL for writing the terraform scripts.

We will start by creating an empty main.tf file.


3.1 Provider

The first line of code in which we are going to write is provider.

We need to tell terraform which cloud provider we are going to connect .e.g - AWS, Google, or Azure

As this article is focused on AWS, so we are going to mention AWS as our provider.

Here is the basic syntax for the provider

1resource "<PROVIDER>_<TYPE>" "<NAME>" {
2 [CONFIG …]
3}
  1. "PROVIDER _ TYPE" - aws, google
  2. "NAME" - You can define your name.

This is how our final main.tf will look like for AWS -

1provider "aws" {
2  region     = "eu-central-1"
3  access_key = "XXXXXXXXXXXXXXXXXXXX"
4  secret_key = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
5} 

Note: You can copy access_key and secret_key from Step-2



3.2 resource - "aws_instance"

Now after defining the provider, next we are going define is resource.

So what do you mean by resource?

Resource - It is something that we are going to provision/start on AWS.

Now for this article, we are going to provision EC2 instance on AWS.

But before we provision the EC2 instance, we need to gather few points -

  1. ami = you need to tell Terraform which AMI(Amazon Machine Image) you are going to use. Is it going to be Ubuntu, CentOS or something else
  2. instance_type = Also based on your need you have to choose the instance_type and it can be t2.nano, t2.micro, t2. small etc.

3.3 How to find ami(Amazon Machine Image)

  1. To find the correct ami you need to Goto: Services -> EC2

AWS services EC2

  1. In the left Navigation you will find Images -> AMIs

AWS AMIs option in the left navigation

  1. On the search menu click on public images and then type Ubuntu.

AWS access key id and secret access key generated

  1. Copy the AMI ID from the search result.

AWS access key id and secret access key generated

3.4 How to find correct instance_type

You can find the correct ìnstance_type` by visiting this page.

Since I am looking for a very basic instance_type not production level instance, so I choose t2.micro

Here is the aws_instance configuration -

1resource "aws_instance" "ec2_example" {
2    ami = "ami-0767046d1677be5a0"  
3    instance_type = "t2.micro" 
4    tags = {
5        Name = "Terraform EC2"
6    }
7}


4. terraform commands

Alright, now we have completed all the pre-requisites for provisioning our first ec2 instance on the AWS.


4.1 terraform plan

The first command which we are going to run is -

1terraform init 
 1Initializing the backend...
 2
 3Initializing provider plugins...
 4- Reusing the previous version of hashicorp/aws from the dependency lock file
 5- Installing hashicorp/aws v3.32.0...
 6- Installed hashicorp/aws v3.32.0 (signed by HashiCorp)
 7
 8Terraform has been successfully initialized!
 9
10You may now begin working with Terraform. Try running "terraform plan" to see
11any changes that are required for your infrastructure. All Terraform commands
12should now work.
13
14If you ever set or change modules or backend configuration for Terraform,
15rerun this command to reinitialize your working directory. If you forget, other
16commands will detect it and remind you to do so if necessary 

The terraform init command is responsible for downloading all the dependencies which are required for the provider AWS.

Once you issue the terraform init command it will download all the provider's dependencies on your local machine.


4.2 terraform plan

This command will help you to understand how many resources you are gonna add or delete.

Here is the command -

1terraform plan 
 1An execution plan has been generated and is shown below.
 2Resource actions are indicated with the following symbols:
 3  + create
 4
 5Terraform will perform the following actions:
 6
 7  # aws_instance.ec2_example will be created
 8  + resource "aws_instance" "ec2_example" {
 9      + ami                          = "ami-0767046d1677be5a0"
10      + arn                          = (known after apply)
11      + associate_public_ip_address  = (known after apply)
12      + availability_zone            = (known after apply)
13      + cpu_core_count               = (known after apply)
14      + cpu_threads_per_core         = (known after apply)
15      + get_password_data            = false
16      + host_id                      = (known after apply)
17      + id                           = (known after apply)
18      + instance_state               = (known after apply)
19      + instance_type                = "t2.micro"
20      + ipv6_address_count           = (known after apply)
21      + ipv6_addresses               = (known after apply)
22      + key_name                     = (known after apply)
23      + outpost_arn                  = (known after apply)
24      + password_data                = (known after apply)
25      + placement_group              = (known after apply)
26      + primary_network_interface_id = (known after apply)
27      + private_dns                  = (known after apply)
28      + private_ip                   = (known after apply)
29      + public_dns                   = (known after apply)
30      + public_ip                    = (known after apply)
31      + secondary_private_ips        = (known after apply)
32      + security_groups              = (known after apply)
33      + source_dest_check            = true
34      + subnet_id                    = (known after apply)
35      + tags                         = {
36          + "Name" = "Terraform EC2"
37        }
38      + tenancy                      = (known after apply)
39      + vpc_security_group_ids       = (known after apply)
40
41      + ebs_block_device {
42          + delete_on_termination = (known after apply)
43          + device_name           = (known after apply)
44          + encrypted             = (known after apply)
45          + iops                  = (known after apply)
46          + kms_key_id            = (known after apply)
47          + snapshot_id           = (known after apply)
48          + tags                  = (known after apply)
49          + throughput            = (known after apply)
50          + volume_id             = (known after apply)
51          + volume_size           = (known after apply)
52          + volume_type           = (known after apply)
53        }
54
55      + enclave_options {
56          + enabled = (known after apply)
57        }
58
59      + ephemeral_block_device {
60          + device_name  = (known after apply)
61          + no_device    = (known after apply)
62          + virtual_name = (known after apply)
63        }
64
65      + metadata_options {
66          + http_endpoint               = (known after apply)
67          + http_put_response_hop_limit = (known after apply)
68          + http_tokens                 = (known after apply)
69        }
70
71      + network_interface {
72          + delete_on_termination = (known after apply)
73          + device_index          = (known after apply)
74          + network_interface_id  = (known after apply)
75        }
76
77      + root_block_device {
78          + delete_on_termination = (known after apply)
79          + device_name           = (known after apply)
80          + encrypted             = (known after apply)
81          + iops                  = (known after apply)
82          + kms_key_id            = (known after apply)
83          + tags                  = (known after apply)
84          + throughput            = (known after apply)
85          + volume_id             = (known after apply)
86          + volume_size           = (known after apply)
87          + volume_type           = (known after apply)
88        }
89    }
90
91Plan: 1 to add, 0 to change, 0 to destroy.
92
93------------------------------------------------------------------------
94
95Note: You didn't specify an "-out" parameter to save this plan, so Terraform
96can't guarantee that exactly these actions will be performed if
97"terraform apply" is subsequently run. 

As you can see the output of terraform plan, at the end it will show all the resources added and deleted.

**(Note:- This command is not going to provision start your t2.micro instance)**


4.3 terraform apply

This command will do some real stuff on AWS. Once you will issue this command, it will be going to connect to AWS and then finally going to provision AWS instance.

Here is the command -

1terraform apply 
 1Plan: 1 to add, 0 to change, 0 to destroy.
 2
 3Do you want to perform these actions?
 4  Terraform will perform the actions described above.
 5  Only 'yes' will be accepted to approve.
 6
 7  Enter a value: yes
 8
 9aws_instance.ec2_example: Creating...
10aws_instance.ec2_example: Still creating... [10s elapsed]
11aws_instance.ec2_example: Still creating... [20s elapsed]
12aws_instance.ec2_example: Still creating... [30s elapsed]
13aws_instance.ec2_example: Creation complete after 33s [id=i-0a948ac635a2010f1]
14
15Apply complete! Resources: 1 added, 0 changed, 0 destroyed. 

As you can see the log output has created t2.micro instance.


4.4 Verify the EC2 setup

Let's verify the setup by going back to AWS console.

Goto -> Services -> EC2 you should see 1 instance running.

AWS ec2 running instance

AWS ec2 running instance with more details

You can also see the Tag name - Terraform EC2 which we mentioned in the terraform script.


4.4 terraform destroy

Now we have seen how to write your terraform script and how to provision your EC2 instance.

Let see how to remove or delete everything from AWS.

We are going to use the command -

1terraform destroy 
 1Plan: 0 to add, 0 to change, 1 to destroy.
 2
 3Do you want to destroy all resources?
 4  Terraform will destroy all your managed infrastructure, as shown above.
 5  There is no undo. Only 'yes' will be accepted to confirm.
 6
 7  Enter a value: yes
 8
 9aws_instance.ec2_example: Destroying... [id=i-0a948ac635a2010f1]
10aws_instance.ec2_example: Still destroying... [id=i-0a948ac635a2010f1, 10s elapsed]
11aws_instance.ec2_example: Still destroying... [id=i-0a948ac635a2010f1, 20s elapsed]
12aws_instance.ec2_example: Still destroying... [id=i-0a948ac635a2010f1, 30s elapsed]
13aws_instance.ec2_example: Still destroying... [id=i-0a948ac635a2010f1, 40s elapsed]
14aws_instance.ec2_example: Destruction complete after 40s
15
16Destroy complete! Resources: 1 destroyed. 

It will remove all the running EC2 Instances.

The previous steps were very basic Terraform examples for setting up an EC2 instance. But in the actual project I think you need to do a little more than just basic EC2 Instance setup. The remaining part of this blog post will help you to do some advanced setup with your EC2 Instance using Terraform.

For example -

Setup up a custom startup script for an Amazon Elastic Compute Cloud (EC2) using Terraform

Copy and Execute script from local machine to remote EC2 instance using file and remote-exec provisioner

5. Setup up a custom startup script for an Amazon Elastic Compute Cloud (EC2) using Terraform

To set up a custom startup script for an Amazon Elastic Compute Cloud (EC2) instance using Terraform, you can use the user_data attribute of the aws_instance resource. The user_data attribute allows you to specify data (such as a script) that will be used by cloud-init to configure the instance when it is launched.

Here is an example of how to use the user_data attribute to specify a custom startup script for an EC2 instance in Terraform. In this example -

  1. First we will setup the EC2 instance
  2. Secondly we are going to install Apache Web Server
  3. At last we are going to set up a very basic HTML page and deploy it on the Apache web server
 1
 2provider "aws" {
 3   region     = "eu-central-1"
 4   access_key = "<INSERT_YOUR_ACCESS_KEY>"
 5   secret_key = "<INSERT_YOUR_SECRET_KEY>"
 6}
 7
 8resource "aws_instance" "ec2_example" {
 9
10    ami = "ami-0767046d1677be5a0"
11    instance_type = "t2.micro"
12    key_name= "aws_key"
13    vpc_security_group_ids = [aws_security_group.main.id]
14
15  user_data = <<-EOF
16      #!/bin/sh
17      sudo apt-get update
18      sudo apt install -y apache2
19      sudo systemctl status apache2
20      sudo systemctl start apache2
21      sudo chown -R $USER:$USER /var/www/html
22      sudo echo "<html><body><h1>Hello this custom page built with Terraform User Data</h1></body></html>" > /var/www/html/index.html
23      EOF
24} 

6. Copy and Execute the script from local machine to remote EC2 instance using file and remote-exec provisioner

In the previous section, you have seen how to execute custom scripts using the user_data block of terraform. But it is not recommended to use user_data for long and complex scripts.

In case of long and complex scripts, you should use file as well as remote-exec provisioner. (For more in-depth tutorial please refer to this blog post on - What is terraform provisioner )

file provisioner- First we will use file provisioner to copy the file to the remote EC2 instance.

remote-exec provisioner- To execute the script copied using file provisioner

Here is an example where you can also use the file provisioner in Terraform to copy a script file from the local machine to the instance and execute it.

 1#main.tf
 2
 3provider "aws" {
 4   region     = "eu-central-1"
 5   access_key = "<INSERT_YOUR_ACCESS_KEY>"
 6   secret_key = "<INSERT_YOUR_SECRET_KEY>"
 7}
 8
 9resource "aws_instance" "ec2_example" {
10
11    ami = "ami-0767046d1677be5a0"  
12    instance_type = "t2.micro" 
13    key_name= "aws_key"
14    vpc_security_group_ids = [aws_security_group.main.id]
15   
16  # file provisioner - 
17  # It will copy the "startup.sh" to remote machine
18  provisioner "file" {
19    source      = "/home/rahul/Jhooq/startup.sh"
20    destination = "/home/ubuntu/startup.sh"
21  }
22  
23  # connection -
24  # This block will be used for ssh connection to initiate the copy
25  connection {
26      type        = "ssh"
27      host        = self.public_ip
28      user        = "ubuntu"
29      private_key = file("/home/rahul/Jhooq/keys/aws/aws_key")
30      timeout     = "4m"
31   }
32}
33
34   # remote-exec - 
35   # Execute the "startup.sh" script copied using "file" provisoner
36   provisioner "remote-exec" {
37    inline = [
38      "chmod +x /home/ubuntu/startup.sh",
39      "/home/ubuntu/startup.sh"
40    ]
41   }
42
43resource "aws_security_group" "main" {
44  egress = [
45    {
46      cidr_blocks      = [ "0.0.0.0/0", ]
47      description      = ""
48      from_port        = 0
49      ipv6_cidr_blocks = []
50      prefix_list_ids  = []
51      protocol         = "-1"
52      security_groups  = []
53      self             = false
54      to_port          = 0
55    }
56  ]
57 ingress                = [
58   {
59     cidr_blocks      = [ "0.0.0.0/0", ]
60     description      = ""
61     from_port        = 22
62     ipv6_cidr_blocks = []
63     prefix_list_ids  = []
64     protocol         = "tcp"
65     security_groups  = []
66     self             = false
67     to_port          = 22
68  }
69  ]
70}
71
72# aws_key_pair -
73# You need to generate the public as well as private key using - ssh-keygen
74# Place the public key here 
75resource "aws_key_pair" "deployer" {
76  key_name   = "aws_key"
77  public_key = "<PLACE_YOU_PUBLIC_KEY>"
78}

Read More - Terragrunt -

  1. How to use Terragrunt?

Posts in this Series