Skip to main content

Module Inputs / Outputs

Module = function — input variables เข้า, output values ออก — ออกแบบ interface ให้ดี = module ที่ reusable

Inputs (Variables)

Variables ใน module = input parameters

Pattern: Required vs Optional

modules/web-app/variables.tf
# Required (no default)
variable "name" {
type = string
description = "Resource name prefix"
}

variable "vpc_id" {
type = string
description = "VPC ID"
}

# Optional (has default)
variable "instance_type" {
type = string
description = "EC2 instance type"
default = "t3.micro"
}

variable "tags" {
type = map(string)
description = "Tags to apply"
default = {}
}

Pattern: Object Variables

ใช้ object สำหรับ related parameters:

variable "asg_config" {
type = object({
min_size = number
max_size = number
desired_capacity = number
instance_type = string
})

default = {
min_size = 1
max_size = 3
desired_capacity = 1
instance_type = "t3.micro"
}
}

ใช้:

module "web" {
source = "./modules/web-app"

asg_config = {
min_size = 2
max_size = 10
desired_capacity = 2
instance_type = "t3.large"
}
}

Pattern: Optional Object Attributes (Terraform 1.3+)

variable "logging" {
type = object({
enabled = bool
retention_days = optional(number, 30)
bucket_name = optional(string)
})

default = {
enabled = true
}
}

User ส่งแค่ enabled ก็พอ — retention_days ใช้ default

Pattern: Validation

variable "environment" {
type = string

validation {
condition = contains(["dev", "staging", "prod"], var.environment)
error_message = "Environment must be: dev, staging, or prod."
}
}

variable "instance_count" {
type = number
default = 1

validation {
condition = var.instance_count >= 1 && var.instance_count <= 100
error_message = "Instance count must be 1-100."
}
}

Outputs

Output = ค่าที่ module return ให้ caller ใช้

modules/web-app/outputs.tf
output "alb_dns_name" {
value = aws_alb.main.dns_name
description = "ALB DNS name for accessing the application"
}

output "alb_arn" {
value = aws_alb.main.arn
description = "ALB ARN (for IAM policies)"
}

output "asg_name" {
value = aws_autoscaling_group.main.name
description = "Auto Scaling Group name"
}

output "security_group_id" {
value = aws_security_group.web.id
description = "Security group attached to instances"
}

Pattern: Output ทุก Resource ที่อาจ Reference

# ✅ Output ครอบคลุม
output "instance_ids" { value = aws_instance.web[*].id }
output "instance_arns" { value = aws_instance.web[*].arn }
output "private_ips" { value = aws_instance.web[*].private_ip }
output "security_group_id" { value = aws_security_group.web.id }

# ❌ Output น้อยเกิน — caller จะมาขอเพิ่มทีหลัง
output "instance_ids" { value = aws_instance.web[*].id }

Pattern: Computed Outputs

output "url" {
value = "https://${aws_alb.main.dns_name}"
description = "Full URL of the application"
}

output "connection_string" {
value = "postgres://${var.username}:${random_password.db.result}@${aws_db_instance.main.endpoint}/${var.db_name}"
sensitive = true
}

Pattern: Object Outputs

Group related outputs:

output "alb" {
value = {
arn = aws_alb.main.arn
dns_name = aws_alb.main.dns_name
zone_id = aws_alb.main.zone_id
listeners = aws_alb_listener.main[*].arn
}
description = "ALB details"
}

ใช้:

module "web" {
source = "./modules/web-app"
# ...
}

resource "aws_route53_record" "app" {
zone_id = data.aws_route53_zone.main.zone_id
name = "app.example.com"
type = "A"

alias {
name = module.web.alb.dns_name
zone_id = module.web.alb.zone_id
evaluate_target_health = true
}
}

Pattern: Sensitive Outputs

output "db_password" {
value = random_password.db.result
sensitive = true
}

→ ค่าไม่ปรากฏใน plan/apply log

Pattern: Conditional Outputs

output "load_balancer_dns" {
value = var.create_lb ? aws_alb.main[0].dns_name : null
description = "ALB DNS (null if not created)"
}

