linuxlab.io
Учебники▾
  • Линукс и сети
    Файловая система, процессы, TCP/IP, BGP и OSPF
    →
  • Terraform и IaC
    HCL, state, plan/apply на sandbox LocalStack
    →
  • Git и GitHub
    Объектная модель, plumbing, ветвление, GitHub Actions
    →
Все учебники →
ЦеныО платформеВойтиСоздать аккаунт
/
Intro
Lessons
Footer
linuxlab-УчебникиЦеныО платформеКонфиденциальность и куки
Copyright © 2026 LinuxLab. Все права защищены.
linuxlab.io
Учебники▾
  • Линукс и сети
    Файловая система, процессы, TCP/IP, BGP и OSPF
    →
  • Terraform и IaC
    HCL, state, plan/apply на sandbox LocalStack
    →
  • Git и GitHub
    Объектная модель, plumbing, ветвление, GitHub Actions
    →
Все учебники →
ЦеныО платформеВойтиСоздать аккаунт
/
  • Введение
  • Уроки
  • How it works
  • База знаний
  • Шпаргалка
  • Capstone
  • Собеседование
home/terraform/kb/Модули/tf-module-inputs-outputs

kb/modules ── Модули ── intermediate

Контракт модуля: input variables и outputs

Снаружи модуль виден только через input variables (что принимает) и output values (что отдаёт). Всё остальное, детали реализации. Хороший модуль скрывает ресурсы за этим контрактом так, чтобы можно было переписать тело без изменения вызовов в root-модуле.

view as markdownaka: terraform-module-contract, terraform-module-interface

Зачем модулю контракт

Модуль, это чёрный ящик для root'а. Root знает: «передаю эти input'ы, получаю эти output'ы». Что внутри, нерелевантно. Это позволяет:

  • Переписать ресурсы внутри модуля без правки root'а.
  • Использовать один модуль десятью разными root'ами.
  • Менять провайдер (aws_s3_bucket_v2 вместо aws_s3_bucket) при обновлении без каскадных правок наружу.

Контракт = variable блоки и output блоки. Всё остальное в модуле, деталь реализации.

Input variables

Каждый variable блок, параметр модуля. У него есть тип, описание, опционально default, опционально validation:

hcl
# modules/s3-bucket/variables.tf
variable "name" {
  type        = string
  description = "Имя S3-бакета. Должно быть глобально уникально."
  validation {
    condition     = length(var.name) >= 3 && length(var.name) <= 63
    error_message = "Bucket name должен быть 3-63 символа (правило AWS)."
  }
  validation {
    condition     = can(regex("^[a-z0-9.-]+$", var.name))
    error_message = "Bucket name: только lowercase, цифры, точка, дефис."
  }
}
variable "versioning_enabled" {
  type        = bool
  description = "Включить versioning. Для prod-бакетов обычно true."
  default     = false
}
variable "lifecycle_rules" {
  type = list(object({
    id      = string
    enabled = bool
    transition = optional(object({
      days   = number
      target = string
    }))
  }))
  description = "Lifecycle rules. См. examples/."
  default     = []
}

Без default, обязательный аргумент

Если у variable нет default, вызывающий обязан передать значение. Иначе terraform ругается на plan. Это хорошо: обязательные имена должны быть обязательными.

Тип имеет значение

type = string, пользователь может передать только строку. Не 42 (число), не ["a", "b"] (список). Опечатка типа ловится на plan, не на apply. См. hcl-types.

Validation, защита границ модуля

Validation ловит то, что тип не ловит:

  • «Длина 3-63», типу string всё равно.
  • «Только lowercase», типу string всё равно.
  • «days > 0», типу number всё равно.

Validation запускается на plan, до любых API-вызовов. Это дешёвый способ отсечь невалидные конфигурации. Без него ошибка вылезет в AWS как «InvalidBucketName», и пользователь модуля не сразу поймёт что не так.

Output values

Outputs, то что модуль возвращает родителю:

hcl
# modules/s3-bucket/outputs.tf
output "arn" {
  value       = aws_s3_bucket.this.arn
  description = "ARN бакета. Используется для IAM-политик."
}
output "bucket" {
  value       = aws_s3_bucket.this.bucket
  description = "Имя бакета (как передано в input)."
}
output "regional_domain_name" {
  value       = aws_s3_bucket.this.bucket_regional_domain_name
  description = "DNS-имя для прямых S3-запросов."
}

В root-модуле читаются как module.<имя>.<output_name>:

hcl
output "logs_arn" {
  value = module.logs.arn
}
resource "aws_iam_policy" "logs_writer" {
  policy = jsonencode({
    Statement = [{
      Effect   = "Allow"
      Action   = "s3:PutObject"
      Resource = "${module.logs.arn}/*"
    }]
  })
}

sensitive output

Если в output попадает секрет, пометь:

hcl
output "access_key_secret" {
  value     = aws_iam_access_key.user.secret
  sensitive = true
}

