DEV Community

Cover image for Understanding Terraform on AWS: Spin Up EC2 Instances
iamtito
iamtito

Posted on

Understanding Terraform on AWS: Spin Up EC2 Instances

Deploying a server
Is it really easy to deploy a server using terraform?
Part I - https://dev.to/iamtito/understanding-terraform-152k

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.


Viewing the state file🧐

{
    "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:

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🙃

Top comments (0)