Codementor Events

Terraform Part I

Published Jul 06, 2019
Terraform Part I

Introduction to 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. Managing existing resources on aws will require the importation of the resources to Terraform and be managed by Terraform, which will allow other resources to be built around it.

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.

terraform plan
+ aws_instance.example.11 ami: "ami-v1" instance_type: "t2.micro"
+ aws_instance.example.12 ami: "ami-v1" instance_type: "t2.micro"
+ aws_instance.example.13 ami: "ami-v1" instance_type: "t2.micro"
+ aws_instance.example.14 ami: "ami-v1" instance_type: "t2.micro"
+ aws_instance.example.15 ami: "ami-v1" instance_type: "t2.micro"
Plan: 5 to add, 0 to change, 0 to destroy.

The above shows what will be added before the changes will be made on the 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.
The graph can also be used to troubleshoot issues,
Change Automation
Complex change sets 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.

Features

The use of variables
resources importation
infrastructure state file
infrastructure diagram generator based on the state
custom output variables
Comparison of Infrastructure As Code tools
interpolation

Next: Deploying an infrastructure using terraform.
Steps:
Set up your AWS account
Install Terraform
Attach aws credential
Deploy a single aws instance
Deploy multiple instances
Attach elastic address to all deployed instances

Setup AWS Account

Login to your aws account, go to your IAM console, go to "Users", click "Add user" to generate an access key and a secret key.Under access type, check Programmatic access, Click the "Create user" button on the last step and you will be able to see the security credentials for that user, which consist of Access Key ID and a Secret Access Key. You should save these keys immediately, as the Secret Access Key will never be shown again.
In order for terraform to be able to make changes to our aws infrastructure, we need to set AWS credential for the user. On your terminal:

export AWS_ACCESS_KEY_ID=(your access key id)
export AWS_SECRET_ACCESS_KEY=(your secret access key)
$ env |grep AWS
AWS_ACCESS_KEY_ID=XXXXXXXXXXXXXXXXXXXXXXX
AWS_SECRET_ACCESS_KEY=XXXXXXXXXXXXXXXXXXX

Install Terraform

To install Terraform, find the appropriate package for your system and download it. After downloading, unzip the package. Terraform runs as a single binary named terraform. Any other files in the package can be safely removed and Terraform will still function.
The final step is to make sure that the terraform binary is available on the PATH. See this page for instruction on setting the PATH on Linux and Mac. This page contains instructions for setting the PATH on Windows.
Easy installation:
Linux 
Download terraform for Linux

$ wget https://releases.hashicorp.com/terraform/0.xx.x/terraform_0.xx.x_linux_amd64.zip
$ unzip terraform_0.xx.x_linux_amd64.zip

set path 
$ sudo mv terraform /usr/local/bin
Mac
Using brew install is the quickest way brew install terraform. If you dont have homebre, install it.

ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" < /dev/null 2> /dev/null

install ruby
brew install terraform
Window
- Download terraform for windows

Note: Terraform is packaged as a zip archive, so after downloading Terraform, unzip the package. Terraform runs as a single binary named terraform. Any other files in the package can be safely removed and Terraform will still function
Copy files from the zip to c:\terraform for example. That's our terraform PATH.
The final step is to make sure that the terraform binary is available on the PATH.
Set the path in your system utility in control panel.

Verifying the Installation
After installing Terraform, verify the installation worked by opening a new terminal session and checking that terraform is available. By executing terraform you should see help output similar to this:

$ 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
    console            Interactive console for Terraform interpolations
# ...

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

Deploying a server

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

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. To bypass the yes prompt use terraform apply -auto-approve.

$ 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 take at least 2min. Another file terraform.tfstate will be generated which contains the state and all the info attributed to the ec2 instance. Currently, its save locally, in the coming chapter, we will be storing the file automatically into a secured s3 bucket for security reasons.

{
    "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:
Screen Shot 2019-07-04 at 8.36.27 PM.png

Notice, the ec2-instance id output i-0bf984bef5ff354d6 matches the instance id on the aws console. Now let's modify our ec2-instance by giving a name for our ec2 instance, we know the Name is configured from the tag, therefore, we will add a tag section to our terraform code for the ec2 resource.
Update existing ec2-instance
Update the resources.tf file by add thing tags

provider "aws" {
    region         = "us-east-1" 
    shared_credentials_file = "/PATH/TO/AWS/CONFIG"
    profile                 = "myAWSprofile"
}
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 exist. 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 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.
meegif.gif
Full Video https://bolatito.s3.amazonaws.com/terraformvideo1.mov

Source Code: https://github.com/iamtito/DevOps/tree/Terraform/Terraform/example1

That's all folk. Feel free to point out any typo,make some corrections, and contribute to this post or submit a PR

NEXT: Deploying Multiple different instances

Discover and read more posts from Bolatito Kabir
get started
post commentsBe the first to share your opinion
Show more replies