Day 25–101 Days of DevOps — Introduction to Terraform — Part 1

Prashant Lakhera
12 min readJul 30, 2021

--

Welcome to Day 25 of 101 Days of DevOps. The topic for today is Terraform. Today, we will start with an introduction to terraform and bare minimum code, but you will learn how to enhance it next few days.

To view the complete course, please check the below url.

For more info, register via the below link

YouTube Channel link

What is terraform?

Terraform is a tool for provisioning infrastructure(or managing Infrastructure as Code). It supports multiple providers(e.g., AWS, Google Cloud, Azure, OpenStack..).

https://registry.terraform.io/browse/providers

Installing Terraform

Installing Terraform is pretty straightforward; download it from Terraform download page and select the appropriate package based on your operating system.

https://learn.hashicorp.com/terraform/getting-started/install.html

https://www.terraform.io/downloads.html

I am using Centos7, so these are the steps I need to follow to install terraform on Centos7.

Step1: Download the Packagewget https://releases.hashicorp.com/terraform/1.0.3/terraform_1.0.3_linux_amd64.zip--2021-07-29 23:56:10--  https://releases.hashicorp.com/terraform/1.0.3/terraform_1.0.3_linux_amd64.zipResolving releases.hashicorp.com (releases.hashicorp.com)... 2a04:4e42:d::439, 151.101.53.183Connecting to releases.hashicorp.com (releases.hashicorp.com)|2a04:4e42:d::439|:443... connected.HTTP request sent, awaiting response... 200 OKLength: 32406882 (31M) [application/zip]Saving to: ‘terraform_1.0.3_linux_amd64.zip’100%[==================================================================================================================================================================>] 32,406,882   101MB/s   in 0.3s2021-07-29 23:56:10 (101 MB/s) - ‘terraform_1.0.3_linux_amd64.zip’ saved [32406882/32406882]Step2: Unzip itunzip terraform_1.0.3_linux_amd64.zipArchive:  terraform_1.0.3_linux_amd64.zipinflating: terraformStep3: Add the binary to PATH environment variable
sudo cp terraform /usr/local/bin/
sudo chmod +x /usr/local/bin/terraform
Step4: Logout and log back in
  • To verify terraform is installed properly.
$ terraform versionTerraform v1.0.3on linux_amd64

As mentioned above, terraform supports many providers; for my use case, I am using AWS.

Prerequisites1: Existing AWS Account(OR Setup a new account)
2: IAM full access(OR at least have AmazonEC2FullAccess)
3: AWS Credentials(AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY)

Once you have pre-requisites 1 and 2 done, the first step is to export Keys AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY.

export AWS_ACCESS_KEY_ID="your access key id here"
export AWS_SECRET_ACCESS_KEY="your secret access key id here"

NOTE: These two variables are bound to your current shell, in case of a reboot, or if you open a new shell window, these changes will be lost

With all prerequisites in place, it’s time to write your first terraform code, but before that, just a brief overview of terraform language.

  • Terraform code is written in the HashiCorp Configuration Language(HCL)
  • All the code ends with the extension of .tf
  • It’s a declarative language(We need to define what infrastructure we want, and terraform will figure out how to create it)

In this first example, I am going to build an EC2 instance, but before creating an EC2 instance go to the AWS console and start with bare minimum what are the parameters we need to build EC2 instance are

  • Amazon Machine Image(AMI)
  • Instance Type

Let break each of these steps by step

  • Amazon Machine Image(AMI): It’s an Operating System Image used to run EC2 instances. For this example, I am using Centos 7 ami-01ed306a12b7d1c96. We can create our own AMI using AWS console or Packer.

https://www.packer.io/intro/

Another way to find out the AMI id for Centos7 Image is

