Terraform Certification (Terraform Associate (003))

Test Details

Description Detail
Duration 1 Hour
Multiple Choice 50-60
Location Online
Expiration 2 Years
Min Grade 70%

IaC and its Benefits

IaC is a short term for Infrastructure as code. It allows you to write down whatever you want to be deployed as code. This strategy allows you to create resources in a automated and predictable way reducing errors and keeping history of changes.

Terraform Characteristics

Terraform uses a language called HCL (Hashicorp Configuration Language) declare IaC, it is platform agnostic allowing you to deploy to most of big cloud providers and even to deploy to multiple cloud providers, terraform tracks the state of each resource deployed and gets them to your desired state.

Terraform Workflow

There are 3 major steps in the terraform workflow:

  1. Write - You write your terraform code and store it somewhere (eg. Github)
  2. Plan - You use the plan command to review the changes that will be applied to your infrastructure
  3. Apply - Deploying the changes you made, provisioning real infrastructure

Terraform main commands

Terraform Init

terraform init initializes the working directory that contains your Terraform code. It follows this steps:

  1. Downloads components - Download modules and plugins (eg. providers and modules)
  2. Sets up backend - Sets up backend that will store Terraform state file.

This is a command that should be executed when you start a terraform or when you add/update providers or modules.
The downloaded providers are binaries that execute API calls to vendors, those API calls are abstracted away by terraform.
To change the path where the plugins are downloaded you can add the flag -plugin-dir, passing the path to the directory where the plugins will be saved.

There are a couple of flags that can be interesting:

Terraform Plan

terraform plan reads the code, creates and shows a plan of the execution/deployment. It does API calls to vendors, but only fetches information.
The plan allows the user to review the action plan before executing anything.
Since it needs to call vendors it also needs to use authentication credentials to connect to them.

Terraform Apply

terraform apply deploys the instructions and statements in the code provisioning the infrastructure specified.
It updates the deployment state tracking file.
It is possible to only destroy infra by adding the flag -destroy
It is possible to only apply changes to a resource by adding the flag -target

Terraform Destroy

terraform destroy is about deprovisioning infrastructure. It looks at the recorded state file created during terraform apply and deprovisions all resources created by it.
This command should be used with caution as it is a non-reversible command. Take backups and be sure that you want to delete infrastructure.

Terraform Providers

Providers are terraform's way of abstracting integrations with API control layer of the infrastructure vendors, they can only be written in Go lang and when you run terraform init only its binary is downloaded.
Terraform finds and install providers when initialising working directory (terraform init), by default, looks for providers in Terraform providers registry/

Providers are plugins. They are released on a different pace from Terraform itself, and each provider has its own series of version numbers, so as a best practice providers should have a specific version.

Configuring providers

# provider is a reserved keyword
# aws tells the name of the provider that will be fetched on public terraform registry
provider "aws" {
	region = "us-east-1" # this allows you to pass configuration parameters
}

Another example:

providers "google" {
	credentials = file("credentials.json")
	project     = "my-project"
	region      = "us-west-1"
}

Important - You can use the property source to interact with plugins/providers and modules that are stored on git repositories, terraform registry, local files, http. s3, subdirectories and GCS.

Declaring Resources

# resource is a reserved keyword
# Then you have the resource type
# At last you have an alias that you give to this resource
resource "aws_instance" "web" {
	ami           = "ami-a1b2c3d4"
	instance_type = "t2.micro"
}

To access the values you follow this pattern [resource-type].[resource-alias].[resource_property]

Querying Resources

# data is a reserved keyword
# Then you have the resource type
# At last you have an alias that you give to this resource
data "aws_instance" "my-vm" {
	instance_id = "i1234567890" # What will be queried, this will vary depending on the resource type
}

To access the values you follow this pattern data.[resource-type].[resource-alias].[resource_property]

The following data can be used to fetch outputs from other states.

data "terraform_remote_state" "remote_state" {
  backend = "remote"
  config = {
    organization = "hashicorp"
    workspaces = {
      name = "vpc-prod"
    }
  }
}

Terraform State

Terraform state is a mechanism that terraform uses to know what has been deployed and it is referred back when executing commands so it can know what to create, edit or delete.

It is a JSON dump containing all the metadata about your Terraform deployment as well as details about all the resources that it has deployed. It is named terraform.tfstate by default and stored in the same directory where your terraform code is, but for better integrity and availability it can also be stored remotely, by using the backend feature.

Terraform Variables

# variable is a reserved keyword
# then comes the name of the variable
variable "my-var" {
	# all the properties are optional
	description = "My test variable"
	type = string
	default = "Hello"
	sensitive = true # hides the value from the output
}

To reference a variable use the following structure var.[variable-name]
Variables can be defined anywhere but as a best practice they should be stored on terraform.tfvars

Precedence - The lowest precedence is environment variables, terraform.tfvars, terraform.tfvars.json, .auto.tfvars and last -var and -var-file.

