Terraform





Getting Started 


Terraform is a tool for building, changing, and versioning infrastructure safely and efficiently. 
Terraform can manage existing and popular service providers as well as custom in-house solutions.

Terraform generates an execution plan describing what it will do to reach the desired state, and then executes it to build the described infrastructure. 
As the configuration changes, Terraform is able to determine what changed and create incremental execution plans which can be applied.


The key features of Terraform are: 

Infrastructure as Code: Infrastructure is described using a high-level configuration syntax. This allows a blueprint of your datacenter to be versioned and treated as you would any other code. Additionally, infrastructure can be shared and re-used. 

Execution Plans: Terraform has a "planning" step where it generates an execution plan. The execution plan shows what Terraform will do when you call apply. This lets you avoid any surprises when Terraform manipulates infrastructure. 

Resource Graph: Terraform builds a graph of all your resources, and parallelizes the creation and modification of any non-dependent resources. Because of this, Terraform builds infrastructure as efficiently as possible, and operators get insight into dependencies in their infrastructure. 

Change Automation: Complex changesets can be applied to your infrastructure with minimal human interaction. With the previously mentioned execution plan and resource graph, you know exactly what Terraform will change and in what order, avoiding many possible human errors.


Install Terraform


Download Terraform Binary for your platform
Copy the binary at a folder reachable from PATH environment variable.

Verify Installation

$ terraform
usage: terraform [--version] [--help] <command> [args]

The available commands for execution are listed below.
The most common, useful commands are shown first, followed by
less common or more advanced commands. If you're just getting
started with Terraform, stick with the common commands. For the
other commands, please read the help and docs before usage.

Common commands:
    apply              Builds or changes infrastructure
    destroy            Destroy Terraform-managed infrastructure
    fmt                Rewrites config files to canonical format
    get                Download and install modules for the configuration
    graph              Create a visual graph of Terraform resources
    import             Import existing infrastructure into Terraform
    init               Initializes Terraform configuration from a module
    output             Read an output from a state file
    plan               Generate and show an execution plan
    push               Upload this Terraform module to Atlas to run
    refresh            Update local state file against real resources
    remote             Configure remote state storage
    show               Inspect Terraform state or plan
    taint              Manually mark a resource for recreation
    untaint            Manually unmark a resource as tainted
    validate           Validates the Terraform files
    version            Prints the Terraform version

All other commands:
    state              Advanced state management





Providers


Terraform is used to create, manage, and manipulate infrastructure resources. Examples of resources include physical machines, VMs, network switches, containers, etc. Almost any infrastructure noun can be represented as a resource in Terraform.

Terraform is agnostic to the underlying platforms by supporting providers
A provider is responsible for understanding API interactions and exposing resources. 
Providers generally are an IaaS (e.g. AWS, GCP, Microsoft Azure, OpenStack), PaaS (e.g. Heroku), or SaaS services (e.g. Atlas, DNSimple, CloudFlare).


Configuration


The set of files used to describe infrastructure in Terraform is simply known as a Terraform configuration

Terraform uses text files to describe infrastructure and to set variables. These text files are called Terraform configurations and end in .tf. This section talks about the format of these files as well as how they're loaded.

The format of the configuration files are able to be in two formats: Terraform format and JSON. 
The Terraform format is more human-readable, supports comments, and is the generally recommended format for most Terraform files. 
The JSON format is meant for machines to create, modify, and update, but can also be done by Terraform operators if you prefer. 
Terraform format ends in .tf and JSON format ends in .tf.json.


Build Infrastructure


Terraform can manage many providers, including multiple providers in a single configuration. Some examples of this are in the use cases section.

If you don't have an AWS account, create one now. For the getting started guide, we'll only be using resources which qualify under the AWS free-tier, meaning it will be free.

Configuration

We're going to write our first configuration now to launch a single AWS EC2 instance
The format of the configuration files is documented here. Configuration files can also be JSON, but we recommend only using JSON when the configuration is generated by a machine.

Example: example.tf

provider "aws" {
  access_key = "ACCESS_KEY_HERE"
  secret_key = "SECRET_KEY_HERE"
  region     = "us-east-1"
}

