Kyle Edwards

Terraform

Infrastructure as code (IaC)

Using code to declaratively describe the desired state of your system. This code can be versioned and stored in source control. IaC solutions like Terraform attempt to establish the state of the system in a consistent and idempotent way. Automating these normally manual processes reduces the risk for human error.

Other benefits include leveraging existing or reusable components, and the code itself becomes the documentation of the infrastructure.

What is Terraform?

Terraform is an open-source infrastructure-as-code tool from HashiCorp implemented as a single binary compiled from Go. Configuration files are written in HashiCorp Configuration Language (HCL). Push-based deployment. No agents to install on remote machines (like the K8s Control Plane node).

Terraform automatically stitches together all .tf files in a directory into a single configuration. Once deployed, Terraform keeps track of the current state of the system. When deploying new configurations, Terraform compares the desired state with the current state and makes only the changes necessary.

Use a secrets service provider/resource to keep sensitive data secure.

HCL

block_type "label" "name_label" {
	key = "value"
	nested_block {
		key = "value"
	}
}

# Setting properties
#
#               ┌─ This is from the AWS provider plugin
#               │
#               │              ┌─ This is a name label
#               │              │
resource "aws_instance" "my_webserver" {
	name = "web-server"
	ebs_volume {
		size = 40
	}
}

# Reading properties
#
#    ┌─ Resource Type
#    │
#    │             ┌─ Name Label
#    │             │        
#    │             │        ┌─ Attribute
#    │             │        │
aws_instance.my_webserver.name # "web-server"
provider "aws" {
	access_key = "ACCESS_KEY"
	secret_key = "SECRET_KEY"
	region = "us_east_1"
}

data "aws_ssm_parameter" "ami" {
	name = "/aws/service/ami-amazon-linux/latest/amzn2-ami-hvm-x86_64-gp2"
}

# NETWORKING

resource "aws_vpc" "vpc" {
	cidr_block = "10.0.0.0/16"
	enable_dns_hostnames = "true"
}

resource "aws_internet_gateway" "igw" {
	vpc_id = aws_vpc.vpc.id # RTFM to get these fields
}

resource "aws_subnet" "subnet1" {
	cidr_block = "10.0.0.0/24"
	vpc_id = aws_vpc.vpc.id
	map_public_ip_on_launch = "true"
}

# ROUTING

resource "aws_route_table" "rtb" {
	vpc_id = aws_vpc.vpc.id
	route {
		cidr_block = "0.0.0.0/0"
		gateway_id = aws
	}
}

Workflow

  1. terraform init downloads providers from the default registry (unless otherwise provided) and sets up a state file.
  2. terraform plan shows you the changes it will make. You can save this to a file for the next step.
  3. terraform apply runs the steps from the plan.
  4. terraform destroy removes everything from the current state. Obviously this is a dangerous, destructive command when used improperly.

Note: Do not hardcode the provider credentials within your configuration files.

Variables and Output

Input variables

variable "name_label" {
	type = "<data type>"
	description = "Description context"
	default = "<default value>"
	sensitive = true or false # if true, will not log
}

variable "billing_tag" {} # no properties are required
variable "aws_region" {
	type = string
	description = "Region to use for AWS resources"
	default = "us-east-1"
	sensitive = false
}

To use these values, you can refer to them by var.<name_label>.

Data types

variable "aws_regions" {
	type = list(string)
	default = ["us-east-1", "us-east-2"]
}

var.aws_regions[0]

variable "aws_instance_sizes" {
	type = map(string)
	default = {
		micro = "t2.micro"
		small = "t2.small"
		large = "t2.large"
	}
}

var.aws_instance_sizes.micro
# or
var.aws_instance_sizes["micro"]
output "name_label" {
	value = output_value
	description = "Description"
	sensitive = true or false
}

output "public_dns_hostname" {
	value = aws_instance.nginx1.public_dns
	description = "Public DNS name for EC2 instance"
}
locals.tf
main.tf
outputs.tf
variables.tf

To validate your configuration, you can use terraform validate.

terraform init
terraform validate

Supplying Variable Values

  1. Set with default argument
  2. Using the -var flag from the CLI
  3. Using the -var-file flag
  4. Including a terraform.tfvars or terraform.tfvars.json file
  5. Including a .auto.tfvars or .auto.tfvars.json file
  6. Environment variables starting with TF_VAR_

Goes through an order of operations.

Note: Because most of the work with Terraform is getting comfortable with specific provider resources and their parameters, the registry documentation is an invaluable tool.

Provider plugins are Go binaries that implement resources and data sources. Because of this, they can read environment variables directly, so be sure to check the documentation.

Note: It’s all about knowing the provider resources you need to create to accomplish your goals.

Terraform determines a dependency graph of all of the required resources.

Loops and Functions

# Create a tuple
[ for item in items : tuple_element ]

# Create an object
{ for key, value in map : object_key => object_value }

# IP example
[ for x in range(4) : cidrsubnet("10.0.0.0/16", 8, x) ]

These can be very handy to dynamically scale resources.

Function Categories

Modules

Terraform modules are encapsulations of configuration files so they can be versioned and reused. They can both be local modules or modules found on the Terraform registry.

For example, the terraform-aws-modules/vpc/aws module manages a number of resources necessary for implementing an AWS VPC, such as the VPC itself, an internet gateway, and subnets.

Output values of a child module can be used by the parent module as inputs. Child modules can take variables and providers as inputs.