lesson ── terraform-intermediate ── ~16 мин ── 5 шагов
Четыре утилитарных провайдера, без которых не обходится практический
HCL: random (уникальные имена и пароли), time (метки и задержки),
archive (упаковка lambda-кода), external (вызов любого скрипта). В
этом уроке используешь все четыре в одном проекте. Это типичный «зоопарк»
reusable-блоков.
интерактивный sandbox
Поднимется пара контейнеров: terraform 1.9 и localstack 3.8 в одной сети. В браузере откроется терминал, можно сразу terraform init. Каждый шаг проверяется автоматически. TTL 45 минут, без регистрации.
stack ── terraform · localstack · 1 GB RAM · самоуничтожается через 45 мин простоя
cd /home/student/tf-utility
cat > lambda-src/handler.py <<'EOF'
def main(event, context):
return {"statusCode": 200, "body": "hello from terraformlab"}EOF
ls lambda-src/
Дальше. Terraform-часть. В main.tf:
terraform { required_providers { archive = {source = "hashicorp/archive"
version = "~> 2.6"
}
random = {source = "hashicorp/random"
version = "~> 3.6"
}
time = {source = "hashicorp/time"
version = "~> 0.12"
}
}
}
data "archive_file" "lambda" {type = "zip"
source_file = "${path.module}/lambda-src/handler.py" output_path = "${path.module}/lambda.zip"}
Запусти init + плана:
terraform init
terraform plan
Plan покажет что data.archive_file будет прочитан. Файл lambda.zip
создаст сам провайдер на этапе apply.
✓ archive_file прописан. Теперь добавим остальные провайдеры.
Добавь в main.tf:
resource "random_id" "fn_suffix" {byte_length = 4
}
resource "random_password" "api_key" {length = 24
special = false
}
resource "time_static" "created_at" {}locals { function_name = "linuxlab-utility-${random_id.fn_suffix.hex}" created_date = formatdate("YYYY-MM-DD", time_static.created_at.rfc3339)}
Что происходит:
random_id.fn_suffix.hex, 8 hex-символов, уникальные при создании,
стабильные между apply (записаны в state).random_password.api_key.result, пароль длиной 24, без спецсимволов.
Помечен sensitive автоматически.time_static.created_at, фиксирует timestamp при первом apply.
Дальше не меняется.См. tf-utility-providers.
✓ Random + time прописаны. Теперь создадим Lambda и положим её на S3.
Добавь в main.tf:
resource "aws_iam_role" "lambda" { name = "${local.function_name}-role" assume_role_policy = jsonencode({Version = "2012-10-17"
Statement = [{Action = "sts:AssumeRole"
Effect = "Allow"
Principal = { Service = "lambda.amazonaws.com" }}]
})
}
resource "aws_lambda_function" "demo" {function_name = local.function_name
filename = data.archive_file.lambda.output_path
source_code_hash = data.archive_file.lambda.output_base64sha256
handler = "handler.main"
runtime = "python3.12"
role = aws_iam_role.lambda.arn
environment { variables = {API_KEY = random_password.api_key.result
CREATED_DATE = local.created_date
}
}
tags = {CreatedAt = local.created_date
}
}
output "function_name" {value = aws_lambda_function.demo.function_name
}
Главное:
source_code_hash = data.archive_file.lambda.output_base64sha256,
triggerит апдейт Lambda при изменении кода. Без него Terraform
не заметит правки в handler.py.random_password.api_key.result идёт в env Lambda. В state он
в открытом виде.local.created_date, рассчитанная локаль из time_static.terraform apply -auto-approve
✓ Lambda создана с уникальным именем и timestamp.
Покажем, что source_code_hash работает: поменяем код handler'а.
cat > lambda-src/handler.py <<'EOF'
def main(event, context):
return {"statusCode": 200, "body": "hello v2"}EOF
terraform plan
Plan покажет:
# aws_lambda_function.demo will be updated in-place
~ source_code_hash = "..." -> "..."
Terraform увидел что hash изменился (потому что код изменился) и
хочет обновить Lambda. Без source_code_hash он бы не заметил,
filename тот же.
terraform apply -auto-approve
Lambda перевыкатилась с новым кодом.
✓ Lambda обновилась. archive_file + source_code_hash работает.
OpenTofu держит CLI и state совместимыми с Terraform по командам
этого шага: миграция обычно проходит через mv .terraform .terraform.bak; tofu init -upgrade. Но при первом переходе
сделай backup state и прогон на feature-branch - расхождения
концентрируются в новых фичах (variables в backend,
state-encryption, OCI registry-backed модули). См.
tf-opentofu-parity для полной матрицы.
Иногда нужно «новый suffix, новый бакет, всё свежее». Без HCL-изменения random_id хранится в state и не меняется. Принуди:
terraform apply -auto-approve -replace=random_id.fn_suffix
Plan покажет:
# random_id.fn_suffix will be replaced (due to -replace)
И каскадно, все ресурсы, которые от него зависят (Lambda, IAM-роль, потому что в имени есть random_id.fn_suffix.hex):
# aws_iam_role.lambda will be destroyed
# aws_iam_role.lambda will be created (new name)
...
Apply пройдёт, всё пересоздастся с новым suffix. Это типичный кейс «протух pet name, хочу свежий».
✓ Регенерация random отработала каскадно.
Иногда нужно прочитать значение из источника, которого нет среди AWS data sources: git commit hash, secret из внутреннего хранилища, результат HTTP-запроса с auth.
terraform { required_providers { external = {source = "hashicorp/external"
version = "~> 2.3"
}
}
}
data "external" "build_info" { program = ["bash", "${path.module}/scripts/build-info.sh"]}
resource "aws_lambda_function" "demo" {# ...
tags = {GitCommit = data.external.build_info.result.commit
}
}
Скрипт build-info.sh:
#!/usr/bin/env bash
set -euo pipefail
commit=$(git -C / rev-parse HEAD 2>/dev/null || echo "unknown")
jq -n --arg c "$commit" '{commit: $c}'Важно:
tonumber(data.external.x.result.count).См. tf-archive-external-http про все три data-провайдера.
random, уникальные ID и пароли (sensitive в state): time, таймстампы и задержки. archive_file, упаковка файлов в zip для Lambda и Layers. external, выполнить произвольный скрипт, обменяться JSON. Все четыре HashiCorp-official, стабильные, без зависимости от AWS-API.
команды
terraform apply -replace=random_id.suffixпринудительная регенерация: каскад пересоздаст всё что зависитterraform state show random_password.userпароль виден в state: sensitive только в выводеterraform planexternal/archive вычисляются на каждом plan: учитывайконцепции