Terraform Associate Notes

Mục lục
- What?
 - Exam objectives
 - Question Types
 - Why?
 - Benefits
 - IAC and its benefits
 - Workflow
 - Terraform commands, concepts
 - Commands
 - Resource addressing
 - Installing TF and TF providers
 - TF
 - TF providers
 - TF state
 - Concept
 - TF Variables and Outputs
 - Variables
 - Outputs
 - TF Provisioners
 - TF State Commands
 - State commands
 - Local and remote state storage
 - TF Modules
 - TF built-in functions
 - TF Type Constraints
 - TF Dynamic blocks
 - TF fmt, taint, import
 - TF fmt
 - TF taint
 - TF import
 - TF config blocks
 - TF workspaces (CLI)
 - Debugging TF
 - TF Enterprise
 - Sentinel
 - Best Practice: Vault Provider
 - TF Registry, TF Cloud workspaces
 - TF Registry
 - TF Cloud workspaces
 - TF OSS workspaces and TF Cloud
 - Benefits
 - Other resources
 - Preperation
 - Courses
 - VNTechies Dev Blog
 
What?
Exam objectives
| No | Objective | 
|---|---|
| 1 | Understand Infrastructure as Code (IaC) concepts | 
| 1a | Explain what IaC is | 
| 1b | Describe advantages of IaC patterns | 
| 2 | Understand Terraform's purpose (vs other IaC) | 
| 2a | Explain multi-cloud and provider-agnostic benefits | 
| 2b | Explain the benefits of state | 
| 3 | Understand Terraform basics | 
| 3a | Handle Terraform and provider installation and versioning | 
| 3b | Describe plug-in based architecture | 
| 3c | Demonstrate using multiple providers | 
| 3d | Describe how Terraform finds and fetches providers | 
| 3e | Explain when to use and not use provisioners and when to use local-exec or remote-exec | 
| 4 | Use the Terraform CLI (outside of core workflow) | 
| 4a | Given a scenario: choose when to use terraform fmt to format code | 
| 4b | Given a scenario: choose when to use terraform taint to taint Terraform resources | 
| 4c | Given a scenario: choose when to use terraform import to import existing infrastructure into your Terraform state | 
| 4d | Given a scenario: choose when to use terraform workspace to create workspaces | 
| 4e | Given a scenario: choose when to use terraform state to view Terraform state | 
| 4f | Given a scenario: choose when to enable verbose logging and what the outcome/value is | 
| 5 | Interact with Terraform modules | 
| 5a | Contrast module source options | 
| 5b | Interact with module inputs and outputs | 
| 5c | Describe variable scope within modules/child modules | 
| 5d | Discover modules from the public Terraform Module Registry | 
| 5e | Defining module version | 
| 6 | Navigate Terraform workflow | 
| 6a | Describe Terraform workflow ( Write -> Plan -> Create ) | 
| 6b | Initialize a Terraform working directory (terraform init) | 
| 6c | Validate a Terraform configuration (terraform validate) | 
| 6d | Generate and review an execution plan for Terraform (terraform plan) | 
| 6e | Execute changes to infrastructure with Terraform (terraform apply) | 
| 6f | Destroy Terraform managed infrastructure (terraform destroy) | 
| 7 | Implement and maintain state | 
| 7a | Describe default local backend | 
| 7b | Outline state locking | 
| 7c | Handle backend authentication methods | 
| 7d | Describe remote state storage mechanisms and supported standard backends | 
| 7e | Describe effect of Terraform refresh on state | 
| 7f | Describe backend block in configuration and best practices for partial configurations | 
| 7g | Understand secret management in state files | 
| 8 | Read, generate, and modify configuration | 
| 8a | Demonstrate use of variables and outputs | 
| 8b | Describe secure secret injection best practice | 
| 8c | Understand the use of collection and structural types | 
| 8d | Create and differentiate resource and data configuration | 
| 8e | Use resource addressing and resource parameters to connect resources together | 
| 8f | Use Terraform built-in functions to write configuration | 
| 8g | Configure resource using a dynamic block | 
| 8h | Describe built-in dependency management (order of execution based) | 
| 9 | Understand Terraform Cloud and Enterprise capabilities | 
| 9a | Describe the benefits of Sentinel, registry, and workspaces | 
| 9b | Differentiate OSS and Terraform Cloud workspaces | 
| 9c | Summarize 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
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
- Download, Unzip, and Use
 - Setup TF repo on Linux (only)
 - Resources
 
TF providers
- Abstracting integration with the API control layer
 - Look at the TF registry
 - Providers are plugins
 - TF can use custom providers
 tf initinstalls 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.tfstatefile - 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
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)])
}
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 "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/
variable "region" {
  type    = string
  default = "us-east-1"
}
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"
}
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
Resources:
tf console
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
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"
  }]
}
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
}
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()
}
#!/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 
defaultworkspace, 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
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"
  }
}
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
| Component | Workspace | Cloud workspace | 
|---|---|---|
| Terraform Configuration | On disk | In linked version control repository or periodically uploaded via API/CLI | 
| Variable Values | As .tfvars files, as CLI arguments, or in shell environment | In workspace (in TF Cloud) | 
| State | On disk or in remote backend | In workspace (in TF Cloud) | 
| Credentials and Secrets | In shell environment or entered at prompts | In 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
- https://www.udemy.com/course/terraform-beginner-to-advanced/
 - HashiCorp Certified: Terraform Associate 2023
 - HashiCorp Certified: Terraform Associate Practice Exam 2023
 
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 🚀
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.
- Website: https://vntechies.dev
 - Github repository: https://github.com/vntechies
 - Facebook page: https://facebook.com/vntechies
 
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é!

