Skip to main content

Terraform

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.

Configuration files describe to Terraform the components needed to run a single application or your entire datacenter. 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 infrastructure Terraform can manage includes low-level components such as compute instances, storage, and networking, as well as high-level components such as DNS entries, SaaS features, etc.

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.


Why terraform:


Terraform provides a flexible abstraction of resources and providers. This model allows for representing everything from physical hardware, virtual machines, and containers, to email and DNS providers. Because of this flexibility, Terraform can be used to solve many different problems. This means there are a number of existing tools that overlap with the capabilities of Terraform. 

 

Multi-Tier Applications


 

A very common pattern is the N-tier architecture. The most common 2-tier architecture is a pool of web servers that use a database tier. Additional tiers get added for API servers, caching servers, routing meshes, etc. This pattern is used because the tiers can be scaled independently and provide a separation of concerns.

 

Terraform is an ideal tool for building and managing these infrastructures. Each tier can be described as a collection of resources, and the dependencies between each tier are handled automatically; Terraform will ensure the database tier is available before the web servers are started and that the load balancers are aware of the web nodes. Each tier can then be scaled easily using Terraform by modifying a single count configuration value. Because the creation and provisioning of a resource is codified and automated, elastically scaling with load becomes trivial.

 

Self-Service Clusters


At a certain organizational size, it becomes very challenging for a centralized operations team to manage a large and growing infrastructure. Instead it becomes more attractive to make "self-serve" infrastructure, allowing product teams to manage their own infrastructure using tooling provided by the central operations team.

Using Terraform, the knowledge of how to build and scale a service can be codified in a configuration. Terraform configurations can be shared within an organization enabling customer teams to use the configuration as a black box and use Terraform as a tool to manage their services.

Terraform basics:


Terraform must first be installed on your machine. Terraform is distributed as a binary package for all supported platforms and architecture. This page will not cover how to compile Terraform from source.

Installing Terraform


To install Terraform, find the appropriate package for your system and download it. Terraform is packaged as a zip archive.

After downloading Terraform, unzip the package into a directory where Terraform will be installed. The directory will contain a binary program terraform. The final step is to make sure the directory you installed Terraform to is on the PATH. See this page for instructions on setting the PATH on Linux and Mac. This page contains instructions for setting the PATH on Windows.

Example for Linux/Mac - Type the following into your terminal:
PATH=/usr/local/terraform/bin:/home/your-user-name/terraform:$PATH

Example for Windows - Type the following into Powershell:
[Environment]::SetEnvironmentVariable("PATH", $env:PATH + ({;C:\terraform},{C:\terraform})[$env:PATH[-1] -eq ';'], "User")

Verifying the Installation


After installing Terraform, verify the installation worked by opening a new terminal session and checking that terraform is available. By executingterraform you should see help output similar to that below:
$ terraform
Usage: terraform [--version] [--help] [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

If you get an error that terraform could not be found, then your PATH environment variable was not setup properly. Please go back and ensure that your PATH variable contains the directory where Terraform was installed.

Otherwise, Terraform is installed and ready to go!

 

Configuration


 

The set of files used to describe infrastructure in Terraform is simply known as a Terraform 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.

 

The entire configuration is shown below. We'll go over each part after. Save the contents to a file named example.tf. Verify that there are no other *.tf files in your directory, since Terraform loads all of them.

 
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"
}

Replace the ACCESS_KEY_HERE and SECRET_KEY_HERE with your AWS access key and secret key, available from this page. We're hardcoding them for now, but will extract these into variables later in the getting started guide.

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 ourproviders reference. For our EC2 instance, we specify an AMI for Ubuntu, and request a "t2.micro" instance so we qualify under the free tier.

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

+ aws_instance.example
ami: "ami-0d729a60"
availability_zone: ""
ebs_block_device.#: ""
ephemeral_block_device.#: ""
instance_state: ""
instance_type: "t2.micro"
key_name: ""
placement_group: ""
private_dns: ""
private_ip: ""
public_dns: ""
public_ip: ""
root_block_device.#: ""
security_groups.#: ""
source_dest_check: "true"
subnet_id: ""
tenancy: ""
vpc_security_group_ids.#: ""

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 , 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"
instance_type: "" => "t2.micro"
[...]

aws_instance.example: Still creating... (10s elapsed)
aws_instance.example: Creation complete

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

...

Done! You can go to the AWS console to prove to yourself that the EC2 instance has been created.

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. It is generally recommended to setup remote state when working with Terraform. This will mean that any potential secrets stored in the state file, will not be checked into version control