ตัวอย่าง: Well-Designed Module Interface

modules/eks-cluster/variables.tf
variable "cluster_name" {
type = string
description = "Name of the EKS cluster"

validation {
condition = can(regex("^[a-zA-Z0-9-]+$", var.cluster_name))
error_message = "Cluster name must be alphanumeric with hyphens."
}
}

variable "kubernetes_version" {
type = string
description = "Kubernetes version (e.g., 1.30)"
default = "1.30"
}

variable "vpc_config" {
type = object({
vpc_id = string
subnet_ids = list(string)
})
description = "VPC configuration for cluster"
}

variable "node_groups" {
type = map(object({
instance_types = list(string)
desired_size = number
min_size = number
max_size = number
capacity_type = optional(string, "ON_DEMAND")
disk_size = optional(number, 20)
labels = optional(map(string), {})
taints = optional(list(object({
key = string
value = string
effect = string
})), [])
}))

description = "Map of node group configurations"
default = {}
}

variable "enable_irsa" {
type = bool
description = "Enable IAM Roles for Service Accounts"
default = true
}

variable "tags" {
type = map(string)
description = "Tags to apply to all resources"
default = {}
}
modules/eks-cluster/outputs.tf
output "cluster_id" {
value = aws_eks_cluster.main.id
description = "Cluster ID"
}

output "cluster_endpoint" {
value = aws_eks_cluster.main.endpoint
description = "Cluster API endpoint"
}

output "cluster_ca_certificate" {
value = aws_eks_cluster.main.certificate_authority[0].data
description = "Cluster CA certificate (base64)"
sensitive = true
}

output "cluster_security_group_id" {
value = aws_eks_cluster.main.vpc_config[0].cluster_security_group_id
description = "Cluster security group"
}

output "node_groups" {
value = {
for k, v in aws_eks_node_group.this : k => {
arn = v.arn
status = v.status
capacity = v.scaling_config[0]
}
}
description = "Node groups summary"
}

output "oidc_provider_arn" {
value = var.enable_irsa ? aws_iam_openid_connect_provider.this[0].arn : null
description = "OIDC provider ARN for IRSA"
}

output "kubeconfig_command" {
value = "aws eks update-kubeconfig --name ${aws_eks_cluster.main.name} --region ${data.aws_region.current.name}"
description = "Run this command to update kubeconfig"
}

Best Practices

Variables

✅ DO:
- Description ทุกตัว
- Validation rules ที่ make sense
- Default ที่ sensible สำหรับ optional
- Type ที่ specific (อย่าใช้ any)
- Group related vars เป็น object

❌ DON'T:
- ห้ามมี variable ที่ไม่ใช้
- ห้ามใช้ name สั้นเกิน (`x`, `count`, `name`)
- ห้าม default ที่ require change ใน production

Outputs

✅ DO:
- Output ทุก attribute ที่ caller อาจใช้
- Description ทุก output
- Sensitive marker สำหรับ secrets
- Computed values (URL, connection string)
- Object outputs สำหรับ related fields

❌ DON'T:
- ห้าม output ค่าที่หา reference ตรงๆ ได้ง่าย
- ห้าม output ทุก attribute (เลือกที่ค่ามาก)

ตัวอย่าง: API Design Comparison

❌ Bad Interface

module "thing" {
source = "./modules/thing"

x = "abc"
y = 5
z = true
config = "{...}" # JSON string!
}

✅ Good Interface

module "thing" {
source = "./modules/thing"

name = "my-app"
instance_count = 5
enable_logging = true

database_config = {
engine = "postgres"
instance_class = "db.t3.micro"
backup_retention = 7
}

tags = {
Environment = "production"
}
}

สรุป

  • Inputs = variable block — required, optional, validated
  • Outputs = output block — descriptive, sensitive-aware, computed
  • ใช้ object types สำหรับ related parameters
  • Optional attributes (1.3+) ลด boilerplate
  • Document ทุก variable + output
  • Sensible defaults ให้ optional vars
  • Output ครอบคลุม ทุก attribute ที่ caller อาจใช้

ต่อไป → Module Best Practices