aws ec2 describe-images \--owners aws-marketplace \--filters Name=product-code,Values=aw0evgkw8e5c1q413zgy5pjce \--query 'Images[*].[CreationDate,Name,ImageId]' \--filters "Name=name,Values=CentOS Linux 7*" \--region us-west-2 \--output table \| sort -r|  2020-03-09T21:54:48.000Z|  CentOS Linux 7 x86_64 HVM EBS ENA 2002_01-b7ee8a69-ee97-4a49-9e68-afaee216db2e-ami-0042af67f8e4dcc20.4  |  ami-0bc06212a56393ee1  ||  2019-01-30T23:43:37.000Z|  CentOS Linux 7 x86_64 HVM EBS ENA 1901_01-b7ee8a69-ee97-4a49-9e68-afaee216db2e-ami-05713873c6794f575.4  |  ami-01ed306a12b7d1c96  ||  2018-06-13T15:58:14.000Z|  CentOS Linux 7 x86_64 HVM EBS ENA 1805_01-b7ee8a69-ee97-4a49-9e68-afaee216db2e-ami-77ec9308.4           |  ami-3ecc8f46           ||  2018-05-17T09:30:44.000Z|  CentOS Linux 7 x86_64 HVM EBS ENA 1804_2-b7ee8a69-ee97-4a49-9e68-afaee216db2e-ami-55a2322a.4            |  ami-5490ed2c           ||  2018-04-04T00:11:39.000Z|  CentOS Linux 7 x86_64 HVM EBS ENA 1803_01-b7ee8a69-ee97-4a49-9e68-afaee216db2e-ami-8274d6ff.4           |  ami-0ebdd976           ||  2017-12-05T14:49:18.000Z|  CentOS Linux 7 x86_64 HVM EBS 1708_11.01-b7ee8a69-ee97-4a49-9e68-afaee216db2e-ami-95096eef.4            |  ami-b63ae0ce           ||                                                                        DescribeImages                                                                         |-----------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------------------+----------------------------------------------------------------------------------------------------------+-------------------------++--------------------------+----------------------------------------------------------------------------------------------------------+-------------------------+

For more info, please refer to AWS official doc

https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/AMIs.html

  • Instance Type: Type of EC2 instance to run, as every instance type provides different capabilities(CPU, Memory, I/O). For this example, I am using t2.micro(1 Virtual CPU, 1GB Memory)

For more info, please refer to the Amazon EC2 instance type

We will first start with the provider block. We need to tell terraform which provider we are using(aws in this case).

  • This tells terraform that you will use AWS as a provider, and you want to deploy your infrastructure in the us-west-2 region.
  • AWS has data centers all over the world, which is grouped into region and availability zones. Region is a separate geographic area(Oregon, Virginia, Sydney) and each region has multiple isolated datacenters(us-west-2a,us-west-2b..)
provider "aws" {
region = "us-west-2"
}

For more info about regions and availability zones, please refer to the below doc.

resource "aws_instance" "mytest" {   ami = "ami-0bc06212a56393ee1"   instance_type = "t2.micro"}
  • The first command we are going to run to setup our instance is terraform init; what this will do is download code for a provider(aws) that we are going to use.
  • Terraform binary contains the basic functionality for terraform. Still, it doesn’t come with the code for any of the providers(e.g., AWS, Azure, and GCP), so when we are first starting to use terraform, we need to run terraform init to tell terraform to scan the code and figure out which providers we are using and download the code for them.
  • By default, the provider code will be downloaded into a .terraform directory, a scratch directory(we may want to add it in a .gitignore).

NOTE: It’s safe to run terraform init command multiple times as it’s idempotent.

$ terraform initInitializing the backend...Initializing provider plugins...- Finding latest version of hashicorp/aws...- Installing hashicorp/aws v3.52.0...- Installed hashicorp/aws v3.52.0 (signed by HashiCorp)Terraform has created a lock file .terraform.lock.hcl to record the providerselections it made above. Include this file in your version control repositoryso that Terraform can guarantee to make the same selections by default whenyou run "terraform init" in the future.Terraform has been successfully initialized!You may now begin working with Terraform. Try running "terraform plan" to seeany changes that are required for your infrastructure. All Terraform commandsshould 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, othercommands will detect it and remind you to do so if necessary.

Before running terraform command to spun our first EC2 instance, run terraform fmt command, which will rewrite terraform configuration files to a canonical format and style

$ terraform fmtmain.tf
  • The next command we are going to run is “terraform plan,” this will tell what terraform actually do before making any changes
  • This is a good way of making any sanity check before making actual changes to env
  • The output of terraform plan command looks similar to the Linux diff command

1: (+ sign): Resource going to be created

2: (- sign): Resources going to be deleted

3: (~ sign): Resource going to be modified

terraform planTerraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:+ createTerraform will perform the following actions:# aws_instance.mytest will be created+ resource "aws_instance" "mytest" {+ ami                                  = "ami-0bc06212a56393ee1"+ 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)+ 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_all                             = (known after apply)+ 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)}+ 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.
  • To apply these changes, run terraform apply
terraform applyTerraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:+ createTerraform will perform the following actions:# aws_instance.mytest will be created+ resource "aws_instance" "mytest" {+ ami                                  = "ami-0bc06212a56393ee1"+ 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)+ 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_all                             = (known after apply)+ 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)}+ 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.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_instance.mytest: Creating...aws_instance.mytest: Still creating... [10s elapsed]aws_instance.mytest: Still creating... [20s elapsed]aws_instance.mytest: Still creating... [30s elapsed]aws_instance.mytest: Creation complete after 31s [id=i-05bc9f1859dee639e]Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
  • What terraform is doing here is reading code and translating it to API calls to providers(aws in this case)