resource "aws_instance" "example" {
  ami           = "ami-0d729a60"
  instance_type = "t2.micro"
}

Note: The above configuration is designed to work on most EC2 accounts, with access to a default VPC. For EC2 Classic users, please use t1.micro for instance_type, and ami-408c7f28 for the ami.

Replace the ACCESS_KEY_HERE and SECRET_KEY_HERE with your AWS access key and secret key, available from this page


provider "aws" {
  access_key = "ACCESS_KEY_HERE"
  secret_key = "SECRET_KEY_HERE"
  region     = "us-east-1"
}

resource "aws_instance" "example" {
  ami           = "ami-0d729a60"
  instance_type = "t2.micro"
}


This is a complete configuration that Terraform is ready to apply. The general structure should be intuitive and straightforward.

The provider block is used to configure the named provider, in our case "aws." A provider is responsible for creating and managing resources. Multiple provider blocks can exist if a Terraform configuration is composed of multiple providers, which is a common situation. 

The resource block defines a resource that exists within the infrastructure. A resource might be a physical component such as an EC2 instance, or it can be a logical resource such as a Heroku application. 

The resource block has two strings before opening the block: the resource type and the resource name. In our example, the resource type is "aws_instance" and the name is "example." The prefix of the type maps to the provider. 
In our case "aws_instance" automatically tells Terraform that it is managed by the "aws" provider. 

Within the resource block itself is configuration for that resource. This is dependent on each resource provider and is fully documented within our providers reference. For our EC2 instance, we specify an AMI for Ubuntu, and request a "t2.micro" instance so we qualify under the free tier.


Execution Plan


Next, let's see what Terraform would do if we asked it to apply this configuration. 
In the same directory as the example.tf file you created, run terraform plan. 
You should see output similar to what is copied below. We've truncated some of the output to save space.

$ 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.


The Terraform execution plan has been generated and is shown below.
Resources are shown in alphabetical order for quick scanning. Green resources
will be created (or destroyed and then created if an existing resource
exists), yellow resources are being changed in-place, and red resources
will be destroyed. Cyan entries are data sources to be read.

Note: You didn't specify an "-out" parameter to save this plan, so when
"apply" is called, Terraform can't guarantee this is what will execute.

+ aws_instance.example
    ami:                      "ami-0d729a60"
    availability_zone:        "<computed>"
    ebs_block_device.#:       "<computed>"
    ephemeral_block_device.#: "<computed>"
    instance_state:           "<computed>"
    instance_type:            "t2.micro"
    key_name:                 "<computed>"
    network_interface_id:     "<computed>"
    placement_group:          "<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>"
    vpc_security_group_ids.#: "<computed>"


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


terraform plan shows what changes Terraform will apply to your infrastructure given the current state of your infrastructure as well as the current contents of your configuration. 

If terraform plan failed with an error, read the error message and fix the error that occurred. At this stage, it is probably a syntax error in the configuration. The output format is similar to the diff format generated by tools such as Git. 

The output has a "+" next to "aws_instance.example", meaning that Terraform will create this resource. Beneath that, it shows the attributes that will be set. When the value displayed is <computed>, it means that the value won't be known until the resource is created.


Apply


The plan looks good, our configuration appears valid, so it's time to create real resources. Run terraform apply in the same directory as your example.tf, and watch it go! It will take a few minutes since Terraform waits for the EC2 instance to become available.


$ terraform apply
aws_instance.example: Creating...
  ami:                      "" => "ami-0d729a60"
  availability_zone:        "" => "<computed>"
  ebs_block_device.#:       "" => "<computed>"
  ephemeral_block_device.#: "" => "<computed>"
  instance_state:           "" => "<computed>"
  instance_type:            "" => "t2.micro"
  key_name:                 "" => "<computed>"
  network_interface_id:     "" => "<computed>"
  placement_group:          "" => "<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>"
  vpc_security_group_ids.#: "" => "<computed>"
aws_instance.example: Still creating... (10s elapsed)
aws_instance.example: Still creating... (20s elapsed)
aws_instance.example: Still creating... (30s elapsed)
aws_instance.example: Creation complete

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

The state of your infrastructure has been saved to the path
below. This state is required to modify and destroy your
infrastructure, so keep it safe. To inspect the complete state
use the `terraform show` command.

