A Terraform module is a set of Terraform configuration files in a single directory. Even a simple configuration consisting of a single directory with one or more .tf files is a module.
.
├── LICENSE
├── README.md
├── main.tf
├── variables.tf
├── outputs.tf
Terraform에서는 module을 terraform configuration 파일들의 집합이라고 정의한다. 위와 같이 tf 파일들로 구성되어 있는 구조를 module이라고 하는데, 지금까지 실습해오던 디렉토리도 다 module로 분류가 된다.
참조: https://www.oreilly.com/library/view/terraform-up-and/9781491977071/ch04.html
file layout 구조를 사용하는 terraform 디렉토리를 예로 들어보자. stage와 prod 환경에서 공통된 리소스들이 있을 수 있다. 매번 중복된 코드를 작성하는 것 보다, 이렇게 module로 따로 빼내서 관리한다면, 코드재사용이 가능하다.
실습은 terraform에서 제공하는 예제로 해보자. Use Registry Modules in Configuration
terraform에서는 registry를 통해 AWS,Azure 등과 같이 자주 쓰이는 module을 정의해 두었다. Terraform Registry page for the VPC module.
이번 실습에서는 registry의 module을 다운받아서 사용할 것이다.
$ git clone https://github.com/hashicorp/learn-terraform-modules-use.git
$ cd learn-terraform-modules-use
$ ls -al
➜ learn-terraform-modules-use git:(main) ls -al
total 64
drwxr-xr-x 11 nowjean staff 352 Nov 12 09:38 .
drwxr-xr-x 11 nowjean staff 352 Nov 12 09:38 ..
drwxr-xr-x 12 nowjean staff 384 Nov 12 09:38 .git
-rw-r--r-- 1 nowjean staff 716 Nov 12 09:38 .gitignore
-rw-r--r-- 1 nowjean staff 1107 Nov 12 09:38 .terraform.lock.hcl
-rw-r--r-- 1 nowjean staff 47 Nov 12 09:38 LICENSE
-rw-r--r-- 1 nowjean staff 372 Nov 12 09:38 README.md
-rw-r--r-- 1 nowjean staff 861 Nov 12 09:38 main.tf
-rw-r--r-- 1 nowjean staff 267 Nov 12 09:38 outputs.tf
-rw-r--r-- 1 nowjean staff 341 Nov 12 09:38 terraform.tf
-rw-r--r-- 1 nowjean staff 1002 Nov 12 09:38 variables.tf
provider "aws" {
region = "us-west-2"
default_tags {
tags = {
hashicorp-learn = "module-use"
}
}
}
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "3.14.0"
name = var.vpc_name
cidr = var.vpc_cidr
azs = var.vpc_azs
private_subnets = var.vpc_private_subnets
public_subnets = var.vpc_public_subnets
enable_nat_gateway = var.vpc_enable_nat_gateway
tags = var.vpc_tags
}
module "ec2_instances" {
source = "terraform-aws-modules/ec2-instance/aws"
version = "3.5.0"
count = 2
name = "my-ec2-cluster"
ami = "ami-0c5204531f799e0c6"
instance_type = "t2.micro"
vpc_security_group_ids = [module.vpc.default_security_group_id]
subnet_id = module.vpc.public_subnets[0]
tags = {
Terraform = "true"
Environment = "dev"
}
}
main.tf 파을을 보면 module이 추가된 것을 볼 수 있다. source 위치를 로컬에 있는 경로로 지정할 수 도 있고, 예제처럼 terraform registry 경로를 지정하여 다운받을 수 도 있다.
soruce 경로 지정방법 참고: Module Sources
moduled을 수정, 적용하거나,source 경로를 변경한 경우 terraform init 명령을 통해 초기화를 시켜줘야한다.
$ terraform init
Initializing modules...
Downloading registry.terraform.io/terraform-aws-modules/ec2-instance/aws 3.5.0 for ec2_instances...
- ec2_instances in .terraform/modules/ec2_instances
Downloading registry.terraform.io/terraform-aws-modules/vpc/aws 3.14.0 for vpc...
- vpc in .terraform/modules/vpc
Initializing the backend...
Initializing provider plugins...
- Reusing previous version of hashicorp/aws from the dependency lock file
- Installing hashicorp/aws v4.4.0...
- Installed hashicorp/aws v4.4.0 (signed by HashiCorp)
Terraform has been successfully initialized!
You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.
If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
➜ learn-terraform-modules-use git:(main) ls -al
total 64
drwxr-xr-x 12 nowjean staff 384 Nov 12 09:44 .
drwxr-xr-x 11 nowjean staff 352 Nov 12 09:38 ..
drwxr-xr-x 12 nowjean staff 384 Nov 12 09:44 .git
-rw-r--r-- 1 nowjean staff 716 Nov 12 09:38 .gitignore
drwxr-xr-x 4 nowjean staff 128 Nov 12 09:44 .terraform
-rw-r--r-- 1 nowjean staff 1107 Nov 12 09:38 .terraform.lock.hcl
-rw-r--r-- 1 nowjean staff 47 Nov 12 09:38 LICENSE
-rw-r--r-- 1 nowjean staff 372 Nov 12 09:38 README.md
-rw-r--r-- 1 nowjean staff 861 Nov 12 09:38 main.tf
-rw-r--r-- 1 nowjean staff 267 Nov 12 09:38 outputs.tf
-rw-r--r-- 1 nowjean staff 341 Nov 12 09:38 terraform.tf
-rw-r--r-- 1 nowjean staff 1002 Nov 12 09:38 variables.tf
➜ learn-terraform-modules-use git:(main) cd .terraform
➜ .terraform git:(main) ls
modules providers
➜ .terraform git:(main) cd modules
➜ modules git:(main) ls
ec2_instances modules.json vpc
terraform init을 하면, registry에서 module을 다운받고, .terrafrom 디렉토리 밑에 module들이 생성된다.
다운받은 module과 main.tf, variable.tf가 매핑되어 ec2, vpc를 생성하게 된다.
$ terraform plan
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# module.ec2_instances[0].aws_instance.this[0] will be created
+ resource "aws_instance" "this" {
+ ami = "ami-0c5204531f799e0c6"
+ arn = (known after apply)
+ associate_public_ip_address = (known after apply)
+ availability_zone = (known after apply)
+ cpu_core_count = (known after apply)
+ cpu_threads_per_core = (known after apply)
+ disable_api_termination = (known after apply)
+ ebs_optimized = (known after apply)
+ get_password_data = false
+ host_id = (known after apply)
+ id = (known after apply)
+ instance_initiated_shutdown_behavior = (known after apply)
+ instance_state = (known after apply)
+ instance_type = "t2.micro"
+ ipv6_address_count = (known after apply)
+ ipv6_addresses = (known after apply)
+ key_name = (known after apply)
+ monitoring = false
+ outpost_arn = (known after apply)
+ password_data = (known after apply)
+ placement_group = (known after apply)
+ placement_partition_number = (known after apply)
+ primary_network_interface_id = (known after apply)
+ private_dns = (known after apply)
+ private_ip = (known after apply)
+ public_dns = (known after apply)
+ public_ip = (known after apply)
+ secondary_private_ips = (known after apply)
+ security_groups = (known after apply)
+ source_dest_check = true
+ subnet_id = (known after apply)
+ tags = {
+ "Environment" = "dev"
+ "Name" = "my-ec2-cluster"
+ "Terraform" = "true"
}
+ tags_all = {
+ "Environment" = "dev"
+ "Name" = "my-ec2-cluster"
+ "Terraform" = "true"
+ "hashicorp-learn" = "module-use"
}
+ tenancy = (known after apply)
+ user_data = (known after apply)
+ user_data_base64 = (known after apply)
+ volume_tags = {
+ "Name" = "my-ec2-cluster"
}
+ vpc_security_group_ids = (known after apply)
+ capacity_reservation_specification {
+ capacity_reservation_preference = (known after apply)
+ capacity_reservation_target {
+ capacity_reservation_id = (known after apply)
}
}
+ credit_specification {}
+ ebs_block_device {
+ delete_on_termination = (known after apply)
+ device_name = (known after apply)
+ encrypted = (known after apply)
+ iops = (known after apply)
+ kms_key_id = (known after apply)
+ snapshot_id = (known after apply)
+ tags = (known after apply)
+ throughput = (known after apply)
+ volume_id = (known after apply)
+ volume_size = (known after apply)
+ volume_type = (known after apply)
}
+ enclave_options {
+ enabled = (known after apply)
}
+ ephemeral_block_device {
+ device_name = (known after apply)
+ no_device = (known after apply)
+ virtual_name = (known after apply)
}
+ metadata_options {
+ http_endpoint = "enabled"
+ http_put_response_hop_limit = 1
+ http_tokens = "optional"
+ instance_metadata_tags = "disabled"
}
+ network_interface {
+ delete_on_termination = (known after apply)
+ device_index = (known after apply)
+ network_interface_id = (known after apply)
}
+ root_block_device {
+ delete_on_termination = (known after apply)
+ device_name = (known after apply)
+ encrypted = (known after apply)
+ iops = (known after apply)
+ kms_key_id = (known after apply)
+ tags = (known after apply)
+ throughput = (known after apply)
+ volume_id = (known after apply)
+ volume_size = (known after apply)
+ volume_type = (known after apply)
}
+ timeouts {}
}
# module.ec2_instances[1].aws_instance.this[0] will be created
+ resource "aws_instance" "this" {
+ ami = "ami-0c5204531f799e0c6"
+ arn = (known after apply)
+ associate_public_ip_address = (known after apply)
+ availability_zone = (known after apply)
+ cpu_core_count = (known after apply)
+ cpu_threads_per_core = (known after apply)
+ disable_api_termination = (known after apply)
+ ebs_optimized = (known after apply)
+ get_password_data = false
+ host_id = (known after apply)
+ id = (known after apply)
+ instance_initiated_shutdown_behavior = (known after apply)
+ instance_state = (known after apply)
+ instance_type = "t2.micro"
+ ipv6_address_count = (known after apply)
+ ipv6_addresses = (known after apply)
+ key_name = (known after apply)
+ monitoring = false
+ outpost_arn = (known after apply)
+ password_data = (known after apply)
+ placement_group = (known after apply)
+ placement_partition_number = (known after apply)
+ primary_network_interface_id = (known after apply)
+ private_dns = (known after apply)
+ private_ip = (known after apply)
+ public_dns = (known after apply)
+ public_ip = (known after apply)
+ secondary_private_ips = (known after apply)
+ security_groups = (known after apply)
+ source_dest_check = true
+ subnet_id = (known after apply)
+ tags = {
+ "Environment" = "dev"
+ "Name" = "my-ec2-cluster"
+ "Terraform" = "true"
}
+ tags_all = {
+ "Environment" = "dev"
+ "Name" = "my-ec2-cluster"
+ "Terraform" = "true"
+ "hashicorp-learn" = "module-use"
}
+ tenancy = (known after apply)
+ user_data = (known after apply)
+ user_data_base64 = (known after apply)
+ volume_tags = {
+ "Name" = "my-ec2-cluster"
}
+ vpc_security_group_ids = (known after apply)
+ capacity_reservation_specification {
+ capacity_reservation_preference = (known after apply)
+ capacity_reservation_target {
+ capacity_reservation_id = (known after apply)
}
}
+ credit_specification {}
+ ebs_block_device {
+ delete_on_termination = (known after apply)
+ device_name = (known after apply)
+ encrypted = (known after apply)
+ iops = (known after apply)
+ kms_key_id = (known after apply)
+ snapshot_id = (known after apply)
+ tags = (known after apply)
+ throughput = (known after apply)
+ volume_id = (known after apply)
+ volume_size = (known after apply)
+ volume_type = (known after apply)
}
+ enclave_options {
+ enabled = (known after apply)
}
+ ephemeral_block_device {
+ device_name = (known after apply)
+ no_device = (known after apply)
+ virtual_name = (known after apply)
}
+ metadata_options {
+ http_endpoint = "enabled"
+ http_put_response_hop_limit = 1
+ http_tokens = "optional"
+ instance_metadata_tags = "disabled"
}
+ network_interface {
+ delete_on_termination = (known after apply)
+ device_index = (known after apply)
+ network_interface_id = (known after apply)
}
+ root_block_device {
+ delete_on_termination = (known after apply)
+ device_name = (known after apply)
+ encrypted = (known after apply)
+ iops = (known after apply)
+ kms_key_id = (known after apply)
+ tags = (known after apply)
+ throughput = (known after apply)
+ volume_id = (known after apply)
+ volume_size = (known after apply)
+ volume_type = (known after apply)
}
+ timeouts {}
}
# module.vpc.aws_internet_gateway.this[0] will be created
+ resource "aws_internet_gateway" "this" {
+ arn = (known after apply)
+ id = (known after apply)
+ owner_id = (known after apply)
+ tags = {
+ "Environment" = "dev"
+ "Name" = "example-vpc"
+ "Terraform" = "true"
}
+ tags_all = {
+ "Environment" = "dev"
+ "Name" = "example-vpc"
+ "Terraform" = "true"
+ "hashicorp-learn" = "module-use"
}
+ vpc_id = (known after apply)
}
# module.vpc.aws_route.public_internet_gateway[0] will be created
+ resource "aws_route" "public_internet_gateway" {
+ destination_cidr_block = "0.0.0.0/0"
+ gateway_id = (known after apply)
+ id = (known after apply)
+ instance_id = (known after apply)
+ instance_owner_id = (known after apply)
+ network_interface_id = (known after apply)
+ origin = (known after apply)
+ route_table_id = (known after apply)
+ state = (known after apply)
+ timeouts {
+ create = "5m"
}
}
# module.vpc.aws_route_table.private[0] will be created
+ resource "aws_route_table" "private" {
+ arn = (known after apply)
+ id = (known after apply)
+ owner_id = (known after apply)
+ propagating_vgws = (known after apply)
+ route = (known after apply)
+ tags = {
+ "Environment" = "dev"
+ "Name" = "example-vpc-private-us-west-2a"
+ "Terraform" = "true"
}
+ tags_all = {
+ "Environment" = "dev"
+ "Name" = "example-vpc-private-us-west-2a"
+ "Terraform" = "true"
+ "hashicorp-learn" = "module-use"
}
+ vpc_id = (known after apply)
}
# module.vpc.aws_route_table.private[1] will be created
+ resource "aws_route_table" "private" {
+ arn = (known after apply)
+ id = (known after apply)
+ owner_id = (known after apply)
+ propagating_vgws = (known after apply)
+ route = (known after apply)
+ tags = {
+ "Environment" = "dev"
+ "Name" = "example-vpc-private-us-west-2b"
+ "Terraform" = "true"
}
+ tags_all = {
+ "Environment" = "dev"
+ "Name" = "example-vpc-private-us-west-2b"
+ "Terraform" = "true"
+ "hashicorp-learn" = "module-use"
}
+ vpc_id = (known after apply)
}
# module.vpc.aws_route_table.public[0] will be created
+ resource "aws_route_table" "public" {
+ arn = (known after apply)
+ id = (known after apply)
+ owner_id = (known after apply)
+ propagating_vgws = (known after apply)
+ route = (known after apply)
+ tags = {
+ "Environment" = "dev"
+ "Name" = "example-vpc-public"
+ "Terraform" = "true"
}
+ tags_all = {
+ "Environment" = "dev"
+ "Name" = "example-vpc-public"
+ "Terraform" = "true"
+ "hashicorp-learn" = "module-use"
}
+ vpc_id = (known after apply)
}
# module.vpc.aws_route_table_association.private[0] will be created
+ resource "aws_route_table_association" "private" {
+ id = (known after apply)
+ route_table_id = (known after apply)
+ subnet_id = (known after apply)
}
# module.vpc.aws_route_table_association.private[1] will be created
+ resource "aws_route_table_association" "private" {
+ id = (known after apply)
+ route_table_id = (known after apply)
+ subnet_id = (known after apply)
}
# module.vpc.aws_route_table_association.public[0] will be created
+ resource "aws_route_table_association" "public" {
+ id = (known after apply)
+ route_table_id = (known after apply)
+ subnet_id = (known after apply)
}
# module.vpc.aws_route_table_association.public[1] will be created
+ resource "aws_route_table_association" "public" {
+ id = (known after apply)
+ route_table_id = (known after apply)
+ subnet_id = (known after apply)
}
# module.vpc.aws_subnet.private[0] will be created
+ resource "aws_subnet" "private" {
+ arn = (known after apply)
+ assign_ipv6_address_on_creation = false
+ availability_zone = "us-west-2a"
+ availability_zone_id = (known after apply)
+ cidr_block = "10.0.1.0/24"
+ enable_dns64 = false
+ enable_resource_name_dns_a_record_on_launch = false
+ enable_resource_name_dns_aaaa_record_on_launch = false
+ id = (known after apply)
+ ipv6_cidr_block_association_id = (known after apply)
+ ipv6_native = false
+ map_public_ip_on_launch = false
+ owner_id = (known after apply)
+ private_dns_hostname_type_on_launch = (known after apply)
+ tags = {
+ "Environment" = "dev"
+ "Name" = "example-vpc-private-us-west-2a"
+ "Terraform" = "true"
}
+ tags_all = {
+ "Environment" = "dev"
+ "Name" = "example-vpc-private-us-west-2a"
+ "Terraform" = "true"
+ "hashicorp-learn" = "module-use"
}
+ vpc_id = (known after apply)
}
# module.vpc.aws_subnet.private[1] will be created
+ resource "aws_subnet" "private" {
+ arn = (known after apply)
+ assign_ipv6_address_on_creation = false
+ availability_zone = "us-west-2b"
+ availability_zone_id = (known after apply)
+ cidr_block = "10.0.2.0/24"
+ enable_dns64 = false
+ enable_resource_name_dns_a_record_on_launch = false
+ enable_resource_name_dns_aaaa_record_on_launch = false
+ id = (known after apply)
+ ipv6_cidr_block_association_id = (known after apply)
+ ipv6_native = false
+ map_public_ip_on_launch = false
+ owner_id = (known after apply)
+ private_dns_hostname_type_on_launch = (known after apply)
+ tags = {
+ "Environment" = "dev"
+ "Name" = "example-vpc-private-us-west-2b"
+ "Terraform" = "true"
}
+ tags_all = {
+ "Environment" = "dev"
+ "Name" = "example-vpc-private-us-west-2b"
+ "Terraform" = "true"
+ "hashicorp-learn" = "module-use"
}
+ vpc_id = (known after apply)
}
# module.vpc.aws_subnet.public[0] will be created
+ resource "aws_subnet" "public" {
+ arn = (known after apply)
+ assign_ipv6_address_on_creation = false
+ availability_zone = "us-west-2a"
+ availability_zone_id = (known after apply)
+ cidr_block = "10.0.101.0/24"
+ enable_dns64 = false
+ enable_resource_name_dns_a_record_on_launch = false
+ enable_resource_name_dns_aaaa_record_on_launch = false
+ id = (known after apply)
+ ipv6_cidr_block_association_id = (known after apply)
+ ipv6_native = false
+ map_public_ip_on_launch = true
+ owner_id = (known after apply)
+ private_dns_hostname_type_on_launch = (known after apply)
+ tags = {
+ "Environment" = "dev"
+ "Name" = "example-vpc-public-us-west-2a"
+ "Terraform" = "true"
}
+ tags_all = {
+ "Environment" = "dev"
+ "Name" = "example-vpc-public-us-west-2a"
+ "Terraform" = "true"
+ "hashicorp-learn" = "module-use"
}
+ vpc_id = (known after apply)
}
# module.vpc.aws_subnet.public[1] will be created
+ resource "aws_subnet" "public" {
+ arn = (known after apply)
+ assign_ipv6_address_on_creation = false
+ availability_zone = "us-west-2b"
+ availability_zone_id = (known after apply)
+ cidr_block = "10.0.102.0/24"
+ enable_dns64 = false
+ enable_resource_name_dns_a_record_on_launch = false
+ enable_resource_name_dns_aaaa_record_on_launch = false
+ id = (known after apply)
+ ipv6_cidr_block_association_id = (known after apply)
+ ipv6_native = false
+ map_public_ip_on_launch = true
+ owner_id = (known after apply)
+ private_dns_hostname_type_on_launch = (known after apply)
+ tags = {
+ "Environment" = "dev"
+ "Name" = "example-vpc-public-us-west-2b"
+ "Terraform" = "true"
}
+ tags_all = {
+ "Environment" = "dev"
+ "Name" = "example-vpc-public-us-west-2b"
+ "Terraform" = "true"
+ "hashicorp-learn" = "module-use"
}
+ vpc_id = (known after apply)
}
# module.vpc.aws_vpc.this[0] will be created
+ resource "aws_vpc" "this" {
+ arn = (known after apply)
+ assign_generated_ipv6_cidr_block = false
+ cidr_block = "10.0.0.0/16"
+ default_network_acl_id = (known after apply)
+ default_route_table_id = (known after apply)
+ default_security_group_id = (known after apply)
+ dhcp_options_id = (known after apply)
+ enable_classiclink = (known after apply)
+ enable_classiclink_dns_support = (known after apply)
+ enable_dns_hostnames = false
+ enable_dns_support = true
+ id = (known after apply)
+ instance_tenancy = "default"
+ ipv6_association_id = (known after apply)
+ ipv6_cidr_block = (known after apply)
+ ipv6_cidr_block_network_border_group = (known after apply)
+ main_route_table_id = (known after apply)
+ owner_id = (known after apply)
+ tags = {
+ "Environment" = "dev"
+ "Name" = "example-vpc"
+ "Terraform" = "true"
}
+ tags_all = {
+ "Environment" = "dev"
+ "Name" = "example-vpc"
+ "Terraform" = "true"
+ "hashicorp-learn" = "module-use"
}
}
Plan: 16 to add, 0 to change, 0 to destroy.
Changes to Outputs:
+ ec2_instance_public_ips = [
+ (known after apply),
+ (known after apply),
]
+ vpc_public_subnets = [
+ (known after apply),
+ (known after apply),
]
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now.
실습에서는 terraform registry의 module을 다운받아서 테스트를 해보았는데, 직접 moduled을 작성해서 stage, prod 환경에 따라 varible 값을 변경하여 적용할 수 도 있고, 팀원들과 공유하여 사용할 때는 module을 github과 같은 곳에 올려 공유할 수 도 있다.