avatar

Terraform Associate Notes

Terraform Associate Notes

Mục lục

What?

Exam objectives

NoObjective
1Understand Infrastructure as Code (IaC) concepts
1aExplain what IaC is
1bDescribe advantages of IaC patterns
2Understand Terraform's purpose (vs other IaC)
2aExplain multi-cloud and provider-agnostic benefits
2bExplain the benefits of state
3Understand Terraform basics
3aHandle Terraform and provider installation and versioning
3bDescribe plug-in based architecture
3cDemonstrate using multiple providers
3dDescribe how Terraform finds and fetches providers
3eExplain when to use and not use provisioners and when to use local-exec or remote-exec
4Use the Terraform CLI (outside of core workflow)
4aGiven a scenario: choose when to use terraform fmt to format code
4bGiven a scenario: choose when to use terraform taint to taint Terraform resources
4cGiven a scenario: choose when to use terraform import to import existing infrastructure into your Terraform state
4dGiven a scenario: choose when to use terraform workspace to create workspaces
4eGiven a scenario: choose when to use terraform state to view Terraform state
4fGiven a scenario: choose when to enable verbose logging and what the outcome/value is
5Interact with Terraform modules
5aContrast module source options
5bInteract with module inputs and outputs
5cDescribe variable scope within modules/child modules
5dDiscover modules from the public Terraform Module Registry
5eDefining module version
6Navigate Terraform workflow
6aDescribe Terraform workflow ( Write -> Plan -> Create )
6bInitialize a Terraform working directory (terraform init)
6cValidate a Terraform configuration (terraform validate)
6dGenerate and review an execution plan for Terraform (terraform plan)
6eExecute changes to infrastructure with Terraform (terraform apply)
6fDestroy Terraform managed infrastructure (terraform destroy)
7Implement and maintain state
7aDescribe default local backend
7bOutline state locking
7cHandle backend authentication methods
7dDescribe remote state storage mechanisms and supported standard backends
7eDescribe effect of Terraform refresh on state
7fDescribe backend block in configuration and best practices for partial configurations
7gUnderstand secret management in state files
8Read, generate, and modify configuration
8aDemonstrate use of variables and outputs
8bDescribe secure secret injection best practice
8cUnderstand the use of collection and structural types
8dCreate and differentiate resource and data configuration
8eUse resource addressing and resource parameters to connect resources together
8fUse Terraform built-in functions to write configuration
8gConfigure resource using a dynamic block
8hDescribe built-in dependency management (order of execution based)
9Understand Terraform Cloud and Enterprise capabilities
9aDescribe the benefits of Sentinel, registry, and workspaces
9bDifferentiate OSS and Terraform Cloud workspaces
9cSummarize features of Terraform Cloud

Question Types

https://developer.hashicorp.com/terraform/tutorials/certification/associate-questions

  • True or False
  • Multiple Choice
  • Multiple Answer
  • Text Match

Why?

Benefits

  • Verify skills
  • Expand skills
  • Enterprise features
  • Solidify fundamentals

IAC and its benefits

  • No more clicks
  • Enable DevOps (version control, collaboration)
  • Declarative infrastructure
  • Speed, Cost, Reduced Risk
  • Cloud Agnostic

Workflow

  • write -> collaboration
  • plan -> review
  • apply -> deploy

Terraform commands, concepts

Commands

terraform init
# download ancillary components and set up the backend (state file,...)
terraform plan
# show a plan of execution/deployment, allowing you to review and authenticate credentials

terraform apply
# deploy the instructions, and statements in the code, update the state files

terraform destroy
# destroy all resources in the state file. It would be best if you backed up before you destroyed anything

Resource addressing

# Provider
provider "aws" {
    region = "us-east-1"
}

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

# Resource
# reserved keyword + resource provided by Terraform provider + Use-provided arbitrary resource name
resource "aws_instance" "web" {
    ami = "ami-abc12312"
    instance_type = "t2.micro"
    # resource config arguments
}

# Resource Address
aws_instance.web

# Data source
# A data source block = fetching and tracking details of an existing resource
# A resource block = creates a resource from scratch.
# reserved keyword + resource provided by Terraform provider + Use-provided arbitrary resource name
data "aws_instance" "my-vm" {
    instance_id = "i-123123123"
    # data source arguments
}
# Data source address
data.aws_instance.my-vm
  • Tf executes code in files with the .tf extension

  • TF looks for providers in TF provider registry

  • Task: Create a VM on AWS using TF

main.tf
provider "aws" {
    region = "us-east-1"
}

