Последняя проверка: май 2026 г.
Разверните сервисы AWS для экзамена TF-PRO с помощью чистого Terraform: пошаговое руководство с привязкой каждого блока к разделам экзамена. Код также совместим с OpenTofu.
К концу этой лабораторной работы вы освоите четыре основные области, на которых сосредоточен экзамен Terraform Pro: создание расширенного HCL, создание и компоновка модулей, операции над состоянием и работа с HCP Terraform — все это с использованием провайдеров random и local, не требующих учетных данных, поэтому вам не придется ничего платить и ничего очищать, кроме одной папки.
Вы будете моделировать входные данные с помощью богатых ограничений типов и валидации, преобразовывать коллекции с использованием выражений и функций, на которые опирается экзамен, создавать переиспользуемый модуль и распространять его с помощью for_each, а затем рефакторить текущее состояние с помощью блоков import, moved и removed. Последний шаг подключает все это к HCP Terraform для удаленного выполнения. Каждый фрагмент кода — это чистый Terraform, идентичный в OpenTofu. Создавайте его в одной рабочей директории; мы указываем файлы, которые находятся в ./modules.
>= 1.9 или OpenTofu >= 1.8 (terraform version). Мы используем блоки removed (1.7+) и import (1.5+), поэтому текущий релиз здесь важнее, чем в лабораторной работе Associate.random и local полностью выполняются на вашей машине.mkdir tf-pro-lab && cd tf-pro-lab).terraform fmt и terraform validate после каждого редактирования — экзамен Pro предполагает, что вы относитесь к ним как к части процесса разработки, а не как к второстепенной задаче.Эта лабораторная работа полностью бесплатна. Провайдеры random и local создают только небольшие файлы на вашем диске плюс локальный terraform.tfstate; ничего не развертывается ни в каком облаке и ничего не тарифицируется в режиме ожидания. Шаг 7 (HCP Terraform) работает на бесплатном уровне (free tier), который включает одну рабочую область для лабораторной работы и операционные функции, о которых спрашивают на экзамене.
Мы начинаем раздел HCL и конфигурация с закрепления актуальной версии Terraform — экзамен Pro предполагает использование языковых особенностей, которые существуют только в более новых версиях (атрибуты объекта optional(), блоки removed). Блок required_providers блокирует random и local, а terraform init записывает файл .terraform.lock.hcl, который вы должны закоммитить, чтобы планы были воспроизводимы для всей команды.
Две привычки, которые нужно выработать сейчас, пройдут через всю лабораторную работу: terraform fmt канонизирует пробелы и выравнивание (экзамен проверяет, что он перезаписывает файлы на месте и завершает работу с ненулевым кодом в режиме -check), а terraform validate проверяет внутреннюю согласованность вашей конфигурации до обращения к любому провайдеру. С закрепленной цепочкой инструментов и этими двумя командами под рукой мы можем писать HCL, который стоит проверять.
terraform {
required_version = ">= 1.9"
required_providers {
random = {
source = "hashicorp/random"
version = "~> 3.6"
}
local = {
source = "hashicorp/local"
version = "~> 2.5"
}
}
}
# Build the muscle memory:
# terraform fmt # rewrite files to canonical style
# terraform validate # check internal consistency, offlineЭкзамен Pro выходит далеко за рамки type = string. Здесь мы объявляем тип object с optional() атрибутами, которые предоставляют значения по умолчанию, если вызывающая сторона их опускает, а затем прикрепляем несколько блоков validation — экзамен проверяет, что переменная может содержать более одного, каждый со своим condition и error_message.
try() в locals — это еще один ход уровня Pro: он возвращает первое выражение, которое вычисляется без ошибок, поэтому чтение возможно отсутствующего атрибута корректно обрабатывается, а не останавливает план. Его 'брат' can() (который возвращает булево значение) появится позже в валидации Шага 4. Эта переменная platform становится единственным источником истины для остальной части лабораторной работы — каждый последующий шаг читает из нее, поэтому правильное определение типа и его гарантий здесь окупится в дальнейшем.
variable "platform" {
description = "Platform configuration."
type = object({
name = string
replicas = optional(number, 2)
features = optional(set(string), [])
owners = list(string)
})
validation {
condition = var.platform.replicas >= 1 && var.platform.replicas <= 10
error_message = "replicas must be between 1 and 10."
}
validation {
condition = length(var.platform.owners) > 0
error_message = "At least one owner is required."
}
default = {
name = "lab"
owners = ["platform@example.com"]
}
}
locals {
# try() returns the first error-free expression - here it shields
# against features being unset and classifies the tier.
tier = length(try(var.platform.features, [])) > 0 ? "enhanced" : "standard"
}Манипуляции с коллекциями — самая сложная часть домена HCL и конфигурация. Мы используем setproduct() для создания каждой пары регион/сервис, затем выражение for для преобразования этих пар в отображение с ключами — это точный шаблон, который позднее будет использоваться for_each. Наряду с этим, flatten() + distinct() сворачивают список списков владельцев в чистый уникальный набор.
Это функции, к которым вопросы Pro-экзамена возвращаются снова и снова: for с => для создания отображения, setproduct для декартовых произведений, flatten для удаления одного уровня вложенности, merge для объединения отображений и jsonencode для сериализации результата. Мы выводим вычисленное отображение deployments в файл, чтобы вы могли выполнить cat out/deployments.json и увидеть форму, которую произвело ваше выражение. Это отображение с ключами — именно то значение, которое мы передадим for_each модуля в следующем шаге.
locals {
regions = ["us-east-1", "eu-west-1"]
services = ["api", "web"]
# setproduct builds every (region, service) pair; the for
# expression reshapes the pairs into a keyed map.
deployments = {
for pair in setproduct(local.regions, local.services) :
"${pair[0]}/${pair[1]}" => {
region = pair[0]
service = pair[1]
}
}
# flatten + distinct collapse nested owner lists into a unique set.
all_owners = distinct(flatten([
for owner in var.platform.owners : split(",", owner)
]))
}
resource "local_file" "matrix" {
filename = "${path.module}/out/deployments.json"
content = jsonencode(local.deployments)
}Теперь домен Модули (25%). Мы извлекаем дочерний модуль workload в ./modules/workload с его собственными required_providers, проверенными входами и опубликованными выходами. Выражение can(regex(...)) в валидации имени — это идиома Pro-уровня: regex выдал бы ошибку при несовпадении, а can превращает эту ошибку в false, которое может использовать condition.
Хорошо построенный модуль скрывает свою внутреннюю реализацию и предоставляет стабильный контракт — вызывающие стороны передают name и replicas и получают в ответ id и manifest_path, никогда не касаясь random_string внутри. То, что модуль объявляет свои собственные требования к провайдерам, является преднамеренным пунктом Pro-экзамена: конфигурация провайдера наследуется от корня по умолчанию, но требования к провайдеру объявляются для каждого модуля. Имея чистый модуль, интересный вопрос заключается в том, как инстанцировать его многократно и соединить эти экземпляры.
# modules/workload/main.tf
terraform {
required_providers {
random = {
source = "hashicorp/random"
version = "~> 3.6"
}
local = {
source = "hashicorp/local"
version = "~> 2.5"
}
}
}
variable "name" {
type = string
validation {
# can() turns regex's raise-on-no-match into a usable bool.
condition = can(regex("^[a-z][a-z0-9-]{1,30}$", var.name))
error_message = "name must be 2-31 chars: lowercase letter, then letters/digits/hyphens."
}
}
variable "replicas" {
type = number
default = 1
}
resource "random_string" "suffix" {
length = 6
special = false
upper = false
}
resource "local_file" "manifest" {
filename = "${path.root}/out/${var.name}.json"
content = jsonencode({
name = var.name
replicas = var.replicas
id = "${var.name}-${random_string.suffix.result}"
})
}
output "id" {
description = "Stable identifier for this workload."
value = "${var.name}-${random_string.suffix.result}"
}
output "manifest_path" {
value = local_file.manifest.filename
}Все еще в домене Модули, мы теперь вызываем module.workload один раз для каждой рабочей нагрузки с for_each по набору, устанавливаем replicas условно для каждого ключа, а затем потребляем выход id каждого экземпляра в единый агрегированный registry.json. Итерация по module.workload с выражением for — это канонический способ, которым экзамен связывает выходы одного модуля с входами другого ресурса.
Явный depends_on на агрегированном файле является учебным моментом: неявные зависимости от ссылки module.workload уже правильно упорядочивают вещи, но depends_on делает намерение очевидным и иногда требуется, когда зависимость не выражается через данные. Запустите terraform init после добавления источника модуля, затем apply. Имея несколько активных экземпляров модуля и агрегированные их выходы, мы получаем реальное состояние, с которым стоит работать — это следующий домен.
# main.tf (root) - fan the module out, then aggregate its outputs
module "workload" {
source = "./modules/workload"
for_each = toset(["api", "web", "worker"])
name = each.key
replicas = each.key == "api" ? 3 : 1
}
resource "local_file" "registry" {
filename = "${path.module}/out/registry.json"
content = jsonencode({
for k, m in module.workload : k => m.id
})
depends_on = [module.workload]
}
output "workload_ids" {
value = { for k, m in module.workload : k => m.id }
}Домен CLI и управление состоянием (25%) — это то, что отличает экзамен Pro от Associate. Мы используем три операции с состоянием, управляемые конфигурацией. Блок import принимает существующий объект в состояние — мы нацелены на встроенный terraform_data, чтобы пример оставался без учетных данных. Блок removed удаляет ресурс из состояния без уничтожения реального объекта — именно то, что вы используете при передаче владения другой конфигурации. А terraform_data с replace_triggered_by принудительно вызывает последующую замену всякий раз, когда изменяется реестр из Шага 5, без участия провайдера.
Также знайте императивные эквиваленты CLI, которые все еще проверяются на экзамене: terraform state list и state show для инспектирования, terraform state mv для переименования, terraform state rm для 'забывания', terraform plan -target=ADDR для сужения области выполнения, и terraform apply -replace=ADDR для принудительного пересоздания одного ресурса. С состоянием, находящимся под вашим полным контролем локально, последний домен — это выполнение всего этого на HCP Terraform.
# 1) Adopt an existing object into state (Terraform 1.5+),
# replacing the older imperative "terraform import" command.
import {
to = terraform_data.legacy
id = "existing-id"
}
resource "terraform_data" "legacy" {}
# 2) Stop managing a resource WITHOUT destroying it (Terraform 1.7+).
removed {
from = random_string.deprecated
lifecycle {
destroy = false
}
}
# 3) terraform_data + replace_triggered_by recreates a marker
# whenever the registry file content changes.
resource "terraform_data" "deploy_marker" {
input = local_file.registry.content
lifecycle {
replace_triggered_by = [local_file.registry]
}
}Домен Операции HCP Terraform (20%) завершает экзамен. Блок cloud — здесь с использованием workspaces { tags = [...] }, так что одна конфигурация может быть сопоставлена со множеством рабочих областей, соответствующих тегам — переносит выполнение с вашего ноутбука. Запуски происходят в HCP Terraform с удаленным, заблокированным состоянием; экзамен противопоставляет режимы выполнения удаленный (remote), локальный (local) и агентский (agent), а также рабочие процессы запуска: управляемый VCS (VCS-driven), управляемый CLI (CLI-driven) и управляемый API (API-driven).
Здесь также собираются воедино операционные возможности, названные в Pro-экзамене: наборы переменных (variable sets) для совместного использования входных данных в рабочих областях, триггеры запуска (run triggers) для связывания применения одной рабочей области с планом другой, задачи запуска (run tasks) для внешних интеграций, наборы политик (policy sets) (Sentinel или OPA) для ограничения применения, и частный реестр модулей (private module registry) для публикации модулей, подобных тому, который вы создали в Шаге 4. Замените my-org на название вашей организации, запустите terraform login, затем terraform init, чтобы перенести состояние. Конфигурация, которую вы написали в Шагах 1–6, выполняется без изменений — изменились только место и ограждения вокруг нее, что и является всей историей Pro.
terraform {
cloud {
organization = "my-org"
workspaces {
tags = ["terraform-pro-lab"]
}
}
}
# Authenticate once, then migrate state to the remote workspace:
# terraform login
# terraform initВсе локально, поэтому удаление происходит быстро:
terraform destroy, чтобы удалить сгенерированные файлы и очистить их из состояния. Из-за блока removed в Шаге 6, random_string.deprecated (если он когда-либо существовал) забывается, а не уничтожается — это преднамеренно.terraform destroy для удаленной рабочей области, затем удалите рабочую область terraform-pro-lab в пользовательском интерфейсе и удалите блок cloud.cd .. && rm -rf tf-pro-lab. Кэш .terraform/, .terraform.lock.hcl и любое локальное состояние удаляются вместе с ней.Экзамен Pro проверяет создание и эксплуатацию Terraform, а не конкретное облако, поэтому эта лабораторная работа намеренно не развертывает облачную инфраструктуру — это позволяет ей оставаться без учетных данных и дает возможность каждому шагу сосредоточиться на HCL, модулях, состоянии и HCP Terraform.
Несколько тем, относящихся к Pro-экзамену, лучше изучать теоретически, чем развертывать здесь: dynamic блоки (которым требуется тип ресурса с повторяющимися вложенными блоками — облачный ресурс — чтобы быть осмысленными), configuration_aliases провайдера и передача псевдонимных провайдеров в модули, разработка политик Sentinel / OPA, а также процесс публикации в частном реестре модулей. Разделы Просмотр и Справочник этой страницы сертификации концептуально охватывают эти темы. Практическая ценность здесь заключается в цикле разработки и эксплуатации, вокруг которого построен экзамен: проверенные сложные входные данные, скомпонованные модули, бесстрашные операции над состоянием и удаленные запуски.