Terraform / OpenTofu Cheatsheet

Workflow

terraform init              # после изменения providers / backend
terraform init -upgrade     # обновить providers
terraform init -reconfigure # пересоздать backend config

terraform fmt -recursive
terraform validate
terraform plan
terraform plan -out=tf.plan
terraform apply tf.plan     # применить plan
terraform apply             # plan + apply
terraform apply -auto-approve  # ⚠️ без confirm

terraform destroy
terraform destroy -target=aws_s3_bucket.b

State

terraform state list
terraform state show aws_s3_bucket.b
terraform state pull > backup.tfstate
terraform state mv aws_s3_bucket.old aws_s3_bucket.new
terraform state rm aws_s3_bucket.b         # удалить из state, не из cloud
terraform refresh                          # синк state с реальностью

Import

# TF 1.5+ декларативно
import {
  to = aws_s3_bucket.legacy
  id = "existing-bucket-name"
}

# CLI способ
terraform import aws_s3_bucket.legacy existing-bucket-name

Targeting

terraform plan -target=aws_vpc.main
terraform apply -target=aws_vpc.main
terraform destroy -target=module.eks

Workspaces (НЕ для prod environments)

terraform workspace list
terraform workspace new dev
terraform workspace select dev
terraform workspace show
terraform workspace delete dev

Output

terraform output
terraform output vpc_id
terraform output -json | jq

HCL — основы

terraform {
  required_version = ">= 1.6"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
  backend "s3" {
    bucket         = "my-tf-state"
    key            = "envs/prod/terraform.tfstate"
    region         = "eu-west-1"
    dynamodb_table = "tf-lock"
    encrypt        = true
  }
}

provider "aws" {
  region = "eu-west-1"
}

Variables

variable "name" {
  description = "Name prefix"
  type        = string
  default     = "myapp"
  sensitive   = false
  validation {
    condition     = length(var.name) > 0
    error_message = "name required"
  }
}

variable "tags" {
  type    = map(string)
  default = {}
}

variable "subnets" {
  type = list(object({
    cidr = string
    az   = string
  }))
}

Передача:

terraform apply -var="name=myapp" -var-file=prod.tfvars
# или через env: TF_VAR_name=myapp terraform apply

Locals и Outputs

locals {
  common_tags = {
    Environment = var.environment
    ManagedBy   = "terraform"
  }
  name_prefix = "${var.project}-${var.environment}"
}

output "vpc_id" {
  description = "VPC ID"
  value       = aws_vpc.main.id
  sensitive   = false
}

Ресурсы

resource "aws_vpc" "main" {
  cidr_block           = var.cidr
  enable_dns_hostnames = true

  tags = merge(local.common_tags, {
    Name = "${local.name_prefix}-vpc"
  })

  lifecycle {
    prevent_destroy = true
    create_before_destroy = true
    ignore_changes = [tags["LastUpdated"]]
  }
}

Data sources

data "aws_caller_identity" "current" {}
data "aws_region" "current" {}

data "aws_ami" "ubuntu" {
  most_recent = true
  owners      = ["099720109477"]
  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-noble-24.04-amd64-server-*"]
  }
}

# Использование
resource "aws_instance" "web" {
  ami = data.aws_ami.ubuntu.id
}

for_each и count

# for_each (предпочтительнее)
resource "aws_subnet" "private" {
  for_each = toset(var.azs)
  vpc_id            = aws_vpc.main.id
  availability_zone = each.key
  cidr_block        = cidrsubnet(var.cidr, 4, index(var.azs, each.key))
}

# Доступ
output "subnet_ids" {
  value = { for k, v in aws_subnet.private : k => v.id }
}

# count
resource "aws_iam_user" "admins" {
  count = length(var.admins)
  name  = var.admins[count.index]
}

Условия (conditional)

# Тернарный
resource "aws_eip" "nat" {
  count = var.enable_nat ? 1 : 0
  domain = "vpc"
}

# Conditional expressions
locals {
  instance_count = var.environment == "prod" ? 3 : 1
}

Модули

module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "~> 5.0"

  name = "myapp-vpc"
  cidr = "10.0.0.0/16"
  azs             = ["eu-west-1a", "eu-west-1b", "eu-west-1c"]
  private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
  public_subnets  = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]
  enable_nat_gateway = true
}

# Локальный модуль
module "eks" {
  source = "../../modules/eks"
  vpc_id = module.vpc.vpc_id
}

# Git модуль
module "shared" {
  source = "git::https://github.com/org/modules.git//eks?ref=v1.2.0"
}

Полезные функции

length(list)
keys(map)
values(map)
contains(list, value)
toset(list)
tolist(set)
merge(map1, map2)
concat(list1, list2)
join(",", list)
split(",", string)

cidrsubnet("10.0.0.0/16", 8, 2)            # → 10.0.2.0/24
cidrhost("10.0.1.0/24", 5)                 # → 10.0.1.5

formatdate("YYYY-MM-DD", timestamp())
jsondecode(file("config.json"))
yamldecode(file("config.yaml"))
templatefile("template.tpl", {var = val})

base64encode("string")
base64decode(b64)
md5("string")
sha256("string")

Tooling вокруг

tflint                      # линтер
tfsec .                     # security
checkov -d .                # security (поддержка K8s, Helm)
terraform-docs markdown .   # ★ авто-генерация README

# pre-commit с tflint, tfsec, terraform_fmt:
# https://github.com/antonbabenko/pre-commit-terraform

# Terragrunt
terragrunt run-all plan
terragrunt run-all apply