resource "aws_instance" "vm" {
    ami = "ami-0c4e4b4eb2e11d1d4"
    subnet_id = "subnet-08d180d74f2ef9dfc"
    instance_type = "t3.micro"
}

Installing TF and TF providers

TF

TF providers

  • Abstracting integration with the API control layer
  • Look at the TF registry
  • Providers are plugins
  • TF can use custom providers
  • tf init installs providers
  • Best practice: specific version of provider -> better version control, won't break your code
provider "azurerm" {
    version = "2.20.0"
    features {}
}

provider "aws" {
    version = "3.7.0"
    region = "us-east-1"
}

TF state

Concept

  • Resource tracking
    • Keep tabs on what has been deployed
    • Critical to TF operations
  • terraform.tfstate = mapping configuration with deployed resource
  • can be stored remotely
  • Never lose your tf state file or let it in the wrong hand

TF Variables and Outputs

Variables

# reserved keyword + user-provided variable name
variable "my-var" {
    description = "My test variable"
    type = string
    value = "Hello"
    validation {
        condition = length(var.my_var) > 4
        error_message = "The string must be more than 4 characters"
    }
    # set it to true if you don't want it printed out in the tf execution, false by default
    sensitive = true
}

# you can define a var like this
# however, if not defined in OS env or CLI import otherwise, it will end up in err
variable "my-var" {}

# referencing a var:
var.my_var

# Base types: string, number, bool
# Complex types: list, set, map, object, tuple
# string
variable "image_id" {
    type = string
    default = "hello"
}
# list of string
variable "az_names" {
    type = list(string)
    default = ["us-east-1"]
}
# list of object
variable "docker_ports" {
    type = list(object({
        internal = number
        external = number
        protocol = string
    }))
    default = [
        {
            internal = 8300
            external = 8300
            protocol = "tcp"
        }
    ]
}
  • terraform.tfvars will be picked up by default

Outputs

# reserved keyword + user-provided var name
output "instance_ip" {
    description = "VM's private  IP"
    values = aws_instance.my-vm.private_ip
}
  • Output vars values are shown on the shell after running tf apply
  • Think as return values to track after a success the tf deployment

TF Provisioners

  • Bootstrapping custom scripts, commands, or actions
  • Can be run locally or remotely
  • Individual resources can have their own "provisioner"
  • 2 types:
    • creation-time
    • destroy-time
  • Best practice:

    In the case of provisioners, HashiCorp recommends using them sparingly and only when the underlying vendors, such as AWS, do not already provide a built-in mechanism for bootstrapping via custom commands or scripts. For example, AWS allows passing scripts through user data in EC2 virtual machines. So if there's a better inherently available method for a resource, Hashicorp recommends using that.

  • TF cannot track changes to provisioners in state files
  • Only when you want to invoke actions not covered by TF declarative model.
  • Expected provisioner return non-zero return code; otherwise, the resource will be trained and re-create again on the subsequent execution
  • Sample code
resource  "null_resource" "dummy_resource" {
    provisioner "local-exec" {
        command = "echo '0' > status.txt"
    }

    provisioner "local-exec" {
        when = destroy
        command = "echo '1' > status.txt"
    }
}
resource "aws_instance" "ec2-vm" {
    ami = "ami"
    instance_type = "t3.micro"
    subnet_id = "subnet_id"
    provisioner "local-exec" {
        command = "aws ec2 wait instance-status-ok --region us-east-1 --instance-ids ${self.id}"
    }
}
# And so Hashicorp has provided the self-object can access any attribute available to the resource the provisioner is attached to.

TF State Commands

  • maps real-world resource -> tf config
  • by default, it saves in the terraform.tfstate file
  • tf refresh state file before any modification
  • resource dependency metadata also included

State commands

  • manipulate and read the tf state file
  • scenario:
    • advanced state management
    • manually remove a resource
    • list out tracked resources and their details
# list all resources tracked by state file
tf state list
# delete a resource from the state file tracking
tf state rm
# show details of a resource
tf state show

Local and remote state storage

  • Local state storage

    • save locally on your system by default
  • Remote state storage

    • S3, Google Storage
    • Allows sharing of state files between teams
    • State files can use remote access to limit the access
  • allow locking state so parallel execution doesn't coincide

  • enable sharing output values with other tf config or code

  • Demo: Persisting TF state in AWS S3

aws --profile vntechies s3api create-bucket --bucket vntechies-tf-samples
main.tf
provider "docker" {}