State path: terraform.tfstate


Done! Check the instance from AWS console




AWS console snapshot


Terraform also puts some state into the terraform.tfstate file by default. This state file is extremely important; it maps various resource metadata to actual resource IDs so that Terraform knows what it is managing. This file must be saved and distributed to anyone who might run Terraform. We recommend simply putting it into version control, since it generally isn't too large. 

You can inspect the state using terraform show:

$ terraform show
aws_instance.example:
  id = i-0bda2366d74f6005b
  ami = ami-0d729a60
  availability_zone = us-east-1d
  disable_api_termination = false
  ebs_block_device.# = 0
  ebs_optimized = false
  ephemeral_block_device.# = 0
  iam_instance_profile =
  instance_state = running
  instance_type = t2.micro
  key_name =
  monitoring = false
  network_interface_id = eni-ebeef52a
  private_dns = ip-172-31-28-160.ec2.internal
  private_ip = 172.31.28.160
  public_dns = ec2-107-23-6-129.compute-1.amazonaws.com
  public_ip = 107.23.6.129
  root_block_device.# = 1
  root_block_device.0.delete_on_termination = true
  root_block_device.0.iops = 0
  root_block_device.0.volume_size = 8
  root_block_device.0.volume_type = standard
  security_groups.# = 0
  source_dest_check = true
  subnet_id = subnet-7ed56c25
  tags.% = 0
  tenancy = default
  vpc_security_group_ids.# = 1
  vpc_security_group_ids.2439997441 = sg-e2f0959f


You can see that by creating our resource, we've also gathered a lot more metadata about it. This metadata can actually be referenced for other resources or outputs.



Provisioning


The EC2 instance we launched at this point is based on the AMI given, but has no additional software installed. 
If you're running an image-based infrastructure (perhaps creating images with Packer), then this is all you need. 
However, many infrastructures still require some sort of initialization or software provisioning step. Terraform supports provisioners.


Congratulations! 
You've built your first infrastructure with Terraform. You've seen the configuration syntax, an example of a basic execution plan, and understand the state file.



Change Infrastructure



In this section, we're going to modify that resource, and see how Terraform handles change. 

Infrastructure is continuously evolving, and Terraform was built to help manage and enact that change. As you change Terraform configurations, Terraform builds an execution plan that only modifies what is necessary to reach your desired state. 

By using Terraform to change infrastructure, you can version control not only your configurations but also your state so you can see how the infrastructure evolved over time.


Configuration


Let's modify the ami of our instance. Edit the "aws_instance.example" resource in your configuration and change it to the following:

resource "aws_instance" "example" {
  ami           = "ami-656be372"
  instance_type = "t1.micro"
}


Note: EC2 Classic users please use AMI ami-656be372 and type t1.micro

We've changed the AMI from being an Ubuntu 14.04 LTS AMI to being an Ubuntu 16.04 LTS AMI. 
Terraform configurations are meant to be changed like this. 
You can also completely remove resources and Terraform will know to destroy the old one.



Execution Plan


Let's see what Terraform will do with the change we made.


$ 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.example: Refreshing state... (ID: i-0bda2366d74f6005b)

The Terraform execution plan has been generated and is shown below.
Resources are shown in alphabetical order for quick scanning. Green resources
will be created (or destroyed and then created if an existing resource
exists), yellow resources are being changed in-place, and red resources
will be destroyed. Cyan entries are data sources to be read.

Note: You didn't specify an "-out" parameter to save this plan, so when
"apply" is called, Terraform can't guarantee this is what will execute.

-/+ aws_instance.example
    ami:                      "ami-0d729a60" => "ami-656be372" (forces new resource)
    availability_zone:        "us-east-1d" => "<computed>"
    ebs_block_device.#:       "0" => "<computed>"
    ephemeral_block_device.#: "0" => "<computed>"
    instance_state:           "running" => "<computed>"
    instance_type:            "t2.micro" => "t1.micro" (forces new resource)
    key_name:                 "" => "<computed>"
    network_interface_id:     "eni-ebeef52a" => "<computed>"
    placement_group:          "" => "<computed>"
    private_dns:              "ip-172-31-28-160.ec2.internal" => "<computed>"
    private_ip:               "172.31.28.160" => "<computed>"
    public_dns:               "ec2-107-23-6-129.compute-1.amazonaws.com" => "<computed>"
    public_ip:                "107.23.6.129" => "<computed>"
    root_block_device.#:      "1" => "<computed>"
    security_groups.#:        "0" => "<computed>"
    source_dest_check:        "true" => "true"
    subnet_id:                "subnet-7ed56c25" => "<computed>"
    tenancy:                  "default" => "<computed>"
    vpc_security_group_ids.#: "1" => "<computed>"


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