You can inspect the state using terraform show:
$ terraform show
aws_instance.example:
id = i-32cf65a8
ami = ami-0d729a60
availability_zone = us-east-1a
instance_state = running
instance_type = t2.micro
private_ip = 172.31.30.244
public_dns = ec2-52-90-212-55.compute-1.amazonaws.com
public_ip = 52.90.212.55
subnet_id = subnet-1497024d
vpc_security_group_ids.# = 1
vpc_security_group_ids.3348721628 = sg-67652003

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, which will be covered later in the getting started guide.

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.

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

- aws_instance.example

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
aws_instance.example: Destroying...

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.

Implicit and Explicit Dependencies


Most dependencies in Terraform are implicit: Terraform is able to infer dependencies based on usage of attributes of other resources.

Using this information, Terraform builds a graph of resources. This tells Terraform not only in what order to create resources, but also what resources can be created in parallel. In our example, since the IP address depended on the EC2 instance, they could not be created in parallel.

Implicit dependencies work well and are usually all you ever need. However, you can also specify explicit dependencies with the depends_on parameter which is available on any resource. For example, we could modify the "aws_eip" resource to the following, which effectively does the same thing and is redundant:
resource "aws_eip" "ip" {
instance = "${aws_instance.example.id}"
depends_on = ["aws_instance.example"]
}

If you're ever unsure about the dependency chain that Terraform is creating, you can use the terraform graph command to view the graph. This command outputs a dot-formatted graph which can be viewed with Graphviz.

Non-Dependent Resources


We can now augment the configuration with another EC2 instance. Because this doesn't rely on any other resource, it can be created in parallel to everything else.
resource "aws_instance" "another" {
ami = "ami-13be557e"
instance_type = "t2.micro"
}

You can view the graph with terraform graph to see that nothing depends on this and that it will likely be created in parallel.

Before moving on, remove this resource from your configuration and terraform apply again to destroy it. We won't use the second instance anymore in the getting started guide.

Defining Variables


Let's first extract our access key, secret key, and region into a few variables. Create another file variables.tf with the following contents.


Note: that the file can be named anything, since Terraform loads all files ending in .tf in a directory.


variable "access_key" {}
variable "secret_key" {}
variable "region" {
default = "us-east-1"
}

This defines three variables within your Terraform configuration. The first two have empty blocks {}. The third sets a default. If a default value is set, the variable is optional. Otherwise, the variable is required. If you run terraform plan now, Terraform will prompt you for the values for unset string variables.

Using Variables in Configuration


Next, replace the AWS provider configuration with the following:
provider "aws" {
access_key = "${var.access_key}"
secret_key = "${var.secret_key}"
region = "${var.region}"
}

This uses more interpolations, this time prefixed with var.. This tells Terraform that you're accessing variables. This configures the AWS provider with the given variables.

Assigning Variables


There are multiple ways to assign variables. Below is also the order in which variable values are chosen. The following is the descending order of precedence in which variables are considered.

Command-line flags


You can set variables directly on the command-line with the -var flag. Any command in Terraform that inspects the configuration accepts this flag, such as applyplan, and refresh:
$ terraform plan \
-var 'access_key=foo' \
-var 'secret_key=bar'
...

Once again, setting variables this way will not save them, and they'll have to be input repeatedly as commands are executed.

From a file


To persist variable values, create a file and assign variables within this file. Create a file named terraform.tfvars with the following contents:
access_key = "foo"
secret_key = "bar"

If a terraform.tfvars file is present in the current directory, Terraform automatically loads it to populate variables. If the file is named something else, you can use the -var-file flag directly to specify a file. These files are the same syntax as Terraform configuration files. And like Terraform configuration files, these files can also be JSON.

From environment variables


Terraform will read environment variables in the form of TF_VAR_name to find the value for a variable. For example, the TF_VAR_access_key variable can be set to set the access_key variable.

We don't recommend saving usernames and password to version control, But you can create a local secret variables file and use -var-file to load it.

You can use multiple -var-file arguments in a single command, with some checked in to version control and others not checked in. For example:
$ terraform plan \
-var-file="secret.tfvars" \
-var-file="production.tfvars"



Note: Environment variables can only populate string-type variables. List and map type variables must be populated via one of the other mechanisms.