Это не шифрует значение. State по-прежнему содержит секрет в открытом виде (плохо, см. tf-state). sensitive = true лишь скрывает значение из CLI-вывода terraform output и plan. Реальная защита секретов, через secret manager (Vault, SSM Parameter Store), не через sensitive = true.

Output не выполняет depends_on неявно

Если output зависит от ресурса, который существует только при определённых условиях, добавь depends_on явно:

hcl
output "lifecycle_rule_id" {
  value      = length(aws_s3_bucket_lifecycle_configuration.this) > 0 ? aws_s3_bucket_lifecycle_configuration.this[0].id : null
  depends_on = [aws_s3_bucket_lifecycle_configuration.this]
}

Как проектировать контракт

Параметризуй то, что меняется

Не делай переменную «на всякий случай». Variable должна отражать реальное различие между use-case'ами модуля.

hcl
# ПЛОХО: на всякий случай
variable "force_destroy" {
  type    = bool
  default = false
}
variable "object_ownership" {
  type    = string
  default = "BucketOwnerEnforced"
}
# ещё 15 variable которые никто не меняет
# ХОРОШО: только то, что реально разное между use-case'ами
variable "name" { type = string }
variable "tags" { type = map(string), default = {} }
variable "lifecycle_rules" { type = list(object({...})), default = [] }

Не-параметризованные значения держи в locals внутри модуля.

Имена, это контракт

Переименуешь variable "name" → variable "bucket_name", это breaking change для всех root'ов, которые используют модуль. Те же правила, что с API: ломать имена нельзя, или это новая major-версия. См. tf-module-versioning.

Группируй сложные параметры в object

Десять плоских variable "rule_X_..." хуже, чем один variable "rules" типа list(object(...)). С object-типом юзер видит сразу всю схему, не гадает какие из переменных идут вместе.

Подводные камни

  • Validation запускается на каждый plan. Если в validation тяжёлая регулярка или большой contains([...100 элементов...], var.x), это время. Обычно неважно, но в модулях которые вызываются 50 раз через for_each, может быть заметно.

  • description нужен. Без description terraform-docs не сгенерит нормальный README, и пользователь модуля будет читать сам HCL чтобы понять что значит bucket_acl. Заодно, это бесплатная документация которую IDE показывает на hover.

  • default = null ≠ нет default. default = null означает «по умолчанию значение null, и это валидно». Если хочешь сделать аргумент обязательным, убирай default целиком, а не ставь null.

  • sensitive = true на input не шифрует, только маскирует. Те же оговорки что для output: значение всё равно в state. См. tf-state.

  • Output, ссылающийся на ресурс через count, ломается когда count = 0. aws_s3_bucket.this[0].arn упадёт с «index 0 out of range» если ресурса нет. Используй try(aws_s3_bucket.this[0].arn, null) или splat: aws_s3_bucket.this[*].arn (вернёт список, возможно пустой).

  • Нельзя сделать validation, ссылающуюся на другой variable. TF 1.9+ добавил cross-variable validation через precondition в lifecycle, но в самом variable блоке всё ещё только self-validation.

§ команды

bash
terraform plan -var-file=examples/dev.tfvars

Тестируем контракт модуля с конкретным набором значений.

bash
terraform-docs markdown table modules/s3-bucket > modules/s3-bucket/README.md

Генерация README с таблицей variables и outputs. Установи отдельно: github.com/terraform-docs.

bash
terraform console -chdir=modules/s3-bucket

REPL внутри модуля. Полезно для проверки выражений в outputs.

§ см. также

  • tf-variableБлок variable: вход в конфигурациюvariable, параметр, который принимает значение снаружи (CLI, env, .tfvars). Объявляешь в HCL: type, default, description, validation. Используешь как var.name. Нужен чтобы убрать хардкод и переиспользовать один HCL для разных окружений.
  • tf-outputБлок output: что Terraform возвращает наружуoutput, это значение, которое Terraform показывает после apply и сохраняет в state. Используется для (а) показать пользователю ID/ARN созданного ресурса, (б) передать значения между модулями, (в) скриптам через terraform output -raw.
  • tf-module-compositionКомпозиция модулей: module of modules, передача провайдеровМодуль может вызывать другие модули, внутри своего main.tf через тот же `module` блок. Адрес в state становится `module.A.module.B.<тип>.<имя>`. Провайдеры наследуются по умолчанию, но при нескольких alias'ах (multi-region, multi-account): передаются явно через `providers = { aws = aws.eu }`. `for_each` над модулем работает с TF 0.13+.
  • tf-sensitivesensitive в Terraform: про логи, не про шифрование`sensitive = true` у variable/output/local прячет значение от CLI-вывода и логов. В state и .tfstate.backup значение **открытое**. Это redaction, не encryption. Реально секретное, храни в Secrets Manager или Vault, читай через data-source, плана не сохраняй в артефактах. `nonsensitive()` нужен в редких случаях когда sensitive-флаг каскадируется и мешает.
Footer
linuxlab-
Copyright © 2026 LinuxLab. Все права защищены.
Учебники
Цены
О платформе
Конфиденциальность и куки