My Terraform Learnings, more focused in AWS.
I used the Terraform v1.10.3
The most part of the content and learnings of this repo, is based on my studies of the Daniel Gil's Terraform Course.
export AWS_ACCESS_KEY_ID="access_key"
export AWS_SECRET_ACCESS_KEY="secret_access_key"
SET AWS_ACCESS_KEY_ID=access_key
SET AWS_SECRET_ACCESS_KEY=secret_access_key
$env:AWS_ACCESS_KEY_ID="access_key"
$env:AWS_SECRET_ACCESS_KEY="secret_access_key"
export ARM_CLIENT_ID=client_id
export ARM_TENANT_ID=tenant_id
export ARM_SUBSCRIPTION_ID=subscription_id
export ARM_CLIENT_SECRET=client_secret
SET ARM_CLIENT_ID=client_id
SET ARM_TENANT_ID=tenant_id
SET ARM_SUBSCRIPTION_ID=subscription_id
SET ARM_CLIENT_SECRET=client_secret
$env:ARM_CLIENT_ID="client_id"
$env:ARM_TENANT_ID="tenant_id"
$env:ARM_SUBSCRIPTION_ID="subscription_id"
$env:ARM_CLIENT_SECRET="client_secret"
- Languages (HCL and JSON)
The main purpose of the Terraform language is declaring resources, which represent infrastructure objects. All other language features exist only to make the definition of resources more flexible and convenient. - Terraform Registry
It is a central repository for Terraform modules, providers, policies, and run tasks. - Providers
Providers are a logical abstraction of an upstream API. They are responsible for understanding API interactions and exposing resources. - Modules
Modules are self-contained packages of Terraform configurations that are managed as a group.
- Hidden Directory
.terraform
- Directory where the Modules and Providers are saved - File
.tfvars
- Declare variables - File
.tfstate
- Save the current state of the infrastructure - File
.tfstate.backup
- Save the previous state from the current state - File
.terraform.lock.hcl
- Is a "dependency lock file", used to control the versions of our Providers
Types of Blocks:
- Terraform
- Providers
- Resources
- Data
- Module
- Variable
- Output
- Locals
terraform {
}
provider "aws" {
}
resource "aws_instance" "vm1" {
}
data "aws_ami" "image" {
}
module "network" {
}
variable "vm_name" {
}
output "vm1_ip" {
}
locals {
}
- Terraform
- Possible Parameters:
required_version
- Specifies the version(s) of Terraform that can be used with the configuration.
required_provider
- Declares the providers required for the configuration and their versions.
backend
- Configures the backend to store and manage the Terraform state.
cloud
- Configures HCP(HashiCorp Cloud Platform - old Terraform Cloud) for managing state and running plans.
experiments
- Enables experimental or beta features of Terraform.
provider_meta
- Passes metadata to Terraform Providers.
- Possible Parameters:
#Terraform Block Example
terraform {
required_version = "~> 1.0.0" #1.0.0 até 1.0.x
required_providers {
aws = {
version = "1.0"
source = "hashicorp/aws"
}
azurerm = {
version = "2.0"
source = "hashicorp/aws"
}
}
backend "s3" {
}
}
terraform -help
- Displays help for Terraform commands and their usage.init
- Prepare your working directory for other commands.fmt
- Reformat your configuration in the standard style.validate
- Check whether the configuration is valid.plan
- Show changes required by the current configuration.apply
- Create or update infrastructure.destroy
- Destroy previously-created infrastructure.
show
- is used to provide human-readable output from a state or plan file. This can be used to inspect a plan to ensure that the planned operations are expected, or to inspect the current state as Terraform sees it.state
- is used for advanced state management. As your Terraform usage becomes more advanced, there are some cases where you may need to modify the Terraform state. Rather than modify the state directly, theterraform state
commands can be used in many cases instead.import
- Terraform can import existing infrastructure resources. This functionality lets you bring existing resources under Terraform management.refresh
- Theterraform refresh
command reads the current settings from all managed remote objects and updates the Terraform state to match.get
- Theget
command is used to download and update modules mentioned in the root module.console
- Theterraform console
command provides an interactive console for evaluating expressions.
Input variables let you customize aspects of Terraform modules without altering the module's own source code. This functionality allows you to share modules across different Terraform configurations, making your module composable and reusable.
Example:
variable "image_id" {
type = string
}
variable "availability_zone_names" {
type = list(string)
default = ["us-west-1a"]
}
variable "docker_ports" {
type = list(object({
internal = number
external = number
protocol = string
}))
default = [
{
internal = 8300
external = 8300
protocol = "tcp"
}
]
}
Declaring the Variables above:
Syntax: var.name_of_variable
provider "aws" {
region = var.region
}
resource "aws_instance" "example" {
ami = var.image_id
instance_type = "t2.micro"
availability_zone = element(var.availability_zone_names, 0)
}
resource "docker_container" "example" {
image = "nginx:latest"
name = "example-nginx"
ports {
internal = var.docker_ports[0].internal
external = var.docker_ports[0].external
protocol = var.docker_ports[0].protocol
}
}
# Declare the variables
variable "region" {
description = "Region where resources will be provisioned on AWS"
type = string
default = "us-west-1"
}
When variables are declared in the root module of your configuration, they can be set in a number of ways:
- In an HCP Terraform workspace.
- Individually, with the
-var
command line option. - In variable definitions (
.tfvars
) files, either specified on the command line or automatically loaded. - As environment variables.
Local Values in Terraform are variables defined within your configuration file to simplify and modularize the code. They allow you to create values that can be reused in multiple parts of the configuration, which helps avoid repetition and makes the code cleaner and easier to maintain.
Example:
locals {
common_tags = {
owner = "juan"
managed-by = "terraform"
}
}
Output values make information about your infrastructure available on the command line, and can expose information for other Terraform configurations to use. Output values are similar to return values in programming languages.
Example:
output "storage_account_id" {
description = "Storage Account ID created in Azure"
value = azurerm_storage_account.storage_account.id
}
Terraform must store state about your managed infrastructure and configuration. This state is used by Terraform to map real world resources to your configuration, keep track of metadata, and to improve performance for large infrastructures.
- Remote state is implemented by a backend or by HCP Terraform, both of which you can configure in your configuration's root module.
The backend defines where Terraform stores its state data files.
To configure a backend, add a nested backend block within the top-level terraform block. The following example configures the remote backend.
#S3 Example
terraform {
backend "s3" {
bucket = "mybucket"
key = "path/to/my/key"
region = "us-east-1"
}
}
The terraform_remote_state
data source uses the latest state snapshot from a specified state backend to retrieve the root module output values from some other Terraform configuration.
You can use provisioners to model specific actions on the local machine or on a remote machine in order to prepare servers or other infrastructure objects for service.
** Provisioners are a last resource.
** Instead, use user-data at AWS.
Modules are containers for multiple resources that are used together. A module consists of a collection of .tf
and/or .tf.json
files kept together in a directory.
Modules are the main way to package and reuse resource configurations with Terraform.
- Local Modules
- Remote Modules
Meta-arguments are special parameters used in resource blocks to control how resources are created and managed. They provide a way to dynamically configure resources and make your Terraform configurations more flexible and reusable.
- Resource
- depends_on
- count
- for_each
- provider
- lifecycle
- Module
- depends_on
- count
- for_each
- provider
Use the depends_on
meta-argument to handle hidden resource or module dependencies that Terraform cannot automatically infer. You only need to explicitly specify a dependency when a resource or module relies on another resource's behavior but does not access any of that resource's data in its arguments.
count
is a meta-argument defined by the Terraform language. It can be used with modules and with every resource type.
The count
meta-argument accepts a whole number, and creates that many instances of the resource or module. Each instance has a distinct infrastructure object associated with it, and each is separately created, updated, or destroyed when the configuration is applied.
resource "aws_instance" "server" {
count = 4 # create four similar EC2 instances
ami = "ami-a1b2c3d4"
instance_type = "t2.micro"
tags = {
Name = "Server ${count.index}"
}
}
for_each
is a meta-argument defined by the Terraform language. It can be used with modules and with every resource type.
The for_each
meta-argument accepts a map or a set of strings, and creates an instance for each item in that map or set. Each instance has a distinct infrastructure object associated with it, and each is separately created, updated, or destroyed when the configuration is applied.
# my_buckets.tf
module "bucket" {
for_each = toset(["assets", "media"])
source = "./publish_bucket"
name = "${each.key}_bucket"
}
The provider
meta-argument specifies which provider configuration to use for a resource, overriding Terraform's default behavior of selecting one based on the resource type name. Its value should be an unquoted <PROVIDER>.<ALIAS>
reference.
By default, Terraform interprets the initial word in the resource type name (separated by underscores) as the local name of a provider, and uses that provider's default configuration. For example, the resource type google_compute_instance
is associated automatically with the default configuration for the provider named google
.
By using the provider meta-argument, you can select an alternate provider
configuration for a resource:
# default configuration
provider "google" {
region = "us-central1"
}
# alternate configuration, whose alias is "europe"
provider "google" {
alias = "europe"
region = "europe-west1"
}
resource "google_compute_instance" "example" {
# This "provider" meta-argument selects the google provider
# configuration whose alias is "europe", rather than the
# default configuration.
provider = google.europe
# ...
}
lifecycle
is a nested block that can appear within a resource block. The lifecycle
block and its contents are meta-arguments, available for all resource
blocks regardless of type.
The arguments available within a lifecycle
block are create_before_destroy
, prevent_destroy
, ignore_changes
, and replace_triggered_by
.
resource "azurerm_resource_group" "example" {
# ...
lifecycle {
create_before_destroy = true
}
}
A conditional expression uses the value of a boolean expression to select one of two values.
The syntax of a conditional expression is as follows:
condition ? true_val : false_val
If condition
is true
then the result is true_val
. If conditio
is false
then the result is false_val
.
A common use of conditional expressions is to define defaults to replace invalid values:
var.a != "" ? var.a : "default-a"
A for
expression creates a complex type value by transforming another complex type value. Each element in the input value can correspond to either one or zero values in the result, and an arbitrary expression can be used to transform each input element into an output element.
For example, if var.list
were a list of strings, then the following expression would produce a tuple of strings with all-uppercase letters:
[for s in var.list : upper(s)]
A splat expression provides a more concise way to express a common operation that could otherwise be performed with a for
expression.
If var.list
is a list of objects that all have an attribute id
, then a list of the ids could be produced with the following for
expression:
[for o in var.list : o.id]
This is equivalent to the following splat expression:
var.list[*].id
Within top-level block constructs like resources, expressions can usually be used only when assigning a value to an argument using the name = expression
form. This covers many uses, but some resource types include repeatable nested blocks in their arguments, which typically represent separate objects that are related to (or embedded within) the containing object:
resource "aws_elastic_beanstalk_environment" "tfenvtest" {
name = "tf-test-name" # can use expressions here
setting {
# but the "setting" block is always a literal block
}
}
You can dynamically construct repeatable nested blocks like setting
using a special dynamic
block type, which is supported inside resource
, data
, provider
, and provisioner
blocks:
resource "aws_elastic_beanstalk_environment" "tfenvtest" {
name = "tf-test-name"
application = aws_elastic_beanstalk_application.tftest.name
solution_stack_name = "64bit Amazon Linux 2018.03 v2.11.4 running Go 1.12.6"
dynamic "setting" {
for_each = var.settings
content {
namespace = setting.value["namespace"]
name = setting.value["name"]
value = setting.value["value"]
}
}
}
The Terraform language includes a number of built-in functions that you can call from within expressions to transform and combine values. The general syntax for function calls is a function name followed by comma-separated arguments in parentheses:
max(5, 12, 9)
There's a lot of types of functions, like:
- Numeric Functions
- String Functions
- Collection Functions
- Encoding Functions
- Filesystem Functions
- Date and Time Functions
- Hash and Crypto Functions
- IP Network Functions
- Type Conversion Functions
- Terraform-specific Functions
In Terraform, a workspace is like a separate environment where you can manage resources independently using the same configuration files. ** Cannot use Workspaces when your environments needs different credentials.
Data sources allow Terraform to use information defined outside of Terraform, defined by another separate Terraform configuration, or modified by functions.
A data source is accessed via a special kind of resource known as a data resource, declared using a data block:
data "aws_ami" "example" {
most_recent = true
owners = ["self"]
tags = {
Name = "app-server"
Tested = "true"
}
}
-replace=ADDRESS
- Instructs Terraform to plan to replace the resource instance with the given address. This is helpful when one or more remote objects have become degraded, and you can use replacement objects with the same configuration to align with immutable infrastructure patterns.-target=ADDRESS
- Instructs Terraform to focus its planning efforts only on resource instances which match the given address and on any objects that those instances depend on.
Manages a resource that delays creation and/or destruction, typically for further resources. This prevents cross-platform compatibility and destroy-time issues with using the local-exec
provisioner.