Creating AWS resources:



  1. VPC:resource "aws_vpc" "main" { cidr_block = "10.0.0.0/16" }

  2. Internet Gateway
    resource "aws_internet_gateway" "gw" {
    vpc_id = "${aws_vpc.main.id}"

    tags {
    Name = "main"
    }
    }

    The following arguments are supported:

    • vpc_id - (Required) The VPC ID to create in.

    • tags - (Optional) A mapping of tags to assign to the resource.



  3. Security Group
    Basic usage
    resource "aws_security_group" "allow_all" {
    name = "allow_all"
    description = "Allow all inbound traffic"

    ingress {
    from_port = 0
    to_port = 0
    protocol = "-1"
    cidr_blocks = ["0.0.0.0/0"]
    }

    egress {
    from_port = 0
    to_port = 0
    protocol = "-1"
    cidr_blocks = ["0.0.0.0/0"]
    prefix_list_ids = ["pl-12c4e678"]
    }
    }



  4. Subnet


    variable "subnet_id" {}

    data "aws_subnet" "selected" {
    id = "${var.subnet_id}"
    }

    resource "aws_security_group" "subnet" {
    vpc_id = "${data.aws_subnet.selected.vpc_id}"

    ingress {
    cidr_blocks = ["${data.aws_subnet.selected.cidr_block}"]
    from_port = 80
    to_port = 80
    protocol = "tcp"
    }
    }



  5. Route Table

    variable "subnet_id" {}

    data "aws_route_table" "selected" {
    subnet_id = "${var.subnet_id}"
    }

    resource "aws_route" "route" {
    route_table_id = "${data.aws_route_table.selected.id}"
    destination_cidr_block = "10.0.1.0/22"
    vpc_peering_connection_id = "pcx-45ff3dc1"
    }



  6. Route table association

    resource "aws_route_table_association" "a" {
    subnet_id = "${aws_subnet.foo.id}"
    route_table_id = "${aws_route_table.bar.id}"
    }



  7. EC2 instance

    # Create a new instance of the latest Ubuntu 14.04 on an
    # t2.micro node with an AWS Tag naming it "HelloWorld"
    provider "aws" {
    region = "us-west-2"
    }

    data "aws_ami" "ubuntu" {
    most_recent = true
    filter {
    name = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-trusty-14.04-amd64-server-*"]
    }
    filter {
    name = "virtualization-type"
    values = ["hvm"]
    }
    owners = ["099720109477"] # Canonical
    }

    resource "aws_instance" "web" {
    ami = "${data.aws_ami.ubuntu.id}"
    instance_type = "t2.micro"
    tags {
    Name = "HelloWorld"
    }
    }



  8. EC2 instance elastic IP

    resource "aws_eip" "lb" {
    instance = "${aws_instance.web.id}"
    vpc = true
    }



  9. Provisioner without bastion

    Many provisioners require access to the remote resource. For example, a provisioner may need to use SSH or WinRM to connect to the resource.

    Terraform uses a number of defaults when connecting to a resource, but these can be overridden using a connection block in either a resource or provisioner. Any connection information provided in a resource will apply to all the provisioners, but it can be scoped to a single provisioner as well. One use case is to have an initial provisioner connect as the root user to setup user accounts, and have subsequent provisioners connect as a user with more limited permissions.
    # Copies the file as the root user using SSH
    provisioner "file" {
    source = "conf/myapp.conf"
    destination = "/etc/myapp.conf"
    connection {
    type = "ssh"
    user = "root"
    password = "${var.root_password}"
    }
    }

    # Copies the file as the Administrator user using WinRM
    provisioner "file" {
    source = "conf/myapp.conf"
    destination = "C:/App/myapp.conf"
    connection {
    type = "winrm"
    user = "Administrator"
    password = "${var.admin_password}"
    }
    }


  10. Provisioner with bastion:provisioner "file" {
    source = "configure.sh"
    destination = "/tmp/configure.sh"
    connection {
    agent = false
    bastion_host = "${var.nat_public_ip}"
    bastion_user = "ec2-user"
    bastion_port = 22
    bastion_private_key = "${file("${var.tf_home}/${var.aws_key_path}")}"
    user = "centos"
    host = "${self.private_ip}"
    private_key = "${file("${var.tf_home}/${var.aws_key_path}")}"
    timeout = "2m"
    }
    }

  11. Remote-exec provisioner
    The remote-exec provisioner invokes a script on a remote resource after it is created. This can be used to run a configuration management tool, bootstrap into a cluster, etc. To invoke a local process, see the local-exec provisioner instead. The remote-exec provisioner supports both ssh and winrm type connections.
    # Run puppet and join our Consul cluster
    resource "aws_instance" "web" {
    ...
    provisioner "remote-exec" {
    inline = [
    "puppet apply",
    "consul join ${aws_instance.web.private_ip}"
    ]
    }
    }


  12. Output
    Outputs are a way to tell Terraform what data is important. This data is outputted when apply is called, and can be queried using the terraform output command.Let's define an output to show us the public IP address of the elastic IP address that we create. Add this to any of your *.tf files:
    output "ip" {
    value = "${aws_eip.ip.public_ip}"
    }

    This defines an output variable named "ip". The value field specifies what the value will be, and almost always contains one or more interpolations, since the output data is typically dynamic. In this case, we're outputting the public_ipattribute of the elastic IP address.

    Multiple output blocks can be defined to specify multiple output variables.

  13. Modules
    Modules in Terraform are self-contained packages of Terraform configurations that are managed as a group. Modules are used to create reusable components, improve organization, and to treat pieces of infrastructure as a black box.
     Create a configuration file with the following contents: 
    provider "aws" {
    access_key = "AWS ACCESS KEY"
    secret_key = "AWS SECRET KEY"
    region = "AWS REGION"
    }

    module "consul" {
    source = "github.com/hashicorp/consul/terraform/aws"

    key_name = "AWS SSH KEY NAME"
    key_path = "PATH TO ABOVE PRIVATE KEY"
    region = "us-east-1"
    servers = "3"
    }

      The module block tells Terraform to create and manage a module. It is very similar to the resource block. It has a logical name -- in this case "consul" -- and a set of configurations.

     The source configuration is the only mandatory key for modules. It tells Terraform where the module can be retrieved. Terraform automatically downloads and manages modules for you. For our example, we're getting the module directly from GitHub. Terraform can retrieve modules from a variety of sources including Git, Mercurial, HTTP, and file paths.

     The other configurations are parameters to our module. Please fill them in with the proper values.

     Prior to running any command such as plan with a configuration that uses modules, you'll have to get the modules. This is done using the get command.

     
    $ terraform get
    ...

     This command will download the modules if they haven't been already. By default, the command will not check for updates, so it is safe (and fast) to run multiple times. You can use the -u flag to check and download updates.

     


    With the modules downloaded, we can now plan and apply it. If you runterraform plan, you should see output similar to below:
    $ terraform plan
    ...
    + module.consul.aws_instance.server.0
    ...
    + module.consul.aws_instance.server.1
    ...
    + module.consul.aws_instance.server.2
    ...
    + module.consul.aws_security_group.consul
    ...
    Plan: 4 to add, 0 to change, 0 to destroy.

    Conceptually, the module is treated like a black box. In the plan, however Terraform shows each resource the module manages so you can see each detail about what the plan will do. If you'd like compressed plan output, you can specify the -module-depth= flag to get Terraform to output summaries by module.

    Next, run terraform apply to create the module. Note that as we warned above, the resources this module creates are outside of the AWS free tier, so this will have some cost associated with it.
    $ terraform apply
    ...
    Apply complete! Resources: 3 added, 0 changed, 0 destroyed.

    After a few minutes, you'll have a three server Consul cluster up and running! Without any knowledge of how Consul works, how to install Consul, or how to configure Consul into a cluster


