Terraform: Installation & Basic Usage
Tags: terraform, cloud, infrastructureascode, devops
Published: 2022-06-07 05:46:45


# Terraform’s basics: basic usage

## Installation
You have read through 2 long articles that explained why Terraform is a good tool and what problems it can solve. Now, let's start using it!

In this article we will cover installation and our first configuration deployment. It will be just one EC2 instance, but the goal is to show you how it works. 

### ============== 
####Prerequisites
1. Installation instructions for [Terraform](https://learn.hashicorp.com/tutorials/terraform/install-cli).
2. Installation instructions for [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html)
3. [AWS account](https://aws.amazon.com/free/) and [credentials](https://docs.aws.amazon.com/general/latest/gr/aws-sec-cred-types.html#access-keys-and-secret-access-keys) that would allow Terraform to manage resources. For this part we are interested in IAM User with programmatic access. We will require `AWS Access Key` and `AWS Secret Access Key`. 

Once you installed Terraform, AWS CLI, and have your AWS credentials on hand we would need to add access keys environment variables. Your key will go between double-quotes, without the carets.

```bash
$ export AWS_ACCESS_KEY_ID="<YOUR_AWS_ACCESS_KEY_ID>"
```

```bash
$ export AWS_SECRET_ACCESS_KEY="<YOUR_AWS_SECRET_ACCESS_KEY>"
```
After all these steps we can start using Terraform to manage our resources!

### ============== 


### Deploying our first infrastructure

We will start with a deploy of one EC2 instance. This will introduce you to the workflow of Terraform and basic structure of configuration files.

Enter these commands in the terminal to create a directory for our demo project and go into it:
```bash
# Create folder with the name tf-demo
$ mkdir tf-demo
```
```bash
# Change into tf-demo folder
$ cd tf-demo
```

### Creating our first configuration

Now that we are in our project's folder we need to create a `main.tf` file that will hold our configuration:
```bash
$ touch main.tf
```

Open `main.tf` file with you favourite editor and paste this code:
```terraform
# This block stores terraform settings
terraform {
  # We can define providers such as AWS, Azure, etc.
  # in this block along with their version and
  # where terraform should download them from.
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 3.27"
    }
  }
  # This is the version of Terraform we will use
  required_version = ">= 0.14.9"
}

# These are provider-specific settings
provider "aws" {
  profile = "default"
  region  = "us-west-2"
}

resource "aws_instance" "app_server" {
  ami           = "ami-830c94e3"
  instance_type = "t2.micro"

  tags = {
    Name = "PathToTerraformCertInstance"
  }
}
```

Next we will initialize Terraform in our folder:
```
$ terraform init
# the output should contain this
Terraform has been successfully initialized!
```


If the code indentation is wrong, do not worry! Terraform provides a convenient command `fmt` that formats the code all nice and tidy. Let's try it:
```
$ terraform fmt
```

Next step would be to check our configuration for syntactical errors. This is done using a `validate` command:
```
$ terraform validate
# You should get this output
Success! The configuration is valid.
```

Before we deploy any resources it is a good idea to see what Terraform intends to do with the configuration we wrote. We will do this using `plan` command:
```
$ terraform plan
# output
Terraform used the selected providers to generate the following
execution plan. Resource actions are indicated with the following
symbols:
  + create

Terraform will perform the following actions:

  # aws_instance.app_server will be created
  + resource "aws_instance" "app_server" {
      + ami                                  = "ami-830c94e3"
      + 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)
      + disable_api_termination              = (known after apply)
      + ebs_optimized                        = (known after apply)
      + get_password_data                    = false
      + host_id                              = (known after apply)
      + id                                   = (known after apply)
      + instance_initiated_shutdown_behavior = (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                             = (known after apply)
      + monitoring                           = (known after apply)
      + outpost_arn                          = (known after apply)
      + password_data                        = (known after apply)
      + placement_group                      = (known after apply)
      + placement_partition_number           = (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)
      + secondary_private_ips                = (known after apply)
      + security_groups                      = (known after apply)
      + source_dest_check                    = true
      + subnet_id                            = (known after apply)
      + tags                                 = {
          + "Name" = "PathToTerraformCertInstance"
        }
      + tags_all                             = {
          + "Name" = "PathToTerraformCertInstance"
        }
      + tenancy                              = (known after apply)
      + user_data                            = (known after apply)
      + user_data_base64                     = (known after apply)
      + vpc_security_group_ids               = (known after apply)

      + capacity_reservation_specification {
          + capacity_reservation_preference = (known after apply)

          + capacity_reservation_target {
              + capacity_reservation_id = (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)
          + tags                  = (known after apply)
          + throughput            = (known after apply)
          + volume_id             = (known after apply)
          + volume_size           = (known after apply)
          + volume_type           = (known after apply)
        }

      + enclave_options {
          + enabled = (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)
          + instance_metadata_tags      = (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)
          + tags                  = (known after apply)
          + throughput            = (known after apply)
          + volume_id             = (known after apply)
          + volume_size           = (known after apply)
          + volume_type           = (known after apply)
        }
    }

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

─────────────────────────────────────────────────────────────────

Note: You didn't use the -out option to save this plan, so
Terraform can't guarantee to take exactly these actions if you
run "terraform apply" now.
```
As you can see the output is pretty long, a thing to note is that we can output all this data to a file for analysis. 

Everything looks good. Let's deploy our first resource using Terraform! 
*You will be asked to approve deployment, you'd need to type `yes` to begin deployment process.
```
$ terraform apply
# output
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.app_server: Creating...
aws_instance.app_server: Still creating... [10s elapsed]
aws_instance.app_server: Still creating... [20s elapsed]
aws_instance.app_server: Still creating... [30s elapsed]
aws_instance.app_server: Still creating... [40s elapsed]
aws_instance.app_server: Creation complete after 45s [id=i-02bb0dc43fdd214d3]

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

Congratulations! You have just deployed your first resource using Infrastructure as Code!

We can check the state of Terraform-managed resources:
```
$ terraform state list
# output
aws_instance.app_server
```

Great! Now it is time to take our instance down. It is done using aptly-named command `destroy`, you will need to approve this command with `yes` when prompted:
```
$ terraform destroy
# output
Destroy complete! Resources: 1 destroyed.
```

### Conclusion

This was a brief introduction to Terraform. As you went through the commands you also learned about Terraform's workflow.

This workflow is as follows: `write` -> `plan` -> `create`. We write our configuration, we verify that Terraform will do what we want it to do, and lastly, we create our resources.


You did great following through all the way to the end! I understand that deploying a single instance may not seem all that exciting. Some may argue that it would be faster to go to AWS console and deploy this instance manually. Maybe it's true. The point of this article was to introduce reader to Terraform basics. 

Later on we will be deploying a much more complicated infrastructure with several EC2 instances, public and private subnets, S3 buckets, and bucket policies. We will see how we can leverage Terraform to automatically select active AZs (Availability Zones), search for the latest AMIs, name resources based on their location and purpose, create users and policies, and run bootstrap scripts on our EC2 instances. There is so much more that I want to write about.

Thank you for reading! See you in the next article where we will learn about HashiCorp Configuration Language (HCL)!