resource "docker_image" "nginx-image" {
  name = "nginx"
}

resource "docker_container" "nginx" {
  image = docker_image.nginx-image.latest
  name  = "nginx"
  ports {
    internal = 80
    external = var.external_port
    protocol = "tcp"
  }
}

output "url" {
  description = "Browser URL for container site"
  value       = join(":", ["http://localhost", tostring(var.external_port)])
}
backend.tf
terraform {
  required_providers {
    docker = {
      source = "kreuzwerker/docker"
    }
  }
  required_version = ">=1.0"
  backend "s3" {
    profile = "vntechies"
    region  = "us-east-1"
    key     = "terraform.tfstate"
    bucket  = "vntechies-tf-sample"
  }
}
variable.tf
variable "external_port" {
  type    = number
  default = 8080
  validation {
    condition     = can(regex("8080|80", var.external_port))
    error_message = "Port value can only be 8080 or 80."
  }
}
aws --profile vntechies s3 ls s3://vntechies-tf-samples/

TF Modules

  • container for multiple resources that are used together
  • tf config has at least one module called root
  • can be downloaded or referenced from:
    • tf public registry
    • private registry
    • local system
  • can take inputs and provide outputs
# reserved keyword + module name
module "my-vpc-module" {
    source = "./modules/vpc" # module source (mandatory)
    version = "0.0.5" # module version
    region = var.region # input params for module
}
# allowed params: count, for_each, providers, depends_on
  • Interact with module inputs/outputs
module "my-vpc-module" {
    source = "./modules/vpc"
    server-name = var.server-name # refers to vars
}
output "ip_address" {
    value = aws_instance.private_ip
}
# refers to module out put
module.my_vpc-module.subnet_id
  • Lab: building and testing basic tf module
alias tf=terraform
mkdir -p terraform/module/vpc
touch terraform/module/vpc/main.tf terraform/module/vpc/variables.tf terraform/module/vpc/outputs.tf terraform/main.tf
cd terraform/module/vpc/
/module/vpc/variables.tf
variable "region" {
  type    = string
  default = "us-east-1"
}
/module/vpc/main.tf
provider "aws" {
  region = var.region
}

resource "aws_vpc" "this" {
  cidr_block = "10.0.0.0/16"
}

resource "aws_subnet" "this" {
  vpc_id     = aws_vpc.this.id
  cidr_block = "10.0.1.0/24"
}

data "aws_ssm_parameter" "this" {
  name = "/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2"
}
/module/vpc/outputs.tf
output "subnet_id" {
  value = aws_subnet.this.id
}

output "ami_id" {
  value = data.aws_ssm_parameter.this.value
}
variable "main_region" {
  type    = string
  default = "us-east-1"
}

provider "aws" {
  region = var.main_region
}

module "vpc" {
  source = "./module/vpc"
}

resource "aws_instance" "my-instance" {
  ami           = module.vpc.ami_id
  subnet_id     = module.vpc.subnet_id
  instance_type = "t3.micro"
}
output "PrivateIP" {
  description = "Private IP of EC2 instance"
  value       = aws_instance.my-instance.private_ip
}

TF built-in functions

  • pre-packaged with functions to transform and combine values
  • user-defined functions are not allowed, only built-in
  • general syntax: function_name(arg1, arg2,...)
  • Built-in functions
join("-", ["terraform", var.project-name])
# file, max, flatten
tf console

> max(12,3,4,24,14)
24
> timestamp()
"2022-10-29T00:54:07Z"
> join("-",["test","11232"])
"test-11232"
> contains(["test",1,2,3,3], 2)
true
> contains(["test",1,2,3,3], 5)
false

TF Type Constraints

  • Primitive
    • number
    • string
    • bool
  • Complex
    • list
    • tuple
    • map
    • object
  • Collections
    • list(type)
    • map(type)
    • set(type)
  • Structural
    • object(type)
    • tuple(type)
    • set(type)
  • Dynamic Type
    • any
    • the best effort to decide the type

TF Dynamic blocks

  • construct repeatable nested configuration blocks
  • support block types:
    • resource
    • data
    • provider
    • provisioner
variable "rules" {
  default = [
    {
      port        = 80
      protocol    = "tcp"
      cidr_blocks = ["0.0.0.0/0"]
    },
    {
      port        = 22
      protocol    = "tcp"
      cidr_blocks = ["1.2.3.4/32"]
    }
  ]

}

