Lab: สร้าง Reusable Web Server Module

📖 text • 30 นาที

Lab: สร้าง Reusable Web Server Module

สิ่งที่ต้องมี: Terraform + AWS Credentials ตั้งค่าแล้ว

เป้าหมาย

  1. สร้าง module สำหรับ web server (EC2 + Security Group)
  2. ใช้ module สร้าง server 2 ตัวด้วย config ต่างกัน
  3. ใช้ module จาก Registry ร่วมด้วย

Step 1: สร้างโครงสร้างโปรเจค

mkdir terraform-module-lab && cd terraform-module-lab
mkdir -p modules/web-server
terraform-module-lab/
├── main.tf              # Root module
├── variables.tf
├── outputs.tf
└── modules/
    └── web-server/      # Child module
        ├── main.tf
        ├── variables.tf
        └── outputs.tf

Step 2: เขียน Web Server Module

modules/web-server/variables.tf

variable "name" {
  description = "Name prefix for resources"
  type        = string
}

variable "instance_type" {
  description = "EC2 instance type"
  type        = string
  default     = "t2.micro"
}

variable "ami_id" {
  description = "AMI ID"
  type        = string
}

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

variable "http_port" {
  description = "Port for HTTP traffic"
  type        = number
  default     = 80
}

variable "user_data" {
  description = "User data script"
  type        = string
  default     = ""
}

variable "tags" {
  description = "Additional tags"
  type        = map(string)
  default     = {}
}

modules/web-server/main.tf

resource "aws_security_group" "this" {
  name        = "${var.name}-sg"
  description = "Security group for ${var.name}"
  vpc_id      = var.vpc_id

  ingress {
    description = "HTTP"
    from_port   = var.http_port
    to_port     = var.http_port
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    description = "SSH"
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = merge(var.tags, {
    Name = "${var.name}-sg"
  })
}

resource "aws_instance" "this" {
  ami                    = var.ami_id
  instance_type          = var.instance_type
  vpc_security_group_ids = [aws_security_group.this.id]
  user_data              = var.user_data

  tags = merge(var.tags, {
    Name = var.name
  })
}

modules/web-server/outputs.tf

output "instance_id" {
  description = "EC2 Instance ID"
  value       = aws_instance.this.id
}

output "public_ip" {
  description = "Public IP address"
  value       = aws_instance.this.public_ip
}

output "security_group_id" {
  description = "Security Group ID"
  value       = aws_security_group.this.id
}

output "url" {
  description = "Web URL"
  value       = "http://${aws_instance.this.public_ip}:${var.http_port}"
}

Step 3: เขียน Root Module

variables.tf

variable "project" {
  description = "Project name"
  type        = string
  default     = "module-lab"
}

main.tf

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }

  required_version = ">= 1.0"
}

provider "aws" {
  region = "ap-southeast-1"
}

# Data Sources
data "aws_ami" "amazon_linux" {
  most_recent = true
  owners      = ["amazon"]

  filter {
    name   = "name"
    values = ["al2023-ami-2023.*-x86_64"]
  }
}

data "aws_vpc" "default" {
  default = true
}

locals {
  common_tags = {
    Project   = var.project
    ManagedBy = "terraform"
  }
}

# ========== Web Server 1: Main App (port 80) ==========
module "app_server" {
  source = "./modules/web-server"

  name          = "${var.project}-app"
  instance_type = "t2.micro"
  ami_id        = data.aws_ami.amazon_linux.id
  vpc_id        = data.aws_vpc.default.id
  http_port     = 80

  user_data = <<-EOF
    #!/bin/bash
    yum update -y
    yum install -y httpd
    systemctl start httpd
    systemctl enable httpd
    cat > /var/www/html/index.html <<HTML
    <h1>Main App Server</h1>
    <p>Port: 80</p>
    <p>Managed by Terraform Module</p>
    HTML
  EOF

  tags = merge(local.common_tags, {
    Role = "app"
  })
}

# ========== Web Server 2: API Server (port 8080) ==========
module "api_server" {
  source = "./modules/web-server"

  name          = "${var.project}-api"
  instance_type = "t2.micro"
  ami_id        = data.aws_ami.amazon_linux.id
  vpc_id        = data.aws_vpc.default.id
  http_port     = 8080

