Terraform Module Writer
Эксперт по созданию высококачественных, переиспользуемых Terraform модулей с соблюдением industry best practices.
Module Structure
Standard Directory Layout
modules/ └── my-module/ ├── main.tf # Primary resource definitions ├── variables.tf # Input variable declarations ├── outputs.tf # Output value definitions ├── versions.tf # Provider version constraints ├── locals.tf # Local values and computations ├── data.tf # Data source definitions ├── README.md # Module documentation ├── CHANGELOG.md # Version history ├── examples/ │ ├── basic/ │ │ ├── main.tf │ │ └── outputs.tf │ └── advanced/ │ ├── main.tf │ └── outputs.tf └── tests/ └── module_test.go
versions.tf Template
terraform { required_version = ">= 1.5.0"
required_providers { aws = { source = "hashicorp/aws" version = ">= 5.0.0, < 6.0.0" } random = { source = "hashicorp/random" version = ">= 3.5.0" } } }
Input Variable Design
Variable Declaration Best Practices
variables.tf
Required variables (no default)
variable "name" { description = "Name prefix for all resources created by this module" type = string
validation { condition = can(regex("^[a-z][a-z0-9-]*$", var.name)) error_message = "Name must start with a letter and contain only lowercase letters, numbers, and hyphens." } }
variable "environment" { description = "Environment name (e.g., dev, staging, prod)" type = string
validation { condition = contains(["dev", "staging", "prod"], var.environment) error_message = "Environment must be one of: dev, staging, prod." } }
Optional variables with sensible defaults
variable "instance_type" { description = "EC2 instance type for the application servers" type = string default = "t3.medium"
validation { condition = can(regex("^[a-z][0-9][.][a-z]+$", var.instance_type)) error_message = "Instance type must be a valid AWS instance type format." } }
variable "enable_monitoring" { description = "Enable detailed monitoring for resources" type = bool default = true }
variable "min_instances" { description = "Minimum number of instances in the Auto Scaling Group" type = number default = 2
validation { condition = var.min_instances >= 1 && var.min_instances <= 100 error_message = "Minimum instances must be between 1 and 100." } }
variable "max_instances" { description = "Maximum number of instances in the Auto Scaling Group" type = number default = 10
validation { condition = var.max_instances >= 1 && var.max_instances <= 100 error_message = "Maximum instances must be between 1 and 100." } }
Complex Variable Types
Object type with optional attributes
variable "vpc_config" { description = "VPC configuration for the module" type = object({ vpc_id = string subnet_ids = list(string) security_group_ids = optional(list(string), []) assign_public_ip = optional(bool, false) })
validation { condition = length(var.vpc_config.subnet_ids) >= 2 error_message = "At least 2 subnet IDs required for high availability." } }
Map with specific value types
variable "tags" { description = "Additional tags to apply to all resources" type = map(string) default = {}
validation { condition = alltrue([for k, v in var.tags : can(regex("^[a-zA-Z][a-zA-Z0-9:/_-]*$", k))]) error_message = "Tag keys must start with a letter and contain only valid characters." } }
List of objects
variable "ingress_rules" { description = "List of ingress rules for the security group" type = list(object({ description = string from_port = number to_port = number protocol = string cidr_blocks = optional(list(string), []) security_groups = optional(list(string), []) })) default = []
validation { condition = alltrue([ for rule in var.ingress_rules : rule.from_port >= 0 && rule.from_port <= 65535 && rule.to_port >= 0 && rule.to_port <= 65535 ]) error_message = "Port numbers must be between 0 and 65535." } }
Sensitive variables
variable "database_password" { description = "Master password for the RDS instance" type = string sensitive = true
validation { condition = length(var.database_password) >= 16 error_message = "Database password must be at least 16 characters." } }
Local Values
locals.tf Template
locals {
Naming convention
name_prefix = "${var.name}-${var.environment}"
Common tags applied to all resources
common_tags = merge( var.tags, { Name = local.name_prefix Environment = var.environment ManagedBy = "terraform" Module = "my-module" Project = var.name } )
Computed values
is_production = var.environment == "prod" enable_encryption = local.is_production backup_retention = local.is_production ? 30 : 7 instance_count = local.is_production ? var.max_instances : var.min_instances
Derived configurations
monitoring_config = { enabled = var.enable_monitoring || local.is_production detailed = local.is_production retention_days = local.is_production ? 90 : 30 alarm_threshold = local.is_production ? 80 : 90 }
Dynamic block helpers
ingress_rules_map = { for idx, rule in var.ingress_rules : "${rule.protocol}-${rule.from_port}-${rule.to_port}" => rule }
Conditional resource naming
bucket_name = var.bucket_name != null ? var.bucket_name : "${local.name_prefix}-storage-${random_id.bucket.hex}" }
Resource Definitions
main.tf Template
main.tf
#------------------------------------------------------------------------------
Security Group
#------------------------------------------------------------------------------ resource "aws_security_group" "this" { name_prefix = "${local.name_prefix}-sg-" description = "Security group for ${var.name} ${var.environment}" vpc_id = var.vpc_config.vpc_id
tags = merge(local.common_tags, { Name = "${local.name_prefix}-sg" })
lifecycle { create_before_destroy = true } }
resource "aws_security_group_rule" "ingress" { for_each = local.ingress_rules_map
type = "ingress" security_group_id = aws_security_group.this.id description = each.value.description from_port = each.value.from_port to_port = each.value.to_port protocol = each.value.protocol cidr_blocks = length(each.value.cidr_blocks) > 0 ? each.value.cidr_blocks : null source_security_group_id = length(each.value.security_groups) > 0 ? each.value.security_groups[0] : null }
resource "aws_security_group_rule" "egress" { type = "egress" security_group_id = aws_security_group.this.id description = "Allow all outbound traffic" from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] }
#------------------------------------------------------------------------------
Launch Template
#------------------------------------------------------------------------------ resource "aws_launch_template" "this" { name_prefix = "${local.name_prefix}-lt-" description = "Launch template for ${var.name} ${var.environment}" image_id = data.aws_ami.amazon_linux.id instance_type = var.instance_type
network_interfaces { associate_public_ip_address = var.vpc_config.assign_public_ip security_groups = concat([aws_security_group.this.id], var.vpc_config.security_group_ids) delete_on_termination = true }
iam_instance_profile { arn = aws_iam_instance_profile.this.arn }
monitoring { enabled = local.monitoring_config.detailed }
metadata_options { http_endpoint = "enabled" http_tokens = "required" # IMDSv2 http_put_response_hop_limit = 1 }
block_device_mappings { device_name = "/dev/xvda" ebs { volume_size = var.root_volume_size volume_type = "gp3" encrypted = local.enable_encryption kms_key_id = local.enable_encryption ? var.kms_key_id : null delete_on_termination = true } }
tag_specifications { resource_type = "instance" tags = local.common_tags }
tag_specifications { resource_type = "volume" tags = local.common_tags }
tags = local.common_tags
lifecycle { create_before_destroy = true } }
#------------------------------------------------------------------------------
Auto Scaling Group
#------------------------------------------------------------------------------ resource "aws_autoscaling_group" "this" { name_prefix = "${local.name_prefix}-asg-" desired_capacity = var.desired_instances min_size = var.min_instances max_size = var.max_instances vpc_zone_identifier = var.vpc_config.subnet_ids health_check_type = "ELB" health_check_grace_period = 300
launch_template { id = aws_launch_template.this.id version = "$Latest" }
dynamic "tag" { for_each = local.common_tags content { key = tag.key value = tag.value propagate_at_launch = true } }
lifecycle { create_before_destroy = true ignore_changes = [desired_capacity] } }
Data Sources
data.tf Template
data.tf
Get current AWS account info
data "aws_caller_identity" "current" {} data "aws_region" "current" {} data "aws_partition" "current" {}
Get latest Amazon Linux 2023 AMI
data "aws_ami" "amazon_linux" { most_recent = true owners = ["amazon"]
filter { name = "name" values = ["al2023-ami-*-x86_64"] }
filter { name = "virtualization-type" values = ["hvm"] }
filter { name = "architecture" values = ["x86_64"] } }
Get VPC info if not provided
data "aws_vpc" "selected" { id = var.vpc_config.vpc_id }
Get subnet info for AZ distribution
data "aws_subnet" "selected" { for_each = toset(var.vpc_config.subnet_ids) id = each.value }
Get available AZs
data "aws_availability_zones" "available" { state = "available" filter { name = "opt-in-status" values = ["opt-in-not-required"] } }
Output Definitions
outputs.tf Template
outputs.tf
#------------------------------------------------------------------------------
Primary Outputs
#------------------------------------------------------------------------------ output "id" { description = "The unique identifier for this module instance" value = aws_autoscaling_group.this.id }
output "name" { description = "The name prefix used for all resources" value = local.name_prefix }
output "arn" { description = "ARN of the Auto Scaling Group" value = aws_autoscaling_group.this.arn }
#------------------------------------------------------------------------------
Security Group Outputs
#------------------------------------------------------------------------------ output "security_group_id" { description = "ID of the security group created for this module" value = aws_security_group.this.id }
output "security_group_arn" { description = "ARN of the security group" value = aws_security_group.this.arn }
output "security_group_name" { description = "Name of the security group" value = aws_security_group.this.name }
#------------------------------------------------------------------------------
Launch Template Outputs
#------------------------------------------------------------------------------ output "launch_template_id" { description = "ID of the launch template" value = aws_launch_template.this.id }
output "launch_template_arn" { description = "ARN of the launch template" value = aws_launch_template.this.arn }
output "launch_template_latest_version" { description = "Latest version of the launch template" value = aws_launch_template.this.latest_version }
#------------------------------------------------------------------------------
Computed/Derived Outputs
#------------------------------------------------------------------------------ output "ami_id" { description = "ID of the AMI used for instances" value = data.aws_ami.amazon_linux.id }
output "availability_zones" { description = "List of availability zones where resources are deployed" value = distinct([for s in data.aws_subnet.selected : s.availability_zone]) }
output "configuration" { description = "Summary of the module configuration" value = { environment = var.environment instance_type = var.instance_type min_instances = var.min_instances max_instances = var.max_instances monitoring_enabled = local.monitoring_config.enabled encryption_enabled = local.enable_encryption } }
#------------------------------------------------------------------------------
Sensitive Outputs
#------------------------------------------------------------------------------ output "iam_role_arn" { description = "ARN of the IAM role attached to instances" value = aws_iam_role.this.arn sensitive = false }
Conditional Resource Creation
Using count vs for_each
Use count for simple on/off toggles
resource "aws_cloudwatch_metric_alarm" "high_cpu" { count = var.enable_monitoring ? 1 : 0
alarm_name = "${local.name_prefix}-high-cpu" comparison_operator = "GreaterThanThreshold" evaluation_periods = 2 metric_name = "CPUUtilization" namespace = "AWS/EC2" period = 300 statistic = "Average" threshold = local.monitoring_config.alarm_threshold alarm_description = "CPU utilization exceeded threshold" alarm_actions = var.alarm_actions
dimensions = { AutoScalingGroupName = aws_autoscaling_group.this.name }
tags = local.common_tags }
Use for_each for collections
resource "aws_s3_bucket" "logs" { for_each = var.enable_logging ? toset(["access", "audit", "error"]) : toset([])
bucket = "${local.name_prefix}-${each.key}-logs"
tags = merge(local.common_tags, { LogType = each.key }) }
for_each with complex objects
resource "aws_route53_record" "this" { for_each = { for record in var.dns_records : "${record.name}-${record.type}" => record }
zone_id = var.route53_zone_id name = each.value.name type = each.value.type ttl = each.value.ttl records = each.value.records }
Module Composition
Root Module Example
examples/complete/main.tf
terraform { required_version = ">= 1.5.0" }
provider "aws" { region = var.region }
Network module
module "vpc" { source = "terraform-aws-modules/vpc/aws" version = "~> 5.0"
name = "${var.name}-vpc" cidr = "10.0.0.0/16"
azs = ["${var.region}a", "${var.region}b", "${var.region}c"] 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 single_nat_gateway = var.environment != "prod"
tags = var.tags }
Application module
module "app" { source = "../../"
name = var.name environment = var.environment
vpc_config = { vpc_id = module.vpc.vpc_id subnet_ids = module.vpc.private_subnets }
instance_type = var.instance_type min_instances = var.min_instances max_instances = var.max_instances enable_monitoring = true
ingress_rules = [ { description = "HTTP from ALB" from_port = 80 to_port = 80 protocol = "tcp" security_groups = [module.alb.security_group_id] } ]
tags = var.tags }
ALB module
module "alb" { source = "terraform-aws-modules/alb/aws" version = "~> 8.0"
name = "${var.name}-alb" load_balancer_type = "application" vpc_id = module.vpc.vpc_id subnets = module.vpc.public_subnets
target_groups = [ { name = "${var.name}-tg" backend_protocol = "HTTP" backend_port = 80 target_type = "instance" } ]
tags = var.tags }
Outputs
output "vpc_id" { value = module.vpc.vpc_id }
output "app_security_group_id" { value = module.app.security_group_id }
output "alb_dns_name" { value = module.alb.lb_dns_name }
Documentation
README.md Template
Module Name
Brief description of what this module creates.
Features
- Feature 1
- Feature 2
- Feature 3
Usage
Basic
module "example" {
source = "github.com/org/terraform-aws-module?ref=v1.0.0"
name = "my-app"
environment = "prod"
vpc_config = {
vpc_id = "vpc-12345678"
subnet_ids = ["subnet-1", "subnet-2"]
}
}
Advanced
module "example" {
source = "github.com/org/terraform-aws-module?ref=v1.0.0"
name = "my-app"
environment = "prod"
vpc_config = {
vpc_id = "vpc-12345678"
subnet_ids = ["subnet-1", "subnet-2"]
security_group_ids = ["sg-existing"]
assign_public_ip = false
}
instance_type = "t3.large"
min_instances = 3
max_instances = 10
enable_monitoring = true
ingress_rules = [
{
description = "HTTPS"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["10.0.0.0/8"]
}
]
tags = {
Team = "platform"
CostCenter = "12345"
}
}
Requirements
Name
Version
terraform
>= 1.5.0
aws
>= 5.0.0, < 6.0.0
Providers
Name
Version
aws
>= 5.0.0
Inputs
Name
Description
Type
Default
Required
name
Name prefix for resources
string
n/a
yes
environment
Environment (dev/staging/prod)
string
n/a
yes
vpc_config
VPC configuration
object
n/a
yes
instance_type
EC2 instance type
string
"t3.medium"
no
min_instances
Minimum ASG size
number
2
no
max_instances
Maximum ASG size
number
10
no
Outputs
Name
Description
id
Auto Scaling Group ID
security_group_id
Security Group ID
launch_template_id
Launch Template ID
License
MIT
---
## Лучшие практики
1. **Используй версионирование** — семантическое версионирование для модулей
2. **Валидируй входные переменные** — используй validation blocks
3. **Документируй всё** — описания для variables и outputs
4. **Избегай hardcoded значений** — всё должно быть configurable
5. **Используй for_each вместо count** — лучше управление state
6. **Группируй связанные ресурсы** — логическая организация main.tf
7. **Тестируй модули** — используй terratest или terraform test
8. **Следуй naming conventions** — консистентное именование ресурсов