resource "aws_security_group" "my-sg" {
  name   = "my-aws-sg"
  vpc_id = aws_vpc.my-vpc.id
  dynamic "ingress" {
    for_each = var.rules
    content {
      from_port  = ingress.values["port"]
      to_port    = ingress.values["port"]
      protocol   = ingress.values["protocol"]
      cidr_block = ingress.values["cirds"]
    }
  }
}
  • Lab: Using Terraform Dynamic Blocks and Built-in Functions to Deploy to AWS
alias tf=terraform
touch main.tf variables.tf outputs.tf script.sh
variables.tf
variable "rules" {
  type = list(object({
    port        = number
    protocol    = string
    cidr_blocks = list(string)
  }))

  default = [{
    cidr_blocks = ["0.0.0.0/0"]
    port        = 80
    protocol    = "tcp"
    }, {
    cidr_blocks = ["0.0.0.0/0"]
    port        = 22
    protocol    = "tcp"
    }, {
    cidr_blocks = ["6.7.8.9/32"]
    port        = 3689
    protocol    = "tcp"
  }]
}
main.tf
provider "aws" {
  region = "us-east-1"
}

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

module "vpc" {
  source = "terraform-aws-modules/vpc/aws"

  name = "my-vpc"
  cidr = "10.0.0.0/16"

  azs            = ["us-east-1a"]
  public_subnets = ["10.0.1.0/24"]
}

resource "aws_security_group" "my-sg" {
  vpc_id = module.vpc.vpc_id
  name   = join("_", ["sg", module.vpc.vpc_id])
  dynamic "ingress" {
    for_each = var.rules
    content {
      from_port   = ingress.value["port"]
      to_port     = ingress.value["port"]
      protocol    = ingress.value["protocol"]
      cidr_blocks = ingress.value["cidr_blocks"]
    }
  }
  egress {
    cidr_blocks = ["0.0.0.0/0"]
    from_port   = 0
    protocol    = "-1"
    to_port     = 0
  }
  tags = {
    "Name" = "Terraform-Dynamic-SG"
  }
}

resource "aws_instance" "my-instance" {
  ami             = data.aws_ssm_parameter.ami_id.value
  subnet_id       = module.vpc.public_subnets[0]
  instance_type   = "t3.micro"
  security_groups = [aws_security_group.my-sg.id]
  user_data       = fileexists("script.sh") ? file("script.sh") : null
}
outputs.tf
output "Web-Server-URL" {
  description = "Web server URL"
  value       = join("", ["http://", aws_instance.my-instance.public_ip])
}

output "Time-Date" {
  description = "Date/time of execution"
  value       = timestamp()
}
script.sh
#!/bin/bash
sudo yum -y install httpd
sudo systemctl start httpd && sudo systemctl enable httpd

TF fmt, taint, import

TF fmt

  • Format for code readability
  • Safe to run anytime
  • Helps in keeping code consistent
tf fmt

TF taint

  • taint a resource, forcing it to be destroyed and recreated
  • modifies the state file case the recreation workflows
  • may cause others to be modified
tf taint RESOURCE_ADD
  • When
    • to cause provisioners to run
    • replace misbehaving resources
    • mimic side effects of recreation not modeled by any attributes of the resource

TF import

  • map existing resources to tf using an ID

  • ID depends on underlying vendors

  • import the same resource to multiple tf resources can cause unknown behavior and is not recommende

    d

tf import Resource_address ID
  • When:
    • need to work with existing resources
    • not allowed to create new resources
    • not in control of the creation process

TF config blocks

  • config block for controlling tf own behavior
  • only allows constant, named resources, and variables are not allowed
