Understanding Terraform on AWS: Spin Up EC2 Instances
Tags: terraform, devops, aws, tutorial
Published: 2020-02-12 11:53:43

**Deploying a server**
Is it really easy to deploy a server using terraform?
Part I - https://dev.to/iamtito/understanding-terraform-152k
![](https://media.giphy.com/media/YAnpMSHcurJVS/source.gif)
Terraform code is written in a language called HCL in files with the extension .tf. It is a declarative language. So the main goal will be to describe the infrastructure we want, and Terraform will create it. Firstly we have to configure the cloud provider(s) we want to use. Create a file called `resources.tf` and add the following code in it:
```
provider "aws" {
  region = "us-east-1"
}
```
This instructs Terraform the cloud prover you would be using, in this case, it is aws and that you wish to deploy your infrastructure in the us-east-1 region. If you've already configured your credentials as environment variables, you only need to specify the region. However, if you didn't configure your creds as an environment variable, the below works. i.e it specifies the location of your aws credentials/config
```
provider "aws" {
    region = "us-east-1"
    shared_credentials_file = "/PATH/TO/AWS/CONFIG"
    profile                 = "myAWSprofile"
}
```
Once updated run the below to initialize the project:
```
$ terraform init
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.
```
Now, we want to deploy an ec2 instance of type `t2.micro`. To do this, we need to specify the resource block and set the configuration of the resource block. i.e we need to set the ami (the Amazon Machine Image to run on the EC2 Instance) and the instance_type. Add the following code to `resources.tf` file
```
provider "aws" {
  region = "us-east-1"
}
resource "aws_instance" "server" {
  ami = "ami-2d39803a"
  instance_type = "t2.micro"
}
```
To identify the resource in the terraform code, the resource specifies a type (in this case, `aws_instance`), a name (in this case `server`) and a set of configuration parameters specific to the resource. Resource block describes one or more infrastructure objects, such as virtual networks, compute instances, or higher-level components such as DNS records. Check out the resource documentation.
To build the resource, its advisable to view the resource(s) that would be created before it gets created...use the below code to see what will happen to your infrastructure before applying/deploying it. This is a way to run a sanity test on your changes before deploying/applying it.
```
$ 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_instance.server
      id:                           <computed>
      ami:                          "ami-2d39803a"
      arn:                          <computed>
      associate_public_ip_address:  <computed>
      availability_zone:            <computed>
      cpu_core_count:               <computed>
      cpu_threads_per_core:         <computed>
      ebs_block_device.#:           <computed>
      ephemeral_block_device.#:     <computed>
      get_password_data:            "false"
      host_id:                      <computed>
      instance_state:               <computed>
      instance_type:                "t2.micro"
      ipv6_address_count:           <computed>
      ipv6_addresses.#:             <computed>
      key_name:                     <computed>
      network_interface.#:          <computed>
      network_interface_id:         <computed>
      password_data:                <computed>
      placement_group:              <computed>
      primary_network_interface_id: <computed>
      private_dns:                  <computed>
      private_ip:                   <computed>
      public_dns:                   <computed>
      public_ip:                    <computed>
      root_block_device.#:          <computed>
      security_groups.#:            <computed>
      source_dest_check:            "true"
      subnet_id:                    <computed>
      tenancy:                      <computed>
      volume_tags.%:                <computed>
      vpc_security_group_ids.#:     <computed>


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.
```
Note: resources with a plus sign `+` will be created, resources with a minus sign `-` will be deleted, and resources with a tilde sign `~` will be modified.
To create an instance, run `terraform apply`. Bypass the `yes` prompt by using `terraform apply -auto-approve` command to create the terraform resources.
```
$ terraform apply -auto-approve
aws_instance.server: Creating...
  ami:                          "" => "ami-2d39803a"
  arn:                          "" => "<computed>"
  associate_public_ip_address:  "" => "<computed>"
  availability_zone:            "" => "<computed>"
  cpu_core_count:               "" => "<computed>"
  cpu_threads_per_core:         "" => "<computed>"
  ebs_block_device.#:           "" => "<computed>"
  ephemeral_block_device.#:     "" => "<computed>"
  get_password_data:            "" => "false"
  host_id:                      "" => "<computed>"
  instance_state:               "" => "<computed>"
  instance_type:                "" => "t2.micro"
  ipv6_address_count:           "" => "<computed>"
  ipv6_addresses.#:             "" => "<computed>"
  key_name:                     "" => "<computed>"
  network_interface.#:          "" => "<computed>"
  network_interface_id:         "" => "<computed>"
  password_data:                "" => "<computed>"
  placement_group:              "" => "<computed>"
  primary_network_interface_id: "" => "<computed>"
  private_dns:                  "" => "<computed>"
  private_ip:                   "" => "<computed>"
  public_dns:                   "" => "<computed>"
  public_ip:                    "" => "<computed>"
  root_block_device.#:          "" => "<computed>"
  security_groups.#:            "" => "<computed>"
  source_dest_check:            "" => "true"
  subnet_id:                    "" => "<computed>"
  tenancy:                      "" => "<computed>"
  volume_tags.%:                "" => "<computed>"
  vpc_security_group_ids.#:     "" => "<computed>"
aws_instance.server: Still creating... (10s elapsed)
aws_instance.server: Still creating... (20s elapsed)
aws_instance.server: Still creating... (30s elapsed)
aws_instance.server: Creation complete after 36s (ID: i-0bf984bef5ff354d6)
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
```
Note: It took **36s** for Terraform to provision an ec2 instance in aws, manually it could couple of minutes. 
>The state of the ec2 instance created and all the info attributed to the instance is stored in `terraform.tfstate` file. Currently, its saved locally. In the coming next post, the state will be stored in a remote location to make it for easier version control, safer storage and outputs delegations to multiple teams. Terraform supports storing state in Terraform Cloud, HashiCorp Consul, Amazon S3, Alibaba Cloud OSS, and more.

----
<center>Viewing the state file🧐</center>
```
{
    "version": 3,
    "terraform_version": "0.11.11",
    "serial": 1,
    "lineage": "c2d70ed5-xxxxx-xxxx-xxxx-xxxxxxxx",
    "modules": [
        {
            "path": [
                "root"
            ],
            "outputs": {},
            "resources": {
                "aws_instance.server": {
                    "type": "aws_instance",
                    "depends_on": [],
                    "primary": {
                        "id": "i-0bf984bef5ff354d6",
                        "attributes": {
                            "ami": "ami-2d39803a",
                            "arn": "arn:aws:ec2:us-east-1:139912354378:instance/i-0bf984bef5ff354d6",
                            "associate_public_ip_address": "true",
                            "availability_zone": "us-east-1c",
                            "cpu_core_count": "1",
                            "cpu_threads_per_core": "1",
                            "credit_specification.#": "1",
                            "credit_specification.0.cpu_credits": "standard",
                            "disable_api_termination": "false",
                            "ebs_block_device.#": "0",
                            "ebs_optimized": "false",
                            "ephemeral_block_device.#": "0",
                            "get_password_data": "false",
                            "iam_instance_profile": "",
                            "id": "i-0bf984bef5ff354d6",
                            "instance_state": "running",
                            "instance_type": "t2.micro",
                            "ipv6_addresses.#": "0",
                            "key_name": "",
                            "monitoring": "false",
                            "network_interface.#": "0",
                            "network_interface_id": "eni-0372xxxxxx",
                            "password_data": "",
                            "placement_group": "",
                            "primary_network_interface_id": "eni-0372xxxxxxx",
                            "private_dns": "ip-172-31-53-112.ec2.internal",
                            "private_ip": "172.31.53.112",
                            "public_dns": "ec2-52-91-71-43.compute-1.amazonaws.com",
                            "public_ip": "52.91.71.43",
                            "root_block_device.#": "1",
                            "root_block_device.0.delete_on_termination": "true",
                            "root_block_device.0.iops": "100",
                            "root_block_device.0.volume_id": "vol-0394832bb747e9bf1",
                            "root_block_device.0.volume_size": "8",
                            "root_block_device.0.volume_type": "gp2",
                            "security_groups.#": "1",
                            "security_groups.xxxx": "default",
                            "source_dest_check": "true",
                            "subnet_id": "subnet-xxxxxxx",
                            "tags.%": "0",
                            "tenancy": "default",
                            "volume_tags.%": "0",
                            "vpc_security_group_ids.#": "1",
                            "vpc_security_group_ids.xxxxx": "sg-xxxxxxxx"
                        },
                        "meta": {
                            "e2bfb730-xxxx-xxxx-xxx-xxxxxxx": {
                                "create": 600000000000,
                                "delete": 1200000000000,
                                "update": 600000000000
                            },
                            "schema_version": "1"
                        },
                        "tainted": false
                    },
                    "deposed": [],
                    "provider": "provider.aws"
                }
            },
            "depends_on": []
        }
    ]
}
```
----

Server deployment completed using Terraform! To verify, login to the EC2 console, and you'll see something like this:

![](https://ucarecdn.com/a592bbb6-6787-4da2-b70d-f434b3062b81/)

Notice, the ec2-instance id output `i-0bf984bef5ff354d6` is identical with the instance id on the aws console. Next, modify the ec2-instance by giving a tag name for the ec2 instance.
Update the `resources.tf` file by add thing tags
```
provider "aws" {
  region = "us-east-1"
}
resource "aws_instance" "server" {
  ami               = "ami-2d39803a"
  instance_type     = "t2.micro"
  tags {
      Name          = "server-one"
      Environment   = "Production"
      App           = "ecommerce"
  }
}
```
Run the plan to preview
```
$ 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.

aws_instance.server: Refreshing state... (ID: i-0bf984bef5ff354d6)
------------------------------------------------------------------------
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  ~ update in-place
Terraform will perform the following actions:
  ~ aws_instance.server
      tags.%:           "0" => "3"
      tags.App:         "" => "ecommerce"
      tags.Environment: "" => "Production"
      tags.Name:        "" => "server-one"


Plan: 0 to add, 1 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.
Since terraform keeps track of all the resources it created, therefore, it knows the ec2 instance already exists. The `id` for the deployed ec2 instance is `i-0bf984bef5ff354d6`, then it shows the difference between the current and new intended change, denoted with the sign `~`. Apply the change and verify it on the console.
```
$ terraform apply -auto-approve
aws_instance.server: Refreshing state... (ID: i-0bf984bef5ff354d6)
aws_instance.server: Modifying... (ID: i-0bf984bef5ff354d6)
  tags.%:           "0" => "3"
  tags.App:         "" => "ecommerce"
  tags.Environment: "" => "Production"
  tags.Name:        "" => "server-one"
aws_instance.server: Modifications complete after 3s (ID: i-0bf984bef5ff354d6)
Apply complete! Resources: 0 added, 1 changed, 0 destroyed.
```
It took **3s** for terraform to apply this change to the ec2 instance.
Source Code: https://github.com/iamtito/DevOps/tree/Terraform/Terraform/example1

>That's all folks. Feel free to point out any mistake, make some corrections, and contribute to this post in the comment section.


Next.👉Attaching An ElasticIP and DNS Record(Route53) using terraform🙃