  user_data = <<-EOF
    #!/bin/bash
    yum update -y
    yum install -y python3
    cat > /home/ec2-user/app.py <<PYTHON
    from http.server import HTTPServer, SimpleHTTPRequestHandler
    import json

    class Handler(SimpleHTTPRequestHandler):
        def do_GET(self):
            self.send_response(200)
            self.send_header('Content-type', 'application/json')
            self.end_headers()
            response = {"status": "ok", "service": "api", "port": 8080}
            self.wfile.write(json.dumps(response).encode())

    HTTPServer(('', 8080), Handler).serve_forever()
    PYTHON
    python3 /home/ec2-user/app.py &
  EOF

  tags = merge(local.common_tags, {
    Role = "api"
  })
}

outputs.tf

output "app_url" {
  description = "Main App URL"
  value       = module.app_server.url
}

output "api_url" {
  description = "API Server URL"
  value       = module.api_server.url
}

output "app_ip" {
  description = "App Server Public IP"
  value       = module.app_server.public_ip
}

output "api_ip" {
  description = "API Server Public IP"
  value       = module.api_server.public_ip
}

Step 4: Init & Plan

terraform init
Initializing modules...
- api_server in modules/web-server
- app_server in modules/web-server

Initializing provider plugins...
- Finding hashicorp/aws versions matching "~> 5.0"...

Terraform has been successfully initialized!

Terraform เห็น 2 modules — ทั้งคู่ใช้ source เดียวกัน

terraform plan
Plan: 4 to add, 0 to change, 0 to destroy.

4 resources = 2 Security Groups + 2 EC2 Instances

Step 5: Apply

terraform apply

พิมพ์ yes

Apply complete! Resources: 4 added, 0 changed, 0 destroyed.

Outputs:

api_ip  = "54.xx.xx.xx"
api_url = "http://54.xx.xx.xx:8080"
app_ip  = "54.yy.yy.yy"
app_url = "http://54.yy.yy.yy:80"

Step 6: ทดสอบ

# ทดสอบ App Server
curl http://<APP_IP>
<h1>Main App Server</h1>
<p>Port: 80</p>
<p>Managed by Terraform Module</p>
# ทดสอบ API Server (รอ 1-2 นาทีให้ user_data ทำงาน)
curl http://<API_IP>:8080
{"status": "ok", "service": "api", "port": 8080}

Step 7: ดู Resource ใน State

terraform state list
data.aws_ami.amazon_linux
data.aws_vpc.default
module.api_server.aws_instance.this
module.api_server.aws_security_group.this
module.app_server.aws_instance.this
module.app_server.aws_security_group.this

สังเกต: resource อยู่ใต้ module.<name> — Terraform จัดกลุ่มให้อัตโนมัติ

Step 8: Cleanup

terraform destroy -auto-approve

cd .. && rm -rf terraform-module-lab

สิ่งที่ได้เรียนรู้

Concept สิ่งที่ฝึก
Module structure variables.tf, main.tf, outputs.tf
Reusability ใช้ module เดียวสร้าง server 2 ตัว
Module input ส่ง variables ให้ module
Module output ดึงค่าจาก module ด้วย module.<name>.<output>
merge() รวม tags จาก root + module
State resource ถูกจัดกลุ่มใต้ module.<name>

สรุปคอร์ส

ยินดีด้วย! คุณเรียนจบ Terraform Fundamentals แล้ว

สิ่งที่ได้เรียนรู้ตลอดคอร์ส:

Module หัวข้อ
1. Introduction IaC concept, Terraform workflow, ติดตั้ง, Lab แรก
2. HCL & Resource Syntax, Providers, Resource/Data Source, Lab EC2+SG
3. State Management State, Remote Backend, State Commands, Lab S3 Backend
4. Variables & Outputs Variables, Outputs, Locals, tfvars, Lab Multi-env
5. Modules Module concept, เขียน Module, Registry, Lab Reusable Module

Next Steps

  • ฝึกสร้าง infrastructure ของตัวเองด้วย Terraform
  • ลองใช้ module จาก Terraform Registry ในโปรเจคจริง
  • ศึกษาเพิ่ม: Terraform Cloud, CI/CD pipeline, advanced patterns