Repository contains code samples completed during terraform course
github with labs: https://github.com/btkrausen/hashicorp/tree/master/terraform/Hands-On%20Labs
export AWS_ACCESS_KEY_ID="..."
export AWS_SECRET_ACCESS_KEY="..."
terraform init
terraform plan --auto-approve
terraform apply --autp-approve
Since terraform requires access for state file if collaborative approach. Common approach is to have state file in S3 or in some common remote machine.
#shows state
terraform show
#format code
terraform fmt
#show all managed resources
terraform state list
Basic lifecycle.
terraform apply
terraform validate
terraform init
terraform plan
terraform apply
terraform destroyHCL syntax:
<BLOCK TYPE> "<BLOCK LABEL>" "<BLOCK LABEL>" {
identifier = expression
} Terraform providers: used to interact with Cloud Platforms
terraform version
terraform providersTerraform resources: One or more deployed resource
We can create resources from different providers and use data from 1 resource inside another resource.
Terraform variables:
#global variables located in variables.tf
variable "variables_sub_auto_ip" {
description = "Set Automatic IP Assigment for Variables Subnet"
type = bool
default = true
}
#local variables located at the top of the file, template
locals {
# Block body
local_variable_name = <EXPRESSION OR VALUE>
local_variable_name = <EXPRESSION OR VALUE>
}
#example
locals {
time = timestamp()
}Terraform data blocks:
The way retrieve some information with api (provider api will be used)
Use provider documentation here. For example here
we create data block with type:aws_availability_zones and
local name: available, later we can query retrieved data with:
data.aws_availability_zones.available.<some-data>
data <type> <local-name> {
<identifier> = <expression>
}
#query
data.<type>.<local-name>.<attribute>
#see examples in the code
data "aws_availability_zones" "available" {}Эта data вытаскивает из aws переменную типа aws_availability_zones и из нее мы может вытащить переменные которые находятся внутри этого блока.
data "aws_ami" "ubuntu" {
filter {
name = "virtualization-type"
values = ["hvm"]
}
}data может содержать фильтры Terraform configuration We can use terraform.tf to configure out local terraform
Ways to organize our infrastructure into logical folders. Modules can be online or offline
module "<module-name>" {
source = <module-source>
<input_name> = <description>
<input_name> = <description>
}If we want to see some output variables after our deploy we can use:
terraform output
terraform output --jsonLocal provider is responsible for managing local resources such as files.
terraform {
required_providers {
local = {
source = "hashicorp/local"
version = "2.1.0"
}
}
}tf providers - command to check installed providers
tf init -upgrade - initiate terraform and upgrade all providers
There are two types of provisioners, local performs some tasks on local machine. Remote provisioner executes commands on remote target, examples:
#changes permissions for local key
provisioner "local-exec" {
command = "chmod 600 ${local_file.private_key_pem.filename}"
}
# performs commands on remote target
provisioner "remote-exec" {
inline = [
"sudo rm -rf /tmp",
"sudo git clone https://github.com/hashicorp/demo-terraform-101 /tmp",
"sudo sh /tmp/assets/setup-web.sh",
]
}Terraform taint and replace
taint - manually mark resource for recreation.
Simple way to redeploy only marked resources, not all managed by terraform.
terraform state list
terraform taint <resource.name>
terraform plan
terraform applyExample showing recreation only 1 of resources.
Taint - is deprecated how recommended approach is --replace command.
terraform apply -replace="aws_instance.web_server"Terraform import: There is a way to connect already existing resources to terraform and manage their state. Command to use:
terraform import <resource_type>.<resource_name> <existing_resource_id>
#example
terraform import aws_instance.aws_linux i-0bfff5070c5fb87b6After that unfortunatelly we have to manually copy some parameters into main.tf file, not sure why?
Terraform workspaces: Cool feature, allows to distinguish resources that we are creating in several workspaces
terraform workspace new <workspace-name>
terraform workspace select <workspace-name>
terraform workspace show #current workspaceGood practise is to create a default tag Environment = terraform.workspace, so it will be
easy to understand what infrastructure is currently being used.
Terraform state CLI:
There are some commands that can modify state more flexibly. These commands
should be used in advance cases, not in general terraform usage, for
safety backup is performed everytime state command is called.
terraform state show <resource_type>.<resource_name>
terraform state list
terraform state mv #move item in state
terraform state pull #pull state to stdout
#there are some other commandsDebugging Terraform: We can choose log level and put all logs into log file.
#Logging level
export TF_LOG=TRACE
#logging file
export TF_LOG_PATH="terraform_log.txt"
#disable logging
export TF_LOG=""Terraform modules are basically folder that contains some code, nothing more. Terraform can use local modules, modules from Terraform public registry and modules from private registry(Terraform Cloud, Terraform Enterprise). Local modules:
|-- modules
| |-- server
| |-- server.tf
| |-- web_server
| |-- server.tf
|In that case we will need to reference local code from root module:
module "server_subnet_1" {
source = "./modules/web_server"
...
}Terraform public registry:
module "autoscaling" {
source = "terraform-aws-modules/autoscaling/aws"
...
}After every change in modules we want to run terraform init
Modules Structure:
|-- modules
| |-- server
| |-- main.tf
| |-- variables.tf
| |-- outputs.tf
| |-- web_server
| |-- main.tf
| |-- variables.tf
| |-- outputs.tf
|variables.tf will contain the variable definitions for your module. When your module is used by others,
the variables will be configured as arguments in the module block. Since all Terraform values must be defined,
any variables that are not given a default value will become required arguments.
Variables with default values can also be provided as module arguments, overriding the default value.
We can use outputs from module in different modules(like input and output variables).
Some guidelines to create a good module:
- Encapsulation: Group infrastructure that is always deployed together. Including more infrastructure in a module makes it easier for an end user to deploy that infrastructure but makes the module's purpose and requirements harder to understand
- Privileges: Restrict modules to privilege boundaries. If infrastructure in the module is the responsibility of more than one group, using that module could accidentally violate segregation of duties. Only group resources within privilege boundaries to increase infrastructure segregation and secure your infrastructure
- Volatility: Separate long-lived infrastructure from short-lived. For example, database infrastructure is relatively static while teams could deploy application servers multiple times a day. Managing database infrastructure in the same module as application servers exposes infrastructure that stores state to unnecessary churn and risk.
modules have versions. we can set version expliciltly or write some constraints for versions
terraform init - command is used to initialize a working directory containing Terraform
configuration files. This is the first command that should be run after writing a new Terraform configuration or cloning an existing one from version control.
By default terraform find, download and install providers from public registry, if we don't
want such behaviour we can customize it in configuration.
If we want to change the directory where we track state we can add following configuration
to our terraform.tf file:
backend "local" {
path = "mystate/terraform.tfstate"
}then, to apply our changes:
terraform init --migrate-stateValidation of terraform configuration:
terraform validate - ensures that all configuration is correct
terraform validate -json - useful in pipelines, to see|publish errors
terraform plan - dry-run of your changes and checking and outgoing result
matched your expectations. Also this command checks current state and identifies
differences between existing infrastructure and written infrastructure.
terraform plan
terraform plan -out file
terraform plan -refresh-onlyterraform apply - executes promised actions on actual infrastructure.
terraform apply
#also we can apply from the saved plan
terraform apply myplanterraform apply - performs a lock while applying, so 1 one user can apply at the same time
we can specify timeout if we want
terraform apply -lock-timeout=60s
Not all backends supports locking(which is obviously important for collaborative teams).
Remote state(Terraform Cloud, Terraform Enterprise) supports locking. Default backend local
supports locking.
Since we have some state in remote and several people have access to that state we need to create some permissions control because we dont want everyone to access our state. We configured default S3 state backend in our labs.
To prevents concurrent modifications of our remote-state we are using Dynamo-DB table, first of all we have to configure it manually and then provide value of that DynamoDB to our terraform backend configuration. Not all remote backends supports state locking, here Hashicorp sells their own cloud where lock functionality works more nativelly then in AWS, GCP, Azure clouds.
We can easily transfer our state file just changing configuration in terraform.tf file
and using:
terraform init -migrate-stateexample of terraform Cloud Remote Backend:
terraform {
backend "remote" {
hostname = "app.terraform.io"
organization = "Enterprise-Cloud"
workspaces {
name = "my-aws-app"
}
}
}to change backend:
terraform init -migrate-stateto go back to local backend provider we can simply remove all backend configuration and
terraform by default will use local backend.
terraform refresh is the first part of the terraform plan command.
We can refresh state if we observe any drift in configuration, use command:
terraform apply -refresh-onlyWe can mark some variables as sensitive, so they will not appear in the output (especially important not to expose such variables in pipeline log).
We can declare some local variables(same as constants) and then reference it later in the code:
locals {
service_name = "Automation"
app_team = "Cloud Team"
createdby = "terraform"
}
...
tags = {
"CreatedBy" = local.createdby
}there is an order of precedence in setting variables.
variables.tf - file where declare and describe variables(type, description, isSensitive, default value)
terraform.tfvars - file where we set actual values to declared variables.
the most powerful way to declare variable is to set it with CLI:
terraform plan -var variables_sub_az="us-east-1e" -var variables_sub_cidr="10.0.205.0/24"here variables_sub_az is a variable name.
We can write custom validators for our variables:
variable "cloud" {
type = string
# if condition is true, then terraform plan will fail
validation {
condition = contains(["aws", "azure", "gcp", "vmware"], lower(var.cloud))
error_message = "You must use an approved cloud."
}
# if condition is true, then terraform plan will fail
validation {
condition = lower(var.cloud) == var.cloud
error_message = "The cloud name must not have capital letters."
}
}variable "phone_number" {
type = string
sensitive = true
default = "867-5309"
}
#later we could not see this information in the tf output# we configure provider
provider "vault" {
address = "http://127.0.0.1:8200"
token = <root token>
}
# then we can later reference to this provider
data "vault_generic_secret" "phone_number" {
path = "secret/app"
}