The prefix "-/+" means that Terraform will destroy and recreate the resource, versus purely updating it in-place. While some attributes can do in-place updates (which are shown with a "~" prefix), AMI changing on EC2 instance requires a new resource. Terraform handles these details for you, and the execution plan makes it clear what Terraform will do.

Additionally, the plan output shows that the AMI change is what necessitated the creation of a new resource. Using this information, you can tweak your changes to possibly avoid destroy/create updates if you didn't want to do them at this time.


Apply


From the plan, we know what will happen. Let's apply and enact the change.


$ terraform apply
aws_instance.example: Refreshing state... (ID: i-0bda2366d74f6005b)
aws_instance.example: Destroying...
aws_instance.example: Still destroying... (10s elapsed)
aws_instance.example: Still destroying... (20s elapsed)
aws_instance.example: Still destroying... (30s elapsed)
aws_instance.example: Destruction complete
aws_instance.example: Creating...
  ami:                      "" => "ami-656be372"
  availability_zone:        "" => "<computed>"
  ebs_block_device.#:       "" => "<computed>"
  ephemeral_block_device.#: "" => "<computed>"
  instance_state:           "" => "<computed>"
  instance_type:            "" => "t1.micro"
  key_name:                 "" => "<computed>"
  network_interface_id:     "" => "<computed>"
  placement_group:          "" => "<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>"
  vpc_security_group_ids.#: "" => "<computed>"
aws_instance.example: Still creating... (7s elapsed)
aws_instance.example: Still creating... (10s elapsed)
aws_instance.example: Still creating... (17s elapsed)
aws_instance.example: Still creating... (20s elapsed)
aws_instance.example: Still creating... (27s elapsed)
aws_instance.example: Still creating... (30s elapsed)
aws_instance.example: Still creating... (37s elapsed)
aws_instance.example: Creation complete

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

The state of your infrastructure has been saved to the path
below. This state is required to modify and destroy your
infrastructure, so keep it safe. To inspect the complete state
use the `terraform show` command.

State path: terraform.tfstate


As the plan predicted, Terraform started by destroying our old instance, then creating the new one. 

You can use terraform show again to see the new properties associated with this instance.

$ terraform show
aws_instance.example:
  id = i-0734bf7c1cabf83b0
  ami = ami-656be372
  availability_zone = us-east-1b
  disable_api_termination = false
  ebs_block_device.# = 0
  ebs_optimized = false
  ephemeral_block_device.# = 0
  iam_instance_profile =
  instance_state = running
  instance_type = t1.micro
  key_name =
  monitoring = false
  network_interface_id = eni-5f9068a1
  private_dns = ip-172-31-62-118.ec2.internal
  private_ip = 172.31.62.118
  public_dns = ec2-54-221-4-107.compute-1.amazonaws.com
  public_ip = 54.221.4.107
  root_block_device.# = 1
  root_block_device.0.delete_on_termination = true
  root_block_device.0.iops = 100
  root_block_device.0.volume_size = 8
  root_block_device.0.volume_type = gp2
  security_groups.# = 0
  source_dest_check = true
  subnet_id = subnet-38da5e15
  tags.% = 0
  tenancy = default
  vpc_security_group_ids.# = 1
  vpc_security_group_ids.2439997441 = sg-e2f0959f



Destroy Infrastructure


Destroying your infrastructure is a rare event in production environments. 
But if you're using Terraform to spin up multiple environments such as development, test, QA environments, then destroying is a useful action.

Plan


Before destroying our infrastructure, we can use the plan command to see what resources Terraform will destroy.

$ terraform plan -destroy
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.example: Refreshing state... (ID: i-0734bf7c1cabf83b0)

