136 lines
5.0 KiB
Markdown
136 lines
5.0 KiB
Markdown
# 5) Build & Reuse a Simple Module (25 min)
|
||
|
||
In this section, you’ll turn repeated EC2 logic into a **reusable Terraform module**.
|
||
Modules make your code **DRY** (Don’t Repeat Yourself), easier to test, and simpler to use across teams and environments.
|
||
|
||
## What is a module?
|
||
A **module** is just a folder that contains Terraform configuration files (`.tf`).
|
||
Your **root module** (the folder where you run the CLI) can **call** other modules from:
|
||
- A local path (`./modules/ec2-instance`)
|
||
- A Git repo (`git::https://...`), or
|
||
- A registry (`registry.terraform.io`)
|
||
|
||
Here we’ll use a **local** module.
|
||
|
||
## Step 1: Create the project & module folder
|
||
|
||
```bash
|
||
mkdir -p ~/terraform-modules-lab/modules/ec2-instance
|
||
cd ~/terraform-modules-lab
|
||
```
|
||
- Creates a workspace with a nested folder `modules/ec2-instance` that will contain reusable EC2 code.
|
||
|
||
Your tree will look like:
|
||
```
|
||
terraform-modules-lab/
|
||
main.tf # root (calls the module)
|
||
modules/
|
||
ec2-instance/
|
||
variables.tf
|
||
main.tf # module implementation
|
||
outputs.tf
|
||
```
|
||
|
||
## Step 2: Define module inputs (variables)
|
||
|
||
Create **modules/ec2-instance/variables.tf**:
|
||
```hcl
|
||
variable "instance_type" { description = "Type of EC2 instance"; default = "t2.micro" }
|
||
variable "instance_name" { description = "Tag name for instance" }
|
||
variable "instance_count" { description = "Number of EC2"; default = 1 }
|
||
```
|
||
- These are **inputs** the module expects.
|
||
- `default` makes inputs optional (callers can override).
|
||
- You can add stronger typing & validation for safety:
|
||
```hcl
|
||
variable "instance_type" {
|
||
type = string
|
||
description = "EC2 size"
|
||
default = "t2.micro"
|
||
validation {
|
||
condition = can(regex("^t[23]\.", var.instance_type))
|
||
error_message = "Use a t2.* or t3.* instance for this lab."
|
||
}
|
||
}
|
||
```
|
||
|
||
## Step 3: Implement the module logic
|
||
|
||
Create **modules/ec2-instance/main.tf**:
|
||
```hcl
|
||
resource "aws_instance" "this" {
|
||
count = var.instance_count
|
||
ami = "ami-0e6329e222e662a52"
|
||
instance_type = var.instance_type
|
||
tags = { Name = "${var.instance_name}-${count.index}" }
|
||
}
|
||
```
|
||
- Uses `count` to create **N instances** with a single block.
|
||
- `tags.Name` includes the `count.index` suffix so each instance has a unique name (e.g., `App-Server-0`, `App-Server-1`).
|
||
|
||
> **Note on AMIs:** AMI IDs are **region-specific**. We use an Amazon Linux 2 AMI for **ap-south-1 (Mumbai)**. If you switch regions, update this AMI or fetch it dynamically (e.g., with a data source).
|
||
|
||
## Step 4: Expose useful outputs
|
||
|
||
Create **modules/ec2-instance/outputs.tf**:
|
||
```hcl
|
||
output "instance_ids" { value = [for i in aws_instance.this : i.id] }
|
||
output "public_ips" { value = [for i in aws_instance.this : i.public_ip] }
|
||
```
|
||
- Makes it easy for callers to **consume** important info (IDs, IPs).
|
||
- The `for` expression collects values from the resource instances created via `count`.
|
||
|
||
## Step 5: Call the module from the root
|
||
|
||
Create the **root** `main.tf` at `~/terraform-modules-lab/main.tf`:
|
||
```hcl
|
||
provider "aws" { region = "ap-south-1" }
|
||
|
||
module "ec2_instance" {
|
||
source = "./modules/ec2-instance"
|
||
instance_type = "t2.micro"
|
||
instance_name = "App-Server"
|
||
instance_count = 2
|
||
}
|
||
```
|
||
- `source` points to the **local path** of your module folder.
|
||
- Inputs (`instance_type`, `instance_name`, `instance_count`) are set by the **caller** (the root module).
|
||
- You can define **multiple** module blocks to create different groups of instances, or use `.tfvars` files for environments.
|
||
|
||
## Step 6: Initialize and apply
|
||
|
||
```bash
|
||
terraform init
|
||
terraform apply -auto-approve
|
||
```
|
||
- `terraform init` downloads providers and **fetches module sources** (local/Git/registry).
|
||
- `terraform apply` creates **2 EC2 instances** named `App-Server-0` and `App-Server-1` in **ap-south-1**.
|
||
|
||
After the apply, view outputs:
|
||
```bash
|
||
terraform output
|
||
```
|
||
You should see lists for `instance_ids` and `public_ips`.
|
||
|
||
## How to reuse this module elsewhere
|
||
- Copy the `modules/ec2-instance` folder into any Terraform project and call it with `source = "./modules/ec2-instance"`.
|
||
- Or publish the module to a **Git repo** and reference with `source = "git::https://github.com/yourorg/yourrepo//modules/ec2-instance?ref=v1.0.0"`.
|
||
- Standardize inputs/outputs so teams can use it without reading internals.
|
||
|
||
## Best practices
|
||
- **Version control your modules** (Git tags) to avoid breaking changes.
|
||
- Add **README.md** inside the module with usage examples and input/output docs.
|
||
- Prefer **types & validation** for variables.
|
||
- Keep AMIs **parametrized** or discover them via a `data "aws_ami"` query to avoid hard-coding.
|
||
- If multiple teams use the same module, consider a **private Terraform registry** or a Git monorepo with clear versioning.
|
||
|
||
## Cleanup
|
||
When done (to avoid charges):
|
||
```bash
|
||
terraform destroy -auto-approve
|
||
```
|
||
|
||
## Summary
|
||
You built a **reusable EC2 module**, exposed clean **inputs/outputs**, and consumed it from the **root**.
|
||
This pattern scales to VPCs, RDS, ALBs, and more—compose modules like building blocks to create reliable infrastructure at speed.
|