Terraform and Terraform Cloud
Summary of Terraform
- Build and manage any cloud, infrastructure, or service
- Terraform code is written in HCL
- Terraform does not enforce a file/directory structure
- Build steps are expressed as code (dependencies between resources)
- DRY aka modules
- Immutable infrastructure*
- CLI tool
- API wrapper for other API’s (AWS, Kubernetes, GCP etc.)
<resource-type> <resource-klass> <resource-instance> {
...
...
...
}
data "github_branch" "uat" {
repository = "example"
branch = "uat"
}
resource "github_branch" "development" {
repository = "example"
branch = "development"
}
output "development_source_sha" {
value = "${github_branch.development.source_sha}:-:${data.github_branch.uat.sha}"
}
CLI
terraform init # initialize current directory
terraform plan # dry run to see changes
terraform apply # apply changes
terraform refresh # refresh the state file
terraform output # view Terraform outputs
terraform destroy # destroy what was built by Terraform
terraform console # interactive Terraform session
terraform state # play with the state file, but carefully
terraform show # show the state file
terraform validate # validates all Terraform's files
terraform -help # help for Terraform
terraform <command> -help # help for a Terrform's command
Terraform state
Terraform keeps information about resources it has built in a state file. The file contains all needed data that Terraform needs to modify a resource. By default Terraform uses local storage to store the state file.
Local:
- sometimes includes sensitive data
- hard to share the file with other people (dropbox?)
- backups responsibilities delegated to a user
Remote:
- support multiple backends (S3, GCS, Terraform Cloud, PostgreSQL, Consul, HTTP … and so on)
- easy to share
- backups responsibilities delegated to a backend
State locking
- has to be supported by a backend
- enables only when an operation needs to write state
- prevents against corrupting a state file
- lock can be skipped by passing
-lock
flag, eg.terraform apply -lock
- avoid Ctrl/Cmd-C (or press it once)
Terraform Cloud
- https://app.terraform.io
- it’s easy to use (requires just a token (user/organization))
- stores states file
- stores cloud/service credentials
- stores terraform variables
- queued remote (auto)apply triggered by VCS integration
- 2FA
Terraform versions
- current stable 0.13.4
- installation
asdf plugin-add terraform
asdf install terraform 0.13.4
asdf local terraform 0.13.4
Terraform variables / expressions
Variables definitions should be stored *.tfvars
files.
- primitive
- string
- number
- bool
- structural
- list(<TYPE>)
- map(<TYPE>)
- object({<ATTR NAME> = type, …})
- tuple(<TYPE>) (collection which is ordered)
- null
variable "image_id" {
type = string
description = "The id of the machine image (AMI) to use for the server."
}
variable "port" {
type = number
description = "Default HTTP port"
}
variable "enabled" {
type = bool
description = "HTTP traffic enabled"
default = false
}
variable "availability_zone_names" {
type = list(string)
default = ["us-west-1a", "eu-north-1a"]
}
variable "size" {
type = map
default = {
"small" = "t3.small"
"medium" = "t3.medium"
"big" = "t3.large"
}
}
# plan = var.size["small"]
variable "nomad_ports" {
type = list(object({
internal = number
external = number
protocol = string
}))
default = [
{
internal = 4646
external = 4646
protocol = "tcp"
}
]
}
variable "rack_env" {
type = tuple([string, bool, number])
}
# rack_env = ["RACK_ENV", true, 3000]
Environment Variables
By default Terraform searches the environment of its own process for env. variables named with prefix TF_VAR_
.
TF_VAR_enabled=true terraform apply
Output Variables
Outputs allows to define values in the configuration that is being shared with users or resources.
Outputs are also printed out after such actions like apply
, refresh
or destroy
.
resource "github_branch" "development" {
repository = "example"
branch = "development"
}
output "development_source_sha" {
value = github_branch.development.source_sha
}
Terraform Functions
Terraform has a number of built-in functions for numeric, string, collection, encoding, date, time, encoding, filesystem, hash, IP networks or type conversion.
Local Values
It assigns a name to an expression. It can be treated as a temporary variable the same way like it’s done in programming languages.
variable "regions" { type = list }
variable "aws_global_regions" { type = list }
regions = list["north-1", "central-1"]
aws_global_regions = ["eu", "na"]
locals {
regions = setproduct(var.regions, var.aws_global_regions)
}
output "regions" {
value = local.regions
}
regions = [
[
"north-1",
"eu",
],
[
"north-1",
"na",
],
[
"central-1",
"eu",
],
[
"central-1",
"na",
],
]
Meta arguments
These arguments might useful to define resource dependencies, change a default provider or create multiple resource instances.
depends_on
- list of dependencies for a resourcecount
- number of identical resources to createfor_each
, to create multiple instances according to a map, or set of stringsprovider
, for selecting a non-default provider configurationlifecycle
, for lifecycle customizationscreate_before_destroy
- ensure that a new instance is created before the old one is destroyeprevent_destroy
- a deletion police manignore_changes
- listed attributes will not be taken into state verification
resource "random_id" "bucket_id" {
byte_length = 2
count = 3
}
resource "random_id" "nope" {
byte_length = 4
count = 0
}
Conditional logic
locals {
foo = true
}
output "bar" {
value = (local.foo ? list("baz") : list())
}
variable "userdata_path" {
default = ""
}
data "userdata_file" "template" {
vars = {
file_contents = (length(var.userdata_path) > 0 ? file(var.userdata_path) : "")
}
}
Modules
A module is a container for multiple resources that are used together. Every Terraform configuration has at least one module, known as its root module, which consists of the resources defined in the .tf files in the main working directory.
Structure
Modules use: * input variables * output values * resources
$ tree modules/sample-module/
.
├── README.md
├── main.tf
├── variables.tf
├── outputs.tf
$ cat sample.tf
module "sample_usage" {
source = "./modules/sample-module"
name = local.name
tags = local.tags
}
$ cat sample.tf
module "sample_usage_remote" {
source = "github.com/bartlomiejdanek/tf-modules//modules/sample-module?ref=v0.8.9"
name = local.name
tags = local.tags
}
Good practicies
- avoid multiple Ctrl/Cmd-C key strokes during terraforming
- avoid
null_resources
- use auto format (
terraform fmt -help
) - naming resources
- use
_
over-
- Good:
resource "aws_route_table" "public" {}
- Bad:
resource "aws_route_table" "public-route_table" {}
- Bad:
resource "aws_route_table" "public-aws_route-table" {}
- Always use singular nouns for names
- use
- specify exact version for providers/modules, eg.
~> 2.59.13
- use encrypted remote backend
- prefer HCL over JSON (use
jsonencode
) - use newest stable version (continuous upgrades)
Online examples
tf 0.11
# Configure the GitHub Provider
provider "github" {
token = "${var.github_token}"
owner = "${var.github_owner}"
}
# Add a user to the organization
resource "github_membership" "membership_for_user_x" {
# ...
}
tf 0.12
# Configure the GitHub Provider
provider "github" {
token = var.github_token
owner = var.github_owner
}
# Add a user to the organization
resource "github_membership" "membership_for_user_x" {
# ...
}