DevOps
Book Notes: Practical Terraform
→ 日本語版を読む- Overview
- Versioning
- Commands
- Symbols
- Files
- variable
- Variable types
- local
- output
- data
- provider
- Lists []
- Built-in functions
- Modules
- Ternary operator
- count
- random provider
- dynamic and for_each
- Best practices
- Structuring and resource reference patterns (Chapters 22–23)
Overview
- Notes on key points from "Practical Terraform" for future reference
- Mainly covers Chapters 1–3 and Chapter 17 onward
- No coverage of for loops; for_each is also not covered in depth and needs separate study
- Official documentation: Overview - Configuration Language | Terraform by HashiCorp
Versioning
Terraform 0.12, released in May 2019, is not backwards compatible with versions 0.11 and earlier — be careful.
Commands
- terraform init
- terraform plan
- terraform apply
- terraform destroy
- terraform fmt -recursive
- terraform validate
Symbols
+: resource being added-: resource being deleted-/+: resource being deleted and re-created
Files
- tfstate file: Running
terraform planat least once creates aterraform.tfstatefile. It records the current state of resources. If there is a diff between the tfstate file and HCL code, only the diff is changed.
variable
- Variables come in two types:
variableandlocal - You can override variables at runtime:
terraform apply -var 'variable_name' - Default values can also be set
variable "example_instance_type" {
default = "t3.micro"
}
resource "aws_instance" "example" {
ami = "ami0c3fd0f5d33134a76"
instance_type = var.example_instance_type
}
Variable Types
- 7 types total
- string, number, bool, list, tuple, map, object
- Examples of list, object, and tuple:
variable "ports" {
type = list(number)
}
variable "person" {
type = object({ name=string, age=number})
}
variable "person" {
type = tuple([string, number])
}
local
- As mentioned above, variables come in two types:
variableandlocal - Cannot be overridden at runtime
- No default value setting
locals {
example_instance_type = "t3.micro"
}
resource "aws_instance" "example" {
ami = "ami0c3fd0f5d33134a76"
instance_type = local.example_instance_type
}
output
- Defines values output after
terraform applyruns - In the example below,
example_instance_id = i02bd77505ab68856fis printed to the display
resource "aws_instance" "example" {
ami = "ami0c3fd0f5d33134a76"
instance_type = "t3.micro"
}
output "example_instance_id" {
value = aws_instance.example.id
}
data
- References external data such as AWS AMI images
- The example below filters by image name and state (available), and uses
most_recentto get the latest AMI
data "aws_ami" "recent_amazon_linux_2" {
most_recent = true
owners = ["amazon"]
filter {
name = "name"
values = ["amzn2-ami-hvm-2.0.????????-x86_64-gp2"]
}
filter {
name = "state"
values = ["available"]
}
}
resource "aws_instance" "example" {
ami= data.aws_ami.recent_amazon_linux_2.image_id
instance_type = "t3.micro"
}
provider
- Abstracts API differences between AWS, GCP, etc.
terraform initdownloads the provider binary files- Terraform can detect providers implicitly, but explicit declaration is preferred for manageability
- Default region and other settings can be specified
provider "aws" {
region = "apnortheast1"
}
Lists []
[ ]can be used to pass a list- In the example below, two security groups (ingress and egress) are passed as a list via
[aws_security_group.example_ec2.id]
resource "aws_security_group" "example_ec2" {
name = "example-ec2
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_instance" "example"{
ami = "ami0c3fd0f5d33134a76"
instance_type = "t3.micro"
vpc_security_group_ids = [aws_security_group.example_ec2.id]
}
Built-in Functions
- Common operations like string manipulation are provided as built-in functions
file('file_path')can be used to read an external file
Modules
- Placed in a directory
module "web_server" {
source = "./http_server"
instance_type = "t3.micro"
}
Create main.tf in the ./http_server directory and implement the following code. The module takes instance_type as an argument passed from the caller and creates an instance.
variable "instance_type" {}
resource "aws_instance" "default" {
ami = "ami-0c3fd0f5d33134a76"
instance_type = var.instance_type
}
Ternary Operator
- Conditional branching can be implemented with the ternary operator.
- In the example below, running
terraform plan -var 'env=prod'sets instance_type to m5.large. Any value other than prod sets it to t3.micro.
variable "env" {}
resource "aws_instance" "example" {
ami = "ami-0c3fd0f5d33134a76"
instance_type = var.env == "prod" ? "m5.large" : "t3.micro"
}
count
- Can be used to create multiple resources at once.
- With
count = 3,count.indextakes values {0, 1, 2}. - The example below creates 3 VPCs (10.0.0.0/16, 10.1.0.0/16, 10.2.0.0/16).
resource "aws_vpc" "examples" {
count = 3
cidr_block = "10.${count.index}.0.0/16"
}
random Provider
- Automatically generates random strings
- Can specify string length and whether special characters are included
- The example below generates a 32-character random string with no special characters
- The result is returned in
random_string.password.result
provider "random" {}
resource "random_string" "password" {
length = 32
special = false
}
dynamic and for_each
dynamicenables dynamic resource generation- In the example below, the value set in
for_eachis extracted asingress.value, i.e.,<dynamic_block_name>.value
variable "ports" {
type = list(number)
}
resource "aws_security_group" "default" {
name = "simple-sg"
dynamic "ingress" {
for_each = var.ports
content {
from_port = each.value
to_port = ins.value
cidr_blocks = ["0.0.0.0/0"]
protocol = "tcp"
}
}
}
Best Practices
- Pin the Terraform version
- Pin provider versions as well (public cloud evolves quickly, causing environment discrepancies)
- Suppress delete operations
- But: removing a resource definition from a .tf file and running
terraform applywill delete the resource (※ This is critical — be very careful!) - Always format code
- Always validate code (additional options needed to cover subdirectories)
- Use TFLint to detect invalid code (
tflint --deep) - Understand implicit dependencies and define them explicitly with
depends_on
Structuring and Resource Reference Patterns (Chapters 22–23)
Skipping as it's time-consuming, but it's important — revisit if forgotten.