terraform {
  required_version = ">=1.0"
  required_providers {
    aws = ">=3.0"
}

TF workspaces (CLI)

  • alternate state files within the same working dir
  • tf starts with default workspace, cannot be deleted
# create a workspace
tf workspace new WORKSPACE_NAME
# select a workspace
tf workspace select WORKSPACE_NAME
  • Scenario:

    • Test changes using a parallel, distinct copy of infra
    • can be modeled against branches in version control
  • Meant to share resources and help enable collaboration

  • Access to workspace ${terraform.workspace}

  • Examples:

resource "aws_instance" "example" {
  count = terraform.workspace == "default" ? 5 : 1
}

resource "aws_s3_bucket" "bucket" {
  bucket = "bucket-${terraform.workspace}"
  acl = "private"
}
  • Demo: Workspace
touch main.tf network.tf
tf workspace new test
network.tf
resource "aws_vpc" "vpc_master" {
  cidr_block = "10.0.0.0/16"
  tags = {
    Name = "${terraform.workspace}-vpc"
  }
}

data "aws_availability_zones" "azs" {
  state = "available"
}

resource "aws_subnet" "subnet" {
  availability_zone = element(data.aws_availability_zones.azs.names, 0)
  vpc_id            = aws_vpc.vpc_master.id
  cidr_block        = "10.0.1.0/24"

  tags = {
    Name = "${terraform.workspace}-subnet"
  }
}

resource "aws_security_group" "sg" {
  name        = "${terraform.workspace}-sg"
  description = "Allow TCP 22"
  vpc_id      = aws_vpc.vpc_master.id
  ingress {
    description = "Allow 22 from public IP"
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
  tags = {
    Name = "${terraform.workspace}-sg"
  }
}
main.tf
provider "aws" {
  region = "us-east-1"
}

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

resource "aws_instance" "ec2-vm" {
  ami                         = data.aws_ssm_parameter.ami_id.value
  instance_type               = "t3.micro"
  associate_public_ip_address = true
  vpc_security_group_ids      = [aws_security_group.sg.id]
  subnet_id                   = aws_subnet.subnet.id
  tags = {
    Name = "${terraform.workspace}-ec2"
  }
}

Debugging TF

  • TF_LOG -> stderr
  • levels: TRACE, DEBUG, INFO, WARN, ERROR
  • TF_LOG_PATH
export TF_LOG=TRACE
export TF_LOG_PATH=./terraform.log

TF Enterprise

Sentinel

  • Enforces policies on your code
  • Has its own policy language
  • designed to be approached by a non-programmer
  • Benefits
    • Sandboxing
    • Codification
    • Version control
    • Testing and automation
  • Use cases
    • For enforcing CIS standards
    • Checking to make sure only t3.micro instance types are used
    • Ensure SG don't allow traffic on port 22
import "tfplan"
main = rule {
  all tfplan.resouces.aws_instance as _, instances {
    all instances as _, f {
      (length(r.applied.tags) else 0) > 0
    }
  }
}

Best Practice: Vault Provider

  • Secret management software
  • dynamically provisions credentials and rotates them
  • encrypt sensitive data in transit and reset, provide fine-grained access to secrets using ACLs
  • inject secrets using Vault Provider
  • Benefits:
    • Don't need long-lived credentials
    • Inject secrets to TF deployment at runtime
    • Fine-grained ACLs for access to temp credentials

TF Registry, TF Cloud workspaces

TF Registry

  • The repository of publicly available TF providers and modules
  • you can publish and share your modules and collaborate with others

TF Cloud workspaces

  • dir hosted in TF cloud
  • store old versions of state files by default
  • maintain a record of all execution
  • all TF commands executed on "managed" TF Cloud VMs

TF OSS workspaces and TF Cloud

ComponentWorkspaceCloud workspace
Terraform ConfigurationOn diskIn linked version control repository or periodically uploaded via API/CLI
Variable ValuesAs .tfvars files, as CLI arguments, or in shell environmentIn workspace (in TF Cloud)
StateOn disk or in remote backendIn workspace (in TF Cloud)
Credentials and SecretsIn shell environment or entered at promptsIn workspace (in TF Cloud), stored as sensitive variables

Benefits

  • Remote TF execution
  • Workspace-based org model
  • Version control integration
  • Remote state management and CLI integration
  • Private TF module registry
  • Cost estimation and Sentinel integration

Other resources

https://developer.hashicorp.com/terraform/tutorials/certification/associate-review

  • Study guide
  • Exam review
  • Sample questions

Preperation

  • Basic understanding of public cloud
  • Security best practice (Sentinel and Vault provider)
  • Know TF workflow (write > plan >apply)
  • Commands
init state plan fmt apply validate
  • TF state mechanism very well

  • Familiarize yourself with HCL

  • Know the difference between TF OSS and Enterprise offerings

  • Get hand on!

  • Reminder: TF version of the exam

  • 57 - 60 questions

  • flag questions and come back later

Courses

VNTechies Dev Blog

Kho tài nguyên mã nguồn mở với sứ mệnh đào tạo kiến thức, định hướng nghề nghiệp cho cộng đồng Cloud ☁️ DevOps 🚀

Facebook page

Tham gia group VNTechies - Cloud ☁️ / DevOps 🚀 nếu bạn muốn giao lưu với cộng đồng và cập nhật các thông tin mới nhất về Cloud và DevOps.

Discord banner

Anh chị em hãy follow/ủng hộ VNTechies để cập nhật những thông tin mới nhất về Cloud và DevOps nhé!