The Terraform execution plan has been generated and is shown below.
Resources are shown in alphabetical order for quick scanning. Green resources
will be created (or destroyed and then created if an existing resource
exists), yellow resources are being changed in-place, and red resources
will be destroyed. Cyan entries are data sources to be read.

Note: You didn't specify an "-out" parameter to save this plan, so when
"apply" is called, Terraform can't guarantee this is what will execute.

- aws_instance.example


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


With the -destroy flag, we're asking Terraform to plan a destroy, where all resources under Terraform management are destroyed. 
You can use this output to verify exactly what resources Terraform is managing and will destroy.


Destroy


Let's destroy the infrastructure now:

$ terraform destroy
Do you really want to destroy?
  Terraform will delete all your managed infrastructure.
  There is no undo. Only 'yes' will be accepted to confirm.

  Enter a value: yes

aws_instance.example: Refreshing state... (ID: i-0734bf7c1cabf83b0)
aws_instance.example: Destroying...
aws_instance.example: Still destroying... (10s elapsed)
aws_instance.example: Still destroying... (20s elapsed)
aws_instance.example: Still destroying... (30s elapsed)
aws_instance.example: Still destroying... (40s elapsed)
aws_instance.example: Still destroying... (50s elapsed)
aws_instance.example: Still destroying... (1m0s elapsed)
aws_instance.example: Destruction complete

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

The terraform destroy command should ask you to verify that you really want to destroy the infrastructure. Terraform only accepts the literal "yes" as an answer as a safety mechanism. Once entered, Terraform will go through and destroy the infrastructure.

Just like with apply, Terraform is smart enough to determine what order things should be destroyed. In our case, we only had one resource, so there wasn't any ordering necessary. But in more complicated cases with multiple resources, Terraform will destroy in the proper order.

The terraform show will give empty result


Terraform Vs Others


Chef, Puppet, Ansible Playbook etc 


Configuration management tools install and manage software on a machine that already exists. 
Terraform is not a configuration management tool, and it allows existing tooling to focus on their strengths: bootstrapping and initializing resources.

Using provisioners, Terraform enables any configuration management tool to be used to setup a resource once it has been created. 
Terraform focuses on the higher-level abstraction of the datacenter and associated services, without sacrificing the ability to use configuration management tools to do what they do best. It also embraces the same codification that is responsible for the success of those tools, making entire infrastructure deployments easy and reliable.


Cloudformation, Heat etc


Tools like CloudFormation, Heat, etc. allow the details of an infrastructure to be codified into a configuration file. 
The configuration files allow the infrastructure to be elastically created, modified and destroyed. 
Terraform is inspired by the problems they solve.

Terraform similarly uses configuration files to detail the infrastructure setup, but it goes further by being both cloud-agnostic and enabling multiple providers and services to be combined and composed. 
For example, Terraform can be used to orchestrate an AWS and OpenStack cluster simultaneously, while enabling 3rd-party providers like CloudFlare and DNSimple to be integrated to provide CDN and DNS services. 
This enables Terraform to represent and manage the entire infrastructure with its supporting services, instead of only the subset that exists within a single provider. 
It provides a single unified syntax, instead of requiring operators to use independent and non-inter-operable tools for each platform and service.

Terraform also separates the planning phase from the execution phase, by using the concept of an execution plan. 
By running terraform plan, the current state is refreshed and the configuration is consulted to generate an action plan. 
The plan includes all actions to be taken: which resources will be created, destroyed or modified. It can be inspected by operators to ensure it is exactly what is expected. Using terraform graph, the plan can be visualized to show dependent ordering. 
Once the plan is captured, the execution phase can be limited to only the actions in the plan. 
Other tools combine the planning and execution phases, meaning operators are forced to mentally reason about the effects of a change, which quickly becomes intractable in large infrastructures. 
Terraform lets operators apply changes with confidence, as they know exactly what will happen beforehand.

Support for AWS Resources is high in Cloudformation compared to Terraform. This might be one reason to consider Cloudformation based on your use-case.

State management is done by state file in Terraform, making multi developer to share the state file and also manual changes go un-noticed
Cloudformation does not have state management and manual changes go into consideration.






Comments