Terraform Apps and Infra 3 - Fundamentals
CLI
Basics
1 | # check |
Plan deploy and clean up
1 | # output a deployment plan |
Syntax
Hashicorp Configuration Language (HCL)
tf syntax (default HCL)
Syntax
1
2
3
4
5
6
7
8resource "aws_vpc" "main" {
cidr_block = var.base_cidr_block
}
<BLOCK TYPE> "<BLOCK LABEL>" "<BLOCK LABEL>"{
# block body
<IDENTIFIER> = EXPRESSION # argument
}Components
- blocks: containers for objects like resources.
- arguments: assign a value to a name.
- expressions: represent a value.
- identifers: names (types, resources, variables)
- commnents
1
2
3
4
5
6# single line comment
// single line comment
/*
multi-line comment
multi-line comment
*/
Example
.tf
1
2
3
4
5
6
7variable "example": {
default = "hello"
}
resource "aws_instance" "example": {
instance_type = "t2.micro"
ami = "ami-abc123"
}
tf.json syntax
- Example tf.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18{
"variable": {
"example": {
"default": "hello"
}
}
}
{
"resource": {
"aws_instance": {
"example": {
"instance_type": "t2.micro",
"ami": "ami-abc123"
}
}
}
}
Directories and files
- file extension:
.tf
or.tf.json
. - encoding: UTF-8 (recommended) or CLRF.
- modules
- collection of Terraform files on a directory.
- module: a module is the top level config files on directory. Nested directories are not included, they are considered other modules.
- root module: working directory wjere Terrafrom is invokled. Terraform configuration consist of root module + some child modules.
Resources
Definition
1
2
3
4resource "aws_instance" "web" {
ami = "ami-a1b2c3d4"
instance_type = "t2.micro"
}Resource types
- providers: plugins for Terraform, offers a collection for resource types
- arguments: specific to the selected resource typoes
- documentation: which every provioder uses to describes its resource types and arguments
Meta arguments
depends_on
: specify hidden dependenciescount
: create multiple resource instances according to a countfor_each
: create multiple instances according to a map or set of stringsprovider
: select a non-default provider configurationlifecyle
: set lifecycle configurationsprovisioners
andconnection
: take extra actions after resource creation
Operation timeouts
1
2
3
4
5resource "aws_db_instance" "example" {
# ...
create = "60m"
delete = "2h"
}How to apply configs
create
: create resources that exist in the config, but are not associated with real infra object in the state.destroy
: destroy resources that exist in the state but no longer in the config.update in-place
: update in-place resources whose arguments have existed.destroy and re-create
: destroy and re-create resources whose arguments have changed, but which cannot be updated in-place due to remote API limitations.
Resource behaviour
- accessing resource attributes
- expression with Terraform modules.
- read-only attributes with info obtained from remote APIs.
- providers included data sources.
- resource dependencies
- most secure dependencies are handled automatically.
- Terraform analyzes expressions within a resource block to find references to other objects, and treats those referencies as ordering requirements when creating, updating or destroying resources.
- it’s usually not mecessary to manually specify dependencies cannot be recognized implicitly in the configuration.
- local resources only
- specialized resources types that operate only within Terraform itself, calculating some results and saving those results in the state for future use (ssh-keys, self-signed certs…)
- accessing resource attributes
Variables
Input
Reserved words
source
version
providers
count
for_each
lifecycle
depends_on
local
Optional arguments for variable declarations
default
(it makes the var optional)type
(types accepted)string
number
bool
list<type>
set<type>
map<type>
object({<attribute> = <type>, ...})
tuple[<type>, ...]
description
(documentation)validation
(block validation rules, in addition to types)sensitive
(limit Terraform UI output)
Examples
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18variable "image_id" {
type = string
description = "The id of the machine (AMI) to use for the server"
validations {
# check access to values uin var
condition = length(var.image_id) > 4 && substr(var.image.id, 0, 4)=="ami-"
error_message = "The image id value must be a valid AMI id, starting with \"ami-\"."
}
}
variable "user_information" {
type = object ({
name = string
address = string
})
sensitive = true
}Assign value to variables
- Environment variables
1
2export TF_VAR_image_id=ami_123abc
terraform plan - variable definition files (
.tfvars
,..tfvars.json
)- Examples
1
terraform apply -var-file="testing.tfvars"
1
2
3
4
5image_id = "ami-123abc"
availability_zone_names = [
"eu-west-1",
"eu-west-2"
] - Automatically loaded files
terraform.tfvars
,terraform.tfvars.json
- any files ending with
.auto.tfvars
,.auto.tfvars.json
- Examples
- CLI:
-var
1
terraform apply -var="image_id=ami-123abc" -var='image_list=["ami-456drf","image_id=ami-789ghi"]'
- Terrafrom workspace
- Environment variables
Output
Definition
1
2
3
4
5
6
7
8
9
10
11
12
13output "instance_ip_addr"
availability_zone_names = aws_instance.server.private_ip
description = "The private IP od the main server instance"
# last resort, add comment explaining why
depends_on[
/*
security group rule must be created before this IP address could be
actually used, otherwise the services will be unreachable
*/
aws_security_group_rule.local._access
]
}Arguments and constraints
module.<module_name>.<output_names>
- optional
description
sensitive
depends_on
Local
- Like function temporary local variables
1
2
3
4
5
6resource "aws_instance" "example" {
# ...
# local can only be accessed in expressions
# within the module in which they are declared
tags = local.common_tags
}
Modules
Definition
Collection of
.tf
or.tf.json
files kept together on a directoryTypes (encapsulated)
- Root module (files in main working directory)
1
2
3
4module "aws_elb" "example" {
# retrieve a value from child package
instances = modules.servers.instance_ids
} - Child modules (called by the root module, can declare output to export to caller)
1
2
3
4module "servers" {
source = "./app-cluster"
servers = 5
} - Published modules (downloaded from private or public registry)
1
2
3
4
5module "servers" {
source = "./hashicorp/consul/aws"
version = "0.0.5"
servers = 3
}- Arguments for calling
- source (required)
- version (fopr published modules)
- input variables (defiend in the module)
- meta_arguments (for-each, depends_on, count, providers)
- Arguments for calling
- Root module (files in main working directory)
Refactoring code
- Terraform can see the new location of the module block as an entorely different resource.
- Use
terraform state mv
to inform that the block was moved- When passing resource addresses, use prefix
module.<module_name>
- If called with
count
orfor each
, then use prefixmodule.<module_name>[index]
- When passing resource addresses, use prefix
- Taint (deprecated, use Replace) a resource in a module
1
2# terraform taint module.salt_master.aws_instance.salt_master
terraform apply -replace="aws_instance.example[0]"
Module Sources
- Used during
terraform init
- Types
- Local path
1
2
3module "consult" {
source = "./consult"
} - Module registry
- Terraform public registry
1
2
3
4module "consul" {
source = "hashicorp/consul/aws"
version = "0.1.0"
} - Other registry
1
2
3
4
5
6module "consul" {
# namespace = "app.terraform.io/namespace"
# provider = azurerm
source = "app.terraform.io/example-corp/k8s-cluster/azurerm"
version = "1.1.0"
} - Git repository
1
2
3
4
5
6module "vpc" {
source = "git::https:://hashicorp/consul/vpc.git"
}
module "storage" {
source = "git::ssh:://username@example.com/storage.git?ref=v1.2.0"
} - Mercurial repository
1
2
3module "vpc" {
source = "hg::https:://hashicorp/consul/vpc.hg"
} - HTTP Terraform get (zxip, tar.bz, tar.gz, tar.xz)
1
2
3module "vpc" {
source = "https:://example.com/vpc-module.zip"
}
- Terraform public registry
- SaaS examples (unprefixed URLs or SSH)
- Cloud Hosted Terraform:
"app.terraform.io/namespace"
- GitHub:
"github.com/hashicorp/example"
or"git@github.com:hashicorp/example.git"
- Bitbucket public repos:
"bitbucket.org/hashicorp/example"
- AWS S3:
s3::https://s3-eu-west-1.amazonws.com/examplecorp/vpc-module.zip
- Uses environment variables
AWS_ACCESS_KEY_ID
,AWS_SECRET_ACCESS_KEY
, or./AWS/credentials
file, or EC2 instance profile.
- Uses environment variables
- GCS bucket (Google):
gcs::https://googleapis.com/storage/v1/modules/vpc-module.zip
- Uses environment variables
AWS_ACCESS_KEY_ID
,AWS_SECRET_ACCESS_KEY
, or./AWS/credentials
file, or EC2 instance profile.
- Uses environment variables
- Cloud Hosted Terraform:
- Local path
Expressions and Functions
Expressions: reference or compute values on a configuration
- Types: string, number, bool, list/tuple, map/object, null
- Named values available: resources, input variables, lcoal values, child module outputs, data sources, file system and workspace info, block-local values
- Conditionals:
condition ? true_val : false_val
Functions: transform and combine values on expressions
FUNCION_NAME(<ARGUMENT_1>,<ARGUMENT_2>)
- You can test them directy on CLI via
terraform console
1
2
3terraform console
max(3,2)
exit
State
- State is used by Terraform to map real world resources to your configuration, keep track of metadata, and to improve performance for large infrastructures.
- Stored by default in a local file named
terraform.tfstate
(it is recommended to save a backup), but it can also be stored remotely, which works better in a team environment.
Backend Configuration
- Storage for Terraform snapshots
- Each Terraform config can specify a backend (local or remote) for state storage
- Only used by Terraform CLI top determine
- where the state is stored
- where the operations are performed
- Backend supports multiple workspaces: AzureRM, Consul, COS, GCS, Kubernetes, Local, Manta, Posgres, Remote, S3
- Backend block
1 | terraform { |
- When it changes in a config, you must re-run
terraform init
- When it changes, Terraform gives you the option to migrate to your state
Types
Local
1 | terraform { |
1 | # datasource configuration |
Remote
1 | terraform { |
1 | # datasource configuration |
S3
1 | terraform { |
1 | # datasource configuration |
Azurerm
1 | terraform { |
1 | # datasource configuration |
Working with State
Uses of state
- Metadata: track resources dependencies
- Performance: as cache
- Sync: share file between team members (remote, as read-only)
Lock
- State locking happens automatically on all operations that could write state. You won’t see any message that it is happening.
- If state locking fails, Terraform will not continue.
- Can be unlocked
terraform force-unlock LOCK_ID
(not recommended)
Workspaces
- Workspaces are separate instances of state data, that can be used from the same working directory.
- You can use workspaces to manage multiple non-overlapping groups of resources with the same configuration.
- Every initialized working directory has at least one workspace. (If you haven’t created other workspaces, it is a workspace named default.)
- For a given working directory, only one workspace can be selected at a time.
- Most Terraform commands (including provisioning and state manipulation commands) only interact with the currently selected workspace.
- Use the terraform workspace select command to change the currently selected workspace.
- Use the terraform workspace list, terraform workspace new, and terraform workspace delete commands to manage the available workspaces in the current working directory.