Environment variables are defined following the pattern TF_VAR_[variable_name]

Variable Validation

variable "my-var" {
	# all the properties are optional
	description = "My test variable"
	type = string
	default = "Hello"
	validation {
		condition = length(var.my-var) > 4
		error_message = "The string must be more than 4 characters"
	}
}

Variable types

The base types are: string, number, bool
The complex types are list, set, map, object, tuple
List, set and map are collections and only hold a type
Object tuple and set are structural types and can hold multiple types
There is any type.

variable "availability_zone_names" {
	type = list(string)
	default = ["us-west-1a"]
}
variable "docker_port" {
	type = object({
		internal = number
		external = number
		protocol = string
	})
	default = {
		internal = 8300
		external = 8300
		protocol = "tcp"
	}
}

Important - Terraform will try to convert the values passed to an string variable, so 102, "102" and potato are valid values (if terraform is able to convert)

Terraform Outputs

output "instance_ip" {
	description = "VM's private IP"
	sensitive = false
	value = aws_instance.my-vm.private_ip # The only config parameter required
}

Output variable values are shown on the shell after running terraform apply.

Terraform Provisioner

Mechanism that provides terraform a way of running custom scripts, commands or actions.
You can either run it locally (where terraform commands are being executed), or remotely on resources that spun up through the terraform deployment.
Within Terraform code, each individual resource can have its own provisioner defining the connection method (eg. ssh, WimRM) and the actions/commands or script to execute.

There are 2 types of provisioners: "Creation-time" and "Destroy-time"

It should be a last resource, and terraform state does not track it.

Provisioners are recommended for use when you want to invoke actions not covered by terraform declarative mode.
If the command within a provisioner returns a non-zero code, it is considered failed and the resource will be tained (marked for deletion and recreation on next apply)

resource "null_resource" "dummy_resource" {
	# will return when created
	provisioner "local-exec" {
		command = "echo '0' > status.txt"
	}
	provisioner "local-exec" {
		when = destroy
		command = "echo '1' > status.txt"
	}
}

You can self reference the resource by using self variable

resource "aws_instance" "webserver" {
  ami                         = data.aws_ssm_parameter.webserver-ami.value
  instance_type               = "t3.micro"
  key_name                    = aws_key_pair.webserver-key.key_name
  associate_public_ip_address = true
  vpc_security_group_ids      = [aws_security_group.sg.id]
  subnet_id                   = aws_subnet.subnet.id
  provisioner "remote-exec" {
    inline = [
      "sudo yum -y install httpd && sudo systemctl start httpd",
      "echo '<h1><center>My Test Website With Help From Terraform Provisioner</center></h1>' > index.html",
      "sudo mv index.html /var/www/html/"
    ]
    connection {
      type        = "ssh"
      user        = "ec2-user"
      private_key = file("~/.ssh/id_rsa")
      host        = self.public_ip
    }
  }
  tags = {
    Name = "webserver"
  }
}

Terraform State

It maps real-world resources to resource defined in your terraform code or configuration.
By default it is stored locally in a file called terraform.tfstate, but it can be saved in other places.
Prior to any modification, terraform refreshes the state file.
Resource dependency metadata is also tracked via the state file
It caches resource attributes for subsequent use.

terraform state command is a utility form manipulating and reading the Terraform State file (advanced usecase)
You can manually remove a resource from terraform state file so it won't be managed by terraform anymore.
List tracked resources and their details

Terraform State Subcommand Use
terraform state list List all resources tracked by the state file
terraform state rm Deletes a resource from terraform state file
terraform state show [RESOURCE] Show details of a resource tracked in the terraform state
terraform state push [path] Updates local state to remote state

Remote state storage

You can save the state to a remote data source such as AWS S3 or google storage.
This allows sharing state file between distributed teams
And it allows locking state so parallel executions don't coincide.
It enables sharing output values with other terraform configurations/code.

terraform {
	backend "s3" {
		profile = "demo"
		region = "us-east-1"
		key = "terraformstatefile"
		bucket = "myawesomebucket2344"
	}
}

Important: When using an S3 backend it is possible to add a prefix to your worflow state by adding the property workspace_key_prefix="prefix" the state will be saved then on s3://myawesomebucket2344/prefix/myapp-dev/terraform.tfstate

State Lock

If supported by your backend, Terraform will lock your state for all operations that could write state. This prevents others from acquiring the lock and potentially corrupting your state.

State locking happens automatically on all operations that could write state. You won't see any message that it is happening. If state locking fails, Terraform will not continue.

Important - You can disable state locking for most commands with the -lock flag but it is not recommended.

In case you want to remove the lock you can use the command terraform force-unlock [lock-id]

Terraform Module

It is just another folder or collection of terraform code. It groups multiple resources that are used together making code more reusable.
Every terraform configuration has at least one module, called the root module, which consists of code files in your main working directory.

Modules can be referenced from:

module "name-of-the-module" {
	source = "./modules/vpc" # mandatory
	version = "0.0.5" # module version
	region = var.region # input parameters
}
module "name-of-the-module" {
	source = "hashicorp/consul/aws" # mandatory
	version = "0.0.5" # module version
	region = var.region # input parameters
}
module "name-of-the-module" {
	source = "app.terraform.io/example-corp/k8s-cluster/azurerm" # mandatory
	version = "0.0.5" # module version
	region = var.region # input parameters
}

Other properties are:
count - Allows spawning multiple instances of the module resources
for_each - Allows you iterate over complex variables
providers - Allow you to set specific providers to module
depends_on - Adds explicit dependency to other resources

All outputs will be available for use by following this pattern module.[module-name].[output]

Best Practices

Terraform built-in functions

Terraform comes pre-packaged with functions to help you transform and combine values.
User defined functions are not allowed

You can check the functions here - Functions - Configuration Language | Terraform | HashiCorp Developer

Dynamic blocks

Iterates over a complex type variable and acts like a for loop and outputs a nested block foreach element in your variable. They can be used inside resource, data, provider and provisioner

resource "aws_elastic_beanstalk_environment" "tfenvtest" {
  name                = "tf-test-name"
  application         = "${aws_elastic_beanstalk_application.tftest.name}"
  solution_stack_name = "64bit Amazon Linux 2018.03 v2.11.4 running Go 1.12.6"

  dynamic "setting" {
    for_each = var.settings
    content {
      namespace = setting.value["namespace"]
      name = setting.value["name"]
      value = setting.value["value"]
    }
  }
}

It is also possible to create an iterator to change the name of the variable of the block

resource "aws_elastic_beanstalk_environment" "tfenvtest" {
  name                = "tf-test-name"
  application         = "${aws_elastic_beanstalk_application.tftest.name}"
  solution_stack_name = "64bit Amazon Linux 2018.03 v2.11.4 running Go 1.12.6"

  dynamic "setting" {
    for_each = var.settings
    iterator = property
    content {
      namespace = property.namespace
      name = property.name
      value = property.value
    }
  }
}

Terraform CLI commands

terraform fmt - Format terraform code for readability for the current folder, unless the flag -recursive is passed. Only modifies the style of the code. You can prevent it from modifying the files by using the flag -check

[DEPRECATED] - terraform taint RESOURCE_ADDRESS - Taints a resource, forcing it to be destroyed and recreate. To do that, it modifies the state file and also may cause other resources to be modified. USE: terraform apply -replace="resource_type.resource"

terraform import RESOURCE_ADDRESS id - Maps existing resources to terraform using an id. The id depends on the underlying vendor. Importing the same resource to multiple terraform resources can cause unknown behaviour and it is not recommended.

Terraform configuration block

A special configuration block for controlling terraform behaviour. It only allows constant values.

terraform {
	required_version = ">=0.13.0" # Requires terraform version 
	required_providers {
		aws = ">= 3.0.0"
	}
	backend "s3" {
		profile = "demo"
		region = "us-east-1"
		key = "terraformstatefile"
		bucket = "myawesomebucket2344"
	}
}

Terraform workspaces

Terraform workspaces are alternative state files within the same working directory, allowing different environments to be spawn.
The default namespace is called default and it cannot be deleted

Command Use
terraform workspace new [workspace-name] Creates a workspace
terraform workspace select [workspace-name] Select a workspace

Access to a workspace name is provided through ${terraform.workspace} variable.

Important - The state of workspaces is stored on terraform.tfstate.d

Debugging terraform

TF_LOG is an environment variable for enabling verbose logging in Terraform. By default it send logs to stderr (standard error output)
Can be set to the following levels: TRACE, DEBUG, INFO, WARN, ERROR
With the trace level being the most verbose
You can redirect logs to a file by setting TF_LOG_PATH environment variable

Terraform Cloud

Terraform Sentinel - It is a framework that enforces adherence to policies on your terraform code.

It has its own policy language called sentinel language. It is executed between terraform plan and apply. You can define guardrails for automation, for example, preventing a dev to deploy to production. It allows testing and automation.

Use cases:

Terraform Vault - It is a secrets manager software that provide temporary credentials to users in place of actual long-lived credentials. It dynamically provisions credentials and rotates them. It encrypts sensitive data in transit and at rest, but not when executing, and provides fine-grained access to secrets using ACLs.

To consume the secrets you use vault provider.

terraform-vault 1.png

Terraform Registry - You can contribute with other contributors to make changes to providers and modules

Terraform Cloud Workspaces - Works the same as workspaces locally but in the cloud. It maintains a record of all execution activity and all terraform commands are executed on managed terraform cloud vms

Terraform Cloud Private can connect to Github, BitBucket, Gitlab and azure devops.

terraform-cloud-comparisson 1.png

Terraform parallelism is 10 by default

Terraform Backend types - enhanced and standard