W00t you have deployed your first EC2 server using terraform

Go back to the EC2 console to verify your first deployed server

  • Now, if we can think about it, how does terraform knows that there is only a change in the tag parameter and nothing else
  • Terraform keeps track of all the resources it already created in .tfstate files, so it's aware of the already existing resources.
ls -al terraform.tfstate-rw-rw-r--. 1 centos centos 3691 Jul 30 00:24 terraform.tfstate
  • To Perform clean up whatever we have created so far, run terraform destroy
terraform destroyaws_instance.mytest: Refreshing state... [id=i-05bc9f1859dee639e]Note: Objects have changed outside of TerraformTerraform detected the following changes made outside of Terraform since the last "terraform apply":# aws_instance.mytest has been changed~ resource "aws_instance" "mytest" {id                                   = "i-05bc9f1859dee639e"+ tags                                 = {}# (28 unchanged attributes hidden)# (5 unchanged blocks hidden)}Unless you have made equivalent changes to your configuration, or ignored the relevant attributes using ignore_changes, the following plan may include actions to undo or respond to these changes.───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:- destroyTerraform will perform the following actions:# aws_instance.mytest will be destroyed- resource "aws_instance" "mytest" {- ami                                  = "ami-0bc06212a56393ee1" -> null- arn                                  = "arn:aws:ec2:us-west-2:892515485494:instance/i-05bc9f1859dee639e" -> null- associate_public_ip_address          = true -> null- availability_zone                    = "us-west-2a" -> null- cpu_core_count                       = 1 -> null- cpu_threads_per_core                 = 1 -> null- disable_api_termination              = false -> null- ebs_optimized                        = false -> null- get_password_data                    = false -> null- hibernation                          = false -> null- id                                   = "i-05bc9f1859dee639e" -> null- instance_initiated_shutdown_behavior = "stop" -> null- instance_state                       = "running" -> null- instance_type                        = "t2.micro" -> null- ipv6_address_count                   = 0 -> null- ipv6_addresses                       = [] -> null- monitoring                           = false -> null- primary_network_interface_id         = "eni-012d48f353a4f21c8" -> null- private_dns                          = "ip-172-31-17-18.us-west-2.compute.internal" -> null- private_ip                           = "172.31.17.18" -> null- public_dns                           = "ec2-54-186-147-29.us-west-2.compute.amazonaws.com" -> null- public_ip                            = "54.186.147.29" -> null- secondary_private_ips                = [] -> null- security_groups                      = [- "default",] -> null- source_dest_check                    = true -> null- subnet_id                            = "subnet-fb9d1383" -> null- tags                                 = {} -> null- tags_all                             = {} -> null- tenancy                              = "default" -> null- vpc_security_group_ids               = [- "sg-f151d8c3",] -> null- capacity_reservation_specification {- capacity_reservation_preference = "open" -> null}- credit_specification {- cpu_credits = "standard" -> null}- enclave_options {- enabled = false -> null}- metadata_options {- http_endpoint               = "enabled" -> null- http_put_response_hop_limit = 1 -> null- http_tokens                 = "optional" -> null}- root_block_device {- delete_on_termination = false -> null- device_name           = "/dev/sda1" -> null- encrypted             = false -> null- iops                  = 100 -> null- tags                  = {} -> null- throughput            = 0 -> null- volume_id             = "vol-093bef4a995646417" -> null- volume_size           = 8 -> null- volume_type           = "gp2" -> null}}Plan: 0 to add, 0 to change, 1 to destroy.Do you really want to destroy all resources?Terraform will destroy all your managed infrastructure, as shown above.There is no undo. Only 'yes' will be accepted to confirm.Enter a value: yesaws_instance.mytest: Destroying... [id=i-05bc9f1859dee639e]aws_instance.mytest: Still destroying... [id=i-05bc9f1859dee639e, 10s elapsed]aws_instance.mytest: Still destroying... [id=i-05bc9f1859dee639e, 20s elapsed]aws_instance.mytest: Destruction complete after 30sDestroy complete! Resources: 1 destroyed.

If you like to dig deeper into the AWS concept, please feel free to check my book.

Looking forward to you guys joining this journey and spend a minimum of an hour every day for the next 101 days on DevOps work and post your progress using any of the below mediums.

--

--

Prashant Lakhera
Prashant Lakhera

Written by Prashant Lakhera

AWS Community Builder, Ex-Redhat, Author, Blogger, YouTuber, RHCA, RHCDS, RHCE, Docker Certified,4XAWS, CCNA, MCP, Certified Jenkins, Terraform Certified, 1XGCP

No responses yet