Learning Materials
- Terraform Official Document
- Terraform Best Practice
- Practice exams
- Terrafrom Certificate
IaC Concept
Terraform Basics
Provider
example
terraform { required_providers { aws = { source = "hashicorp/aws" version = "3.58.0" } } } provider "aws" { # Configuration options }Use
aliasto set alternate providers# reference this as `aws.west` provider "aws" { alias = "west" region = "us-west-2" }
Modules
Root Module: A collection oof
.tffiles in the same directory.Calling a child module in module
module "servers" { source = "./app-cluster" # local path of a child module servers = 5 }
Only one
terraformblock across a module.Meta-arguments:
count,for_each,providers(use default if not specified),depends_on
Sources
- Doc
- Source types
- Terraform Registry (registry.terraform.io
)
- all public
- supports version constraint
- HTTP URL or local path
- Git (using HTTPS or SSH)/GitHub/BitBucket
- Cloud Storage (e.g. S3, GCS, but Azure is not supported)
- Terraform Cloud (support private registry )
- Terraform Registry (registry.terraform.io
)
Variables & Output
Provisioner
Build images first. Provisioner is the last resort.
- Applying it in resource block affects all provisioner in that resouce.
null_resourceUse it when do not want to associate with resource
resource "null_resource" "null_resource_simple" { provisioner "local-exec" { command = "echo Hello World" } }Use
triggersargument to re-runresource "aws_instance" "cluster" { count = 3 # ... } resource "null_resource" "cluster" { # Changes to any instance of the cluster requires re-provisioning triggers = { cluster_instance_ids = "${join(",", aws_instance.cluster.*.id)}" } connection { host = "${element(aws_instance.cluster.*.public_ip, 0)}" } provisioner "remote-exec" { inline = [ "bootstrap-cluster.sh ${join(" ", aws_instance.cluster.*.private_ip)}", ] } }
Genric Provisioners
- file
- copy files or directories (
source) or specified content (content) to a created resource (destination).
- copy files or directories (
- local-exec
- Invoke local executable after a resource is created.
- remote-exec
- Invoke script on a remote resource after it’s created.
inlineruns inline commands whilescript&scriptscopy one or multiple scripts to remote and execute.
resource "aws_instance" "web" { # ... provisioner "file" { content = "ami used: ${self.ami}" destination = "/tmp/file.log" } provisioner "local-exec" { command = "echo $FOO >> sample.txt" environment = { FOO = "bar" } } provisioner "file" { source = "script.sh" destination = "/tmp/script.sh" } provisioner "remote-exec" { inline = [ "chmod +x /tmp/script.sh", "/tmp/script.sh args", ] } }- file
Functions
TF does not support user-defined functions.
Collection Function
> concat(["a", ""], ["b", "c"]) ["a", "", "b", "c"] > slice(["a", "b", "c", "d"], 1, 3) [ "b", "c", ] > coalesce("", "b") # return first element not null or not empty string "b" > compact(["a", "", "b", "c"]) ["a", "b", "c"]String Function
> format("Hello, %s!", "Ander") > split(",", "foo,bar,baz") # and join() > replace("1 + 2", "+", "-")IP Network Function
> cidrhost("10.12.127.0/20", 16) # prefix, hostnum 10.12.112.16 > cidrnetmask("172.16.0.0/12") 255.240.0.0 # 11111111 11110000 00000000 0000000 > cidrsubnet("10.1.2.0/24", 4, 15) # prefix, newbits, netnum 10.1.2.240/28 # 2**(8-4) * 15 = 240, 24 + 4 = 28 > cidrsubnets("10.1.0.0/16", 4, 4, 8, 4) [ "10.1.0.0/20", "10.1.16.0/20", "10.1.32.0/24", "10.1.48.0/20",] > [for cidr_block in cidrsubnets("10.0.0.0/8", 8, 8) : cidrsubnets(cidr_block, 4, 4)] [ [ "10.0.0.0/20", "10.0.16.0/20", ], [ "10.1.0.0/20", "10.1.16.0/20", ], ]
Workflows
- Doc
- Different Situation
- Individual: Simply write -> plan -> apply.
- Work as a team: Get the latest code (e.g. via git) -> write -> plan -> commit -> apply
- Core Workflow (Terraform Cloud)
Core Workflow & CLI
- CLI Configuration File (.terraformrc)
terraform init(doc )- Mainly does 3 things:
- Create a
.terraformdirectory - Download plugin dependencies
- Create a dependency lock file (named
.terraform.lock.hcl, doc ) - (Note that tf state is created when
terraform apply)
- Create a
-upgradeoption upgrades plugins.-backend-config=backend.hcloption enables users to store sensitive backend configs in another file.
- Mainly does 3 things:
- Format and Validate
- terraform console
> echo "1 + 5" | terraform console 6 - terraform fmt
- for style convention
-write=trueto overwrite the files
- terraform validate
- Check if the required attributes are presented and the correct types are used for values.
- terraform console
terraform plan-out=FILENAMEfor exporting saved plan
terraform apply-refresh-onlyoption (equivalent toterraform refresh) only updates the state.-replace=option (equivalent toterraform taint) marks a particular object as needed to be replaced.
terraform destroy- Doc
- Equivalent to
terraform apply -destroy(for plan mode, useterraform plan -destroy)
Debugging
- Doc , Troubleshoot tutorial
- Set
TF_LOGenv var toTRACE,DEBUG,INFO,WARN,ERROR, orJSON(TRACEwith json format). - Set
TF_LOG_PATHto append logs to a file. - Check
crash.logif TF has ever crashed.
Implement and Maintain State
Backend
2 types,
Standard&Enhanced- Doc
Standard: only store state, and rely on the local backend for performing operations (e.g. S3).Enhanced: both store state and perform operations.- 2 enhanced backend types:
local&remote.
- 2 enhanced backend types:
# standard backend using s3 terraform { backend "s3" { bucket = "my-terraform-state-bucket" key = "statefile" region = "us-east-1" } } # remote backend terraform { backend "remote" { hostname = "app.terraform.io" organization = "company" workspaces { prefix = "my-app-" } } } # it uses local backend by default terraform { } # local backend terraform { backend "local" { path = "relative/path/to/terraform.tfstate" } }Use
prefixfor multiple workspace- Doc
- example
workspaces { prefix = "app-" }
State
- Mapping to real resources.
- Metadata (e.g. dependencies)
- Performance: Cache state to reduce queries for resources
- Syncing for team collaboration.
Stored in a local file
terraform.tfstate. TF refresh the file before any command.- Storing in remote works better for teamwork (remote state ).
> terraform state --help Usage: terraform state <subcommand> [options] [args] ... Subcommands: list List resources in the state mv Move an item in the state pull Pull current state and output to stdout push Update remote state from a local state file rm Remove instances from the state show Show a resource in the state- rename resource:
terraform state mv <resource>.<old_name> <resource>.<new_name>
- rename resource:
One should always treat state file as containing sensitive data.
- Using a remote backend or Terraform Cloud to manage state is recommended.
- Suppress sensitive date in CLI output
- lock state for ops that write state
- terraform force-unlock to unlock
Backup
- path:
terrraform.tfstate.backup
- path:
Sensitive data exists in the state
- S3: add
encryptoption - TF Cloud: always encrypts state
- S3: add
Importing Resources
terraform import: bring existing resources under Terraform’s management- doc , cli doc , tutorial
- Not every resource is importable, checkout resource doc before importing.
- Procedures
- Write a resource (body can be blank).
- Run
terraform import(Note that TF will not update the script after importing) - Checkout tf output if there is any secondary resource (e.g.
aws_network_acl_ruleforaws_network_acl). Create resource for each of them.
- Examples
- Import into resource:
terraform import aws_instance.foo i-abcd1234 - Import into resource with count:
terraform import 'aws_instance.baz[0]' i-abcd1234 - Import into module:
terraform import module.foo.aws_instance.bar i-abcd1234
- Import into resource:
Read, Generate, and Modify Configuration
Data Block
filter- Doc
- example
data "aws_ami" "web" { filter { name = "state" values = ["available"] } filter { name = "tag:Component" values = ["web"] } }Inject secrets
data "vault_aws_access_credentials" "creds" { backend = data.terraform_remote_state.admin.outputs.backend role = data.terraform_remote_state.admin.outputs.role } provider "aws" { region = var.region access_key = data.vault_aws_access_credentials.creds.access_key secret_key = data.vault_aws_access_credentials.creds.secret_key }
Variables
- Doc
- Using
TF_VAR_env var for configs or secrets
Dynamic Blocks
Example
dynamic "setting" { for_each = var.settings content { namespace = setting.value["namespace"] name = setting.value["name"] value = setting.value["value"] } }
Dependency Management
depends_on(explicit dependency)- Only necessary when a resource or module relies on some other resource’s behavior but doesn’t access any of that resource’s data in its arguments (which is implicit dependency).
- Available for
module&resource.
terraform graphfor visualization
Terraform Cloud & Enterprise
- Terraform Cloud
- Hosted modules & providers on
app.terraform.io
- Hosted modules & providers on
- Feature Matrix
- Sentinel : Policy as Code tool. It allows you to write policies to validate that your infrastructure is in its expected configuration.
- Air Gap : Air Gap or disconnected network is a network security measure employed on one or more computers to ensure that a secure computer network is physically isolated from unsecured networks e.g. Public Internet