Comments

Popular posts from this blog

Salt stack issues

The function “state.apply” is running as PID Restart salt-minion with command:  service salt-minion restart No matching sls found for ‘init’ in env ‘base’ Add top.sls file in the directory where your main sls file is present. Create the file as follows: 1 2 3 base: 'web*' : - apache If the sls is present in a subdirectory elasticsearch/init.sls then write the top.sls as: 1 2 3 base: '*' : - elasticsearch.init How to execute saltstack-formulas create file  /srv/pillar/top.sls  with content: base : ' * ' : - salt create file  /srv/pillar/salt.sls  with content: salt : master : worker_threads : 2 fileserver_backend : - roots - git gitfs_remotes : - git://github.com/saltstack-formulas/epel-formula.git - git://github.com/saltstack-formulas/git-formula.git - git://github.com/saltstack-formulas/nano-formula.git - git://github.com/saltstack-f...

Saltstack and Vault integration

First install and configure vault using this tutorial: https://apassionatechie.wordpress.com/2017/03/05/hashicorp-vault/ Use the latest version of vault. Then install salt using the steps given here: https://docs.saltstack.com/en/latest/topics/installation/ If you face any issues then refer these links: https://apassionatechie.wordpress.com/2017/07/31/salt-issues/ https://apassionatechie.wordpress.com/2017/08/03/salt-stack-formulas/ Now let's integrate vault and salt so that we can access vault secrets from inside salt state. First let's add some key values into our vault. vault write secret/ssh/user1 password="abc123" Then you can check it by reading: vault read secret/ssh/user1 To allow salt to access your secrets you must firstly create a policy as follows: salt-policy.hcl [code] path "secret/*" { capabilities = ["read", "list"] } path "auth/*" { capabilities = ["read", "list","sudo",...

Terraform

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. Configuration files describe to Terraform the components needed to run a single application or your entire datacenter. 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 infrastructure Terraform can manage includes low-level components such as compute instances, storage, and networking, as well as high-level components such as DNS entries, SaaS features, etc. 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...