A summarize of terraform usage and example
- Infrastruture as Code
- Automation of your Infrastructure
- Infrastructure auditable
- Keep infrastructure in certain state
start with terraform
- init
- every time before do anythings
- plan
- check infrasturcture that is going to provision
- apply
- apply infrastructure to provider
- console
- Terraform v0.12 now allows you to use expressions directly when defining most attributes. No need to use interpolation any more
# Old 0.11 example
tags = "${merge(map("Name", "example"), var.common_tags)}"
# Updated 0.12 example
tags = merge({ Name = "example" }, var.common_tags)
- To run an example of Terraform please provide Terraform.tfvars that contains AWS credentail on those folder
Terraform will interpret with tf extension
3 main things are
- Type
- Blocks
- Arguments
Resources are the most important element in the Terraform language
Each resource is associated with a single resource type, which determines the kind of infrastructure object
resource "type" "name" {
arguments = value
}
e.g. resource type aws_instance
resource "aws_instance" "web" {
ami = "ami-a1b2c3d4"
instance_type = "t2.micro"
}
Each resource type in turn belongs to a provider
e.g. aws
provider "aws" {
}
Meta-arguments, which can be used with any resource type to change the behavior of resources
- depends_on
- for specifying hidden dependencies
- count
- for creating multiple resource instances according to a count
- for_each
- to create multiple instances according to a map, or set of strings
- provider
- for selecting a non-default provider configuration
- lifecycle
- for lifecycle customizations
- provisioner and connection
- for taking extra actions after resource creation
- Input Variable
Input variables serve as parameters for a Terraform module
- Variable Type
- string
- bool
- number
- list
- set
- map
- object
- tuple
variable "image_id" {
type = string
}
variable "availability_zone_names" {
type = list(string)
default = ["us-west-1a"]
}
usage by var.<NAME>
resource "aws_instance" "example" {
instance_type = "t2.micro"
ami = var.image_id
}
- Variable Definitions Files
To set lots of variables, it is more convenient to specify their values in a variable definitions file (either .tfvars or .tfvars.json)
.tfvars
image_id = "ami-abc123"
availability_zone_names = [
"us-east-1a",
"us-west-1c",
]
.tfvars.json
{
"image_id": "ami-abc123",
"availability_zone_names": ["us-west-1a", "us-west-1c"]
}
- Output Variable
Output values are like the return values of a Terraform module
output "instance_ip_addr" {
value = aws_instance.server.private_ip
}
Outputs are only rendered when Terraform applies your plan. Running terraform plan will not render outputs
allow data to be fetched or computed for use elsewhere in Terraform configuration
# Find the latest available AMI that is tagged with Component = web
data "aws_ami" "web" {
filter {
name = "state"
values = ["available"]
}
filter {
name = "tag:Component"
values = ["web"]
}
most_recent = true
}
resource "aws_instance" "web" {
ami = data.aws_ami.web.id
instance_type = "t1.micro"
}
Each provider may offer data sources alongside its set of resource types
Terraform includes the concept of provisioners as a measure of pragmatism, knowing that there will always be certain behaviors that can't be directly represented in Terraform's declarative model
invokes a local executable after a resource is created
resource "aws_instance" "web" {
provisioner "local-exec" {
command = "echo ${aws_instance.web.private_ip} >> private_ips.txt"
}
}
The template provider exposes data sources to use templates to generate strings for other Terraform resources or outputs
# Template for initial configuration bash script
data "template_file" "init" {
template = "${file("init.tpl")}"
vars = {
consul_address = "${aws_instance.consul.private_ip}"
}
}
# Create a web server
resource "aws_instance" "web" {
# ...
user_data = "${data.template_file.init.rendered}"
}
Terraform keeps thre remote state of the infrastructure
- Terraform stores state locally in a file named terraform.tfstate
- Backup the previous state in terraform.tfstate.backup
- When you execute terraform apply, a new terraform.tfstate and backup is written
- If thre remote state changes and you hit terraform apply again, terraform will make changes to meet the correct remote state again
The terraform state can be saved remote, using backend functionality in terraform
Example Backend
- S3
- Consul
- terraform enterprise
- Working in a team
- Backends can store their state remotely and protect that state with locks to prevent corruption
- Keeping sensitive information off disk
- The state ever is persisted not in memory
- The default is a local backend (the local file)
- Remote operations
- For larger infrastructures or certain changes, terraform apply can take a long, long time. Some backends support remote operations which enable the operation to execute remotely
configured directly in Terraform files in the terraform section
# example configuring the "consul" backend
terraform {
backend "consul" {
address = "demo.consul.io"
scheme = "https"
path = "example_app/terraform_state"
}
}
before use must always terraform init configured backend
# condition ? true_val : false_val
resource "aws_instance" "web" {
subnet = "${var.env == "production" ? var.prod_subnet : var.dev_subnet}"
}
- loop
- for
- creates a complex type value by transforming another complex type value
- for
# Produce tuple
[for s in var.list : upper(s)]
# Produce object
{for s in var.list : s => upper(s)}
Diff is "[" or "{"
# optional if clause to filter elements
[for s in var.list : upper(s) if s != ""]
# add ... symbol for object
{for s in var.list : substr(s, 0, 1) => s... if s != ""}
- dynamic block
- supported inside resource, data, provider, and provisioner blocks
- acts much like a for expression
- The for_each argument provides the complex value to iterate over
- Best Practices for dynamic Blocks
- Overuse of dynamic blocks can make configuration hard to read and maintain
resource "aws_security_group" "example" {
name = "example" # can use expressions here
dynamic "ingress" {
for_each = var.service_ports
content {
from_port = ingress.value
to_port = ingress.value
protocol = "tcp"
}
}
}
is a container for multiple resources that are used together
To define a module, create a new directory for it and place one or more .tf files inside just as you would do for a root module. Modules can also call other modules using a module block (but recommend keeping flat)
Most commonly, modules use
Any combination of resources and other constructs can be factored out into a module, but over-using modules can make your overall Terraform configuration harder to understand and maintain, so we recommend moderation.
For example, aws_instance and aws_elb are both resource types belonging to the AWS provider.
You might use a module to represent the higher-level concept "HashiCorp Consul cluster running in AWS"
which happens to be constructed from these and other AWS provider resources.
recommend for reusable modules distributed in separate repositories
- Root module
- a file and directory layout
- or have nested under Root module
- README.md
# Dedicated folder for module and README.md
$ tree minimal-module/
.
├── README.md
├── main.tf
├── variables.tf
├── outputs.tf
#Nested
$ tree complete-module/
.
├── README.md
├── main.tf
├── variables.tf
├── outputs.tf
├── ...
├── modules/
│ ├── nestedA/
│ │ ├── README.md
│ │ ├── variables.tf
│ │ ├── main.tf
│ │ ├── outputs.tf
│ ├── nestedB/
│ ├── .../
├── examples/
│ ├── exampleA/
│ │ ├── main.tf
│ ├── exampleB/
│ ├── .../
Two ways to provision software
- Custom AMI
- Standard AMI with
- File upload
- Remote exec
- Automation tools
- chef (integrated with terraform)
- puppet
- ansible
The Terraform language includes a number of built-in functions that you can call from within expressions to transform and combine values
> max(5, 12, 9)
12
> format("Hello, %s!", "Ander")
Hello, Ander!
> format("There are %d lights", 4)
There are 4 lights
All the function reference here
Build Automated Machine Images
Packer is an open source tool for creating identical machine images for multiple platforms from a single source configuration
3 main parts
- variables
- contain the list of variables you need to use or need across other sections on the Packer Template JSON file
- builders
- define what image we are going to create and for which technology/platform we are going to create an image for like AWS, DOCKER, VirtualBox, OpenStack etc.
- provisioners
- the list of built-in or external configuration on management tools like Shell, Ansible, Chef, PowerShell
- adding a necessary software/programs to it
reference Template Structure
{
"variables": {
"aws_access_key": "",
"aws_secret_key": ""
},
"builders": [{
"type": "amazon-ebs",
"access_key": "{{user `aws_access_key`}}",
"secret_key": "{{user `aws_secret_key`}}",
"region": "us-east-1",
"source_ami_filter": {
"filters": {
"virtualization-type": "hvm",
"name": "ubuntu/images/*ubuntu-xenial-16.04-amd64-server-*",
"root-device-type": "ebs"
},
"owners": ["099720109477"],
"most_recent": true
},
"instance_type": "t2.micro",
"ssh_username": "ubuntu",
"ami_name": "packer-example {{timestamp}}"
}]
}
- validate
- validate the template
- output should be Template validated successfully.
- build
- build your image
- artifacts are the results of a build, and typically represent an ID (such as in the case of an AMI)