Terraform guide 1 - Introduction
Terraform: Introduction
Definition
- Terraform = tool for building infrastructure
- Allow simple control versopm
- opensource
- enterprise (collaboration, governance)
- Features
- Infrastructire as Code (IaC)
- idempotent
- high level syntax (Hashicorp)
- easily resusable
- Execution plans
- intents of deploy
- help ensure eveything in development is intentional
- Resource graph
- illustrates all cahnges and dependecies
- Infrastructire as Code (IaC)
- Uses
- Hybrid clouds
- cloud agnostic
- allows deployment to multiple providers simultaneously
- Mult-tier architecture
- allows deployment of several layers of architecture
- usually able to automatically deploy in the correct order
- Software-defined networking
- able to deploy network architecture as well
- Hybrid clouds
- Terraform is high-level infrastructire orchestration tool
- Puppet, Chef, Ansible for configuration management
- Providers that can tell this tools to perform CM duties
- Puppet, Chef, Ansible for configuration management
Setting up Docker Installing Terraform
Prerequisires
- Update the operating system and uninstall older versions
1
2
3
4
5
6
7
8
9sudo yum update -y
sudo yum remove -y docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-engine
- Update the operating system and uninstall older versions
Install Docker CE
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15# install utils
sudo yum install -y yum-utils \
device-mapper-persistent-data \
lvm2
# add docker repo
sudo yum-config-manager \
--add-repo \
https://download.docker.com/linux/centos/docker-ce.repo
# install docker CE and enable it
sudo yum -y install docker-ce
sudo systemctl start docker && sudo systemctl enable docker
# add user to docker group
sudo usermod -aG docker cloud_user
# test installation
docker --versionConfiguring Swarm Manager node
1
docker swarm init --advertise-addr [PRIVATE_IP]
Configure the Swarm Worker node
1
docker swarm join --token [TOKEN] [PRIVATE_IP]:2377
Verify the Swarm cluster
1
docker node ls
Install Terraform 0.11.13 on the Swarm manager
1
2
3
4
5sudo curl -O https://releases.hashicorp.com/terraform/0.11.13/terraform_0.11.13_linux_amd64.zip
sudo yum install -y unzip
sudo unzip terraform_0.11.13_linux_amd64.zip -d /usr/local/bin/
# test it
terraform version
Deploying infrastructure with Terraform: basics
Terraform commands
Common commands
Command Action apply
Builds or changes infrastructure console
Interactive console for Terraform interpolations destroy
Destroys Terraform-managed infrastructure fmt
Rewrites configuration files to canonical format get
Downloads and installs modules for the configuration graph
Creates a visual graph of Terraform resources import
Imports existing infrastructure into Terraform init
Initializes a new or existing Terraform configuration output
Reads an output from a state file plan
Generates and shows an execution plan providers
Prints a tree of the providers used in the configuration push
Uploads this Terraform module to Terraform Enterprise to run refresh
Updates local state file against real resources show
Inspects Terraform state or plan taint
Manually marks a resource for recreation untaint
Manually unmarks a resource as tainted validate
Validates the Terraform files version
Prints the Terraform version workspace
Workspace management Set up the environment
1
2mkdir -p terraform/basics
cd terraform/basicsCreate a Terraform script
1
nano main.tf
main.tf
contents1
2
3
4# Download the latest Ghost image
resource "docker_image" "image_id" {
name = "ghost:latest"
}
Initialize Terraform
1
terraform init
Validate the Terraform file
1
terraform validate
List providers in the folder
1
ls .terraform/plugins/linux_amd64/
List providers used in the configuration
1
terraform providers
Terraform Plan
1
terraform plan
- useful flags
-out=path
: Writes a plan file to the given path. This can be used as input to the “apply” command.-var 'foo=bar'
: Set a variable in the Terraform configuration. This flag can be set multiple times.
- useful flags
Terraform Apply
1
2# you should confirm
terraform apply- useful flags
-auto-approve
: This skips interactive approval of plan before applying-var 'foo=bar'
: This sets a variable in the Terraform configuration. It can be set multiple times.
- useful flags
List the Docker images
1
docker image ls
Terraform Show
1
terraform show
Terraform Destroy
1
2# you should confirm
terraform destroy- useful flags
-auto-approve
: Skip interactive approval of plan before applying.
- useful flags
Re-list the Docker images
1
docker image ls
Using a plan
1
terraform plan -out=tfplan
Applying a plan:
1
terraform apply tfplan
Show the Docker Image resource:
1
terraform show
HashiCorp Configuration Language (HCL)
Terraform code files (.tf and .tf.json)
1
2
3
4
5
6resource "aws_instance" "example" {
ami = "abc123"
network_interface {
# ...
}
}Syntax reference
Type Syntax Single line comments # this is a comment
Multi-line comments /* This is a comment*/
Assign values key = value
Strings "This is a String"
Strings interpolation ${var.foo}.
Numbers 10
# assumed as base 10Boolean true
,false
Lists of primitive types ["foo", "bar", "baz"]
Maps { "foo": "bar", "bar": "baz" }
Style Conventions
- Indent two spaces for each nesting level
- With multiple arguments, align their equals signs
- Setup the environment
1
cd terraform/basics
Deploying a container using Terraform
- Redeploy the Ghost image
1
terraform apply
- Open
main.tf
:1
2
3
4
5
6
7
8
9
10
11
12
13
14# Download the latest Ghost image
resource "docker_image" "image_id" {
name = "ghost:latest"
}
# Start the Container
resource "docker_container" "container_id" {
name = "ghost_blog"
image = "${docker_image.image_id.latest}"
ports {
internal = "2368"
external = "80"
}
} - Validate
main.tf
, plan and apply1
2
3
4
5terraform validate
terraform plan
terraform apply
#check
docker container ls - Check Ghost on browser (
http:://[SWAM_MANAGER_IP]
)
- Redeploy the Ghost image
Cleaning up the environment
1
terraform destroy
Tainting and updating resources
Terraform commands:
- taint: Manually mark a resource for recreation
1
terraform taint [NAME]
- untaint: Manually unmark a resource as tainted
1
terraform untaint [NAME]
- taint: Manually mark a resource for recreation
Updating resources
- edit main.tf
1
2
3
4
5
6
7
8
9
10
11
12
13
14# Download the latest Ghost image
resource "docker_image" "image_id" {
name = "ghost:alpine"
}
# Start the Container
resource "docker_container" "container_id" {
name = "ghost_blog"
image = "${docker_image.image_id.latest}"
ports {
internal = "2368"
external = "80"
}
} - script
1
2
3
4
5
6
7terraform validate
terraform plan
terraform apply
# list containers
docker container ls
# check Ghost image
docker image ls | grep [IMAGE]
- edit main.tf
Cleaning up the environment
1
2
3
4
5
6# reset
terraform destroy
# list the Docker images
docker image ls
# remove the Ghost blog image
docker image rm ghost:latest
Terraform console and output
Terraform commands:
console
: Interactive console for Terraform interpolations
Working with the Terraform console
1
2
3
4
5
6
7
8
9
10# Redeploy image and container
terraform apply
terraform show
terraform console
# Type the following in the console to get the container's name
docker_container.container_id.name
# Type the following in the console to get the container's IP
docker_container.container_id.ip_address
# Break out of the Terraform console by using Ctrl+C
terraform destroyOutput the name and IP of the container
- Edit
main.tf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26# Download the latest Ghost Image
resource "docker_image" "image_id" {
name = "ghost:latest"
}
# Start the Container
resource "docker_container" "container_id" {
name = "blog"
image = "${docker_image.image_id.latest}"
ports {
internal = "2368"
external = "80"
}
}
# Output the IP Address of the Container
output "ip_address" {
value = "${docker_container.container_id.ip_address}"
description = "The IP for the container."
}
# Output the Name of the Container
output "container_name" {
value = "${docker_container.container_id.name}"
description = "The name of the container."
} - Script
1
2
3
4
5terraform validate
# apply changes to get the output
terraform apply
# clean up
terraform destroy
- Edit
Input variables
Definition
- Parameters for a Terraform file
- A variable block configures a single input variable for a Terraform module
- Each block declares a single variable.
Syntax
1
2
3variable [NAME] {
[OPTION] = "[VALUE]"
}Arguments (between { })
Optional parameters Value type
defines the type of the variable (string, list or map) default
default value. If not provided, Terraform will raise an error if a value is not provided by the caller description
human-friendly description for the variable Using variables during an apply
1
terraform apply -var 'foo=bar'
Example with
main.tf
contents1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46# Define variables
variable "image_name" {
description = "Image for container."
default = "ghost:latest"
}
variable "container_name" {
description = "Name of the container."
default = "blog"
}
variable "int_port" {
description = "Internal port for container."
default = "2368"
}
variable "ext_port" {
description = "External port for container."
default = "80"
}
# Download the latest Ghost Image
resource "docker_image" "image_id" {
name = "${var.image_name}"
}
# Start the Container
resource "docker_container" "container_id" {
name = "${var.container_name}"
image = "${docker_image.image_id.latest}"
ports {
internal = "${var.int_port}"
external = "${var.ext_port}"
}
}
# Output the IP Address of the Container
output "ip_address" {
value = "${docker_container.container_id.ip_address}"
description = "The IP for the container."
}
output "container_name" {
value = "${docker_container.container_id.name}"
description = "The name of the container."
}1
2
3
4
5
6
7
8terraform validate
terraform plan
# apply using the variable
terraform apply -var 'ext_port=8080'
# change the container name:
terraform apply -var 'container_name=ghost_blog' -var 'ext_port=8080'
# reset
terraform destroy -var 'ext_port=8080'
Breaking out our variables and outputs
- Edit
variables.tf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17#Define variables
variable "container_name" {
description = "Name of the container."
default = "blog"
}
variable "image_name" {
description = "Image for container."
default = "ghost:latest"
}
variable "int_port" {
description = "Internal port for container."
default = "2368"
}
variable "ext_port" {
description = "External port for container."
default = "80"
} - Edit
main.tf
:1
2
3
4
5
6
7
8
9
10
11
12
13
14# Download the latest Ghost Image
resource "docker_image" "image_id" {
name = "${var.image_name}"
}
# Start the Container
resource "docker_container" "container_id" {
name = "${var.container_name}"
image = "${docker_image.image_id.latest}"
ports {
internal = "${var.int_port}"
external = "${var.ext_port}"
}
} - Edit
outputs.tf
:1
2
3
4
5
6
7
8
9
10#Output the IP Address of the Container
output "ip_address" {
value = "${docker_container.container_id.ip_address}"
description = "The IP for the container."
}
output "container_name" {
value = "${docker_container.container_id.name}"
description = "The name of the container."
} - Script
1
2
3
4
5
6terraform validate
# plan and apply
terraform plan -out=tfplan -var container_name=ghost_blog
terraform apply tfplan
# clean up
terraform destroy -auto-approve -var container_name=ghost_blog
Maps and lookups
- Map: specify different environment variables based on conditions
- Example
- Edit
variables.tf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33#Define variables
variable "env" {
description = "env: dev or prod"
}
variable "image_name" {
type = "map"
description = "Image for container."
default = {
dev = "ghost:latest"
prod = "ghost:alpine"
}
}
variable "container_name" {
type = "map"
description = "Name of the container."
default = {
dev = "blog_dev"
prod = "blog_prod"
}
}
variable "int_port" {
description = "Internal port for container."
default = "2368"
}
variable "ext_port" {
type = "map"
description = "External port for container."
default = {
dev = "8081"
prod = "80"
}
} - Edit
main.tf
1
2
3
4
5
6
7
8
9
10
11
12
13
14# Download the latest Ghost Image
resource "docker_image" "image_id" {
name = "${lookup(var.image_name, var.env)}"
}
# Start the Container
resource "docker_container" "container_id" {
name = "${lookup(var.container_name, var.env)}"
image = "${docker_image.image_id.latest}"
ports {
internal = "${var.int_port}"
external = "${lookup(var.ext_port, var.env)}"
}
} - Validate it and deploy
1
2
3
4
5
6
7
8
9
10
11
12terraform validate
terraform plan -out=tfdev_plan -var env=dev
terraform apply tfdev_plan
# plan the prod deploy
terraform plan -out=tfprod_plan -var env=prod
# use environment variables
export TF_VAR_env=prod
terraform console
# execute a lookup
lookup(var.ext_port, var.env)
# exit the console:
unset TF_VAR_env
- Edit
Terraform Workspaces
Terraform commands
Command Type Action workspace
Command New, list, select and delete Terraform workspaces delete
Subcommand delete a workspace list
Subcommand list Workspaces new
Subcommand create a new workspace select
Subcommand select a workspace show
Subcommand show the name of the current workspace Example
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23cd terraform/basics
# dev workspace
terraform workspace new dev
terraform plan -out=tfdev_plan -var env=dev
terraform apply tfdev_plan
# prod workspace
terraform workspace new prod
terraform plan -out=tfprod_plan -var env=prod
terraform apply tfprod_plan
# default workspace
terraform workspace select default
# find what workspace we are using now
terraform workspace show
# select a workspace
terraform workspace select dev
# clean up
terraform destroy -var env=dev
terraform workspace select prod
terraform destroy -var env=prod
Null resources and Local-exec
null_resource
resource implements the standard resource lifecycle but takes no further actionlocal-exec
provisioner invokes a local executable after a resource is created
- Edit
main.tf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20# Download the latest Ghost Image
resource "docker_image" "image_id" {
name = "${lookup(var.image_name, var.env)}"
}
# Start the Container
resource "docker_container" "container_id" {
name = "${lookup(var.container_name, var.env)}"
image = "${docker_image.image_id.latest}"
ports {
internal = "${var.int_port}"
external = "${lookup(var.ext_port, var.env)}"
}
}
resource "null_resource" "null_id" {
provisioner "local-exec" {
command = "echo ${docker_container.container_id.name}:${docker_container.container_id.ip_address} >> container.txt"
}
} - Null Resource -> perform local commands on our machine without having to deploy extra resources
1
2
3
4
5
6
7
8
9# reinitialize
terraform init
terraform validate
terraform plan -out=tfplan -var env=dev
terraform apply tfplan
# view container info
cat container.txt
# clean up
terraform destroy -auto-approve -var env=dev
Terraform modules
Module: container for multiple resource
1
2
3
4
5
6
7
8
9# setup environment for root module
mkdir -p modules/image
mkdir -p modules/container
# files for image
cd ~/terraform/basics/modules/image
touch main.tf variables.tf outputs.tf
# files for container module
cd ~/terraform/basics/modules/container
touch main.tf variables.tf outputs.tf
Image module
- environment: ``~/terraform/basics/modules/image`
- example
- Edit
main.tf
1
2
3
4# Download the Image
resource "docker_image" "image_id" {
name = "${var.image_name}"
} - Edit
variables.tf
1
2
3variable "image_name" {
description = "Name of the image"
} - Edit
outputs.tf
1
2
3output "image_out" {
value = "${docker_image.image_id.latest}"
} - Initialize Terraform:
1
2
3
4
5terraform init
terraform plan -out=tfplan -var 'image_name=ghost:alpine'
terraform apply -auto-approve tfplan
# clean up
terraform destroy -auto-approve -var 'image_name=ghost:alpine'
- Edit
Container module
- environment:
~/terraform/basics/modules/container
- example: breaking out the container code into it’s own module
- Edit
main.tf
1
2
3
4
5
6
7
8
9# Start the Container
resource "docker_container" "container_id" {
name = "${var.container_name}"
image = "${var.image}"
ports {
internal = "${var.int_port}"
external = "${var.ext_port}"
}
} - Edit
variables.tf
1
2
3
4
5#Define variables
variable "container_name" {}
variable "image" {}
variable "int_port" {}
variable "ext_port" {} - Edit
outputs.tf
1
2
3
4
5
6
7
8#Output the IP Address of the Container
output "ip" {
value = "${docker_container.container_id.ip_address}"
}
output "container_name" {
value = "${docker_container.container_id.name}"
} - Script
1
2
3terraform init
terraform plan -out=tfplan -var 'container_name=blog' -var 'image=ghost:alpine' -var 'int_port=2368' -var 'ext_port=80'
terraform apply tfplan
- Edit
Root module
- environment:
~/terraform/basics/modules/
- example: refactor the root module to use the image and container modules we created previously
- Edit
main.tf
1
2
3
4
5
6
7
8
9
10
11
12
13
14# Download the image
module "image" {
source = "./image"
image_name = "${var.image_name}"
}
# Start the container
module "container" {
source = "./container"
image = "${module.image.image_out}"
container_name = "${var.container_name}"
int_port = "${var.int_port}"
ext_port = "${var.ext_port}"
} - Edit
variables.tf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17#Define variables
variable "container_name" {
description = "Name of the container."
default = "blog"
}
variable "image_name" {
description = "Image for container."
default = "ghost:latest"
}
variable "int_port" {
description = "Internal port for container."
default = "2368"
}
variable "ext_port" {
description = "External port for container."
default = "80"
} - Edit
outputs.tf
1
2
3
4
5
6
7
8# Output the IP Address of the Container
output "ip" {
value = "${module.container.ip}"
}
output "container_name" {
value = "${module.container.container_name}"
} - Script
1
2
3
4
5terraform init
terraform plan -out=tfplan
terraform apply tfplan
# clean up
terraform destroy -auto-approve
- Edit