하나의 Resource 또는 Module 에서 여러 인프라 리소스를 만들어내고 싶을 때 사용하는 Meta-Argument 이다.
# Terraform tree 구조
.
├── _variables_
│ ├── common-test.yaml
│ └── iam_info.yaml
├── main.tf
├── modules
│ └── iam
│ ├── iam.tf
│ └── variables.tf
├── provider.tf
└── variables.tf
# modules/iam/iam.tf
resource "aws_iam_user" "iam" {
count = length(var.iam_info.user_list)
name = var.iam_info.user_list[count.index]
path = "/"
}
위 코드에서는 count 를 사용하여 아래 yaml 파일에 적용된 {iam_info.user_list} 의 길이를 받아와서 iam 사용자를 생성하게 된다.
# _variables_/dev/iam_info.yaml
user_list:
- Aiden
- Jay
- John
# terraform console 을 통한 값 확인
% terraform console
> local.iam_info
{
"user_list" = [
"Aiden",
"Jay",
"John",
]
}
> local.iam_info.user_list
[
"Aiden",
"Jay",
"John",
]
# terraform plan 을 통한 값 확인
% 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.iam.aws_iam_user.iam[0] will be created
+ resource "aws_iam_user" "iam" {
+ arn = (known after apply)
+ force_destroy = false
+ id = (known after apply)
+ name = "Aiden"
+ path = "/"
+ tags_all = (known after apply)
+ unique_id = (known after apply)
}
# module.iam.aws_iam_user.iam[1] will be created
+ resource "aws_iam_user" "iam" {
+ arn = (known after apply)
+ force_destroy = false
+ id = (known after apply)
+ name = "Jay"
+ path = "/"
+ tags_all = (known after apply)
+ unique_id = (known after apply)
}
# module.iam.aws_iam_user.iam[2] will be created
+ resource "aws_iam_user" "iam" {
+ arn = (known after apply)
+ force_destroy = false
+ id = (known after apply)
+ name = "John"
+ path = "/"
+ tags_all = (known after apply)
+ unique_id = (known after apply)
}
count 사용 시 리소스를 인덱스로 식별할 수 있기 때문에 취약하다.
list의 요소 중 중간 요소가 삭제된다면 의도치 않은 변경사항이 발생할 수 있음.
하나의 Resource 혹은 Module에서 여러 인프라 리소스를 만들어내고 싶을 때 사용하는 Meta-Argument이다.
# Terraform tree 구조
.
├── main.tf
├── modules
│ └── vpc
│ ├── subnet.tf
│ ├── variables.tf
│ └── vpc.tf
├── provider.tf
└── variables.tf
# modules/vpc/subnet.tf
variable "pub-subnets" {
type = map(object({
cidr_block = string
availability_zone = string
type = string
}))
default = {
public-a = { cidr_block = "172.20.1.0/24", availability_zone = "ap-northeast-2a", type = "public" }
public-b = { cidr_block = "172.20.2.0/24", availability_zone = "ap-northeast-2b", type = "public" }
public-c = { cidr_block = "172.20.3.0/24", availability_zone = "ap-northeast-2c", type = "public" }
}
}
resource "aws_subnet" "dev-pubilc-subnet" {
for_each = var.pub-subnets
vpc_id = aws_vpc.dev-vpc.id
cidr_block = each.value.cidr_block
availability_zone= each.value.availability_zone
tags = merge(
{
Name = "dev-${each.key}-subnet"
Type = each.value.type
}
)
}
위 코드에서는 for_each 의 map 을 사용하여 퍼블릭 서브넷의 cidr_block, availability_zone, type 을 정의 하였다.
cidr_block = each.value.cidr_block,와 each.value.availability_zone에서는 값이 cidr_block과 availability_zone 의 값이 순서대로 들어갈 것이다.
tags 에서 Name = "dev-${each.key}-subnet" 에는 public-a, public-b, public-c 가 들어갈 것이다.
# terraform plan 을 통한 값 확인
% 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.vpc.aws_subnet.dev-pubilc-subnet["public-a"] will be created
+ resource "aws_subnet" "dev-pubilc-subnet" {
+ arn = (known after apply)
+ assign_ipv6_address_on_creation = false
+ availability_zone = "ap-northeast-2a"
+ availability_zone_id = (known after apply)
+ cidr_block = "172.20.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 = {
+ "Name" = "dev-public-a-subnet"
+ "Type" = "public"
}
+ tags_all = {
+ "Name" = "dev-public-a-subnet"
+ "Type" = "public"
}
+ vpc_id = (known after apply)
}
# module.vpc.aws_subnet.dev-pubilc-subnet["public-b"] will be created
+ resource "aws_subnet" "dev-pubilc-subnet" {
+ arn = (known after apply)
+ assign_ipv6_address_on_creation = false
+ availability_zone = "ap-northeast-2b"
+ availability_zone_id = (known after apply)
+ cidr_block = "172.20.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 = {
+ "Name" = "dev-public-b-subnet"
+ "Type" = "public"
}
+ tags_all = {
+ "Name" = "dev-public-b-subnet"
+ "Type" = "public"
}
+ vpc_id = (known after apply)
}
# module.vpc.aws_subnet.dev-pubilc-subnet["public-c"] will be created
+ resource "aws_subnet" "dev-pubilc-subnet" {
+ arn = (known after apply)
+ assign_ipv6_address_on_creation = false
+ availability_zone = "ap-northeast-2c"
+ availability_zone_id = (known after apply)
+ cidr_block = "172.20.3.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 = {
+ "Name" = "dev-public-c-subnet"
+ "Type" = "public"
}
+ tags_all = {
+ "Name" = "dev-public-c-subnet"
+ "Type" = "public"
}
+ vpc_id = (known after apply)
}
# modules/vpc/subnet.tf
resource "aws_subnet" "subnets_public" {
for_each = var.vpc_info.cidr_blocks_public
vpc_id = aws_vpc.vpc.id
cidr_block = each.value.cidr_block
availability_zone = each.value.availability_zone
tags = merge(
{
Name = "${var.common_info.env}-${each.value.subnet_name}"
},
var.common_tags
)
}
위 코드에서도 동일하게 for_each 를 사용하였다. 위와 다른점은 yaml 파일을 만들어 정의된 파일에서 값을 가져 오는거라 코드의 재사용성과 코드가 더 간결해진 것을 확인할 수 있다.
# _variables_/dev/vpc_info.yaml
# VPC
cidr_block_vpc: 172.21.0.0/16
vpc_name: vpc
# VPC - Subnet
cidr_blocks_public:
public_a:
subnet_name: public-a
cidr_block: 172.21.0.0/22
availability_zone: ap-northeast-2a
public_b:
subnet_name: public-b
cidr_block: 172.21.4.0/22
availability_zone: ap-northeast-2b
public_c:
subnet_name: public-c
cidr_block: 172.21.8.0/22
availability_zone: ap-northeast-2c
# terraform plan 을 통한 값 확인
% 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.vpc.aws_subnet.subnets_public["public_a"] will be created
+ resource "aws_subnet" "subnets_public" {
+ arn = (known after apply)
+ assign_ipv6_address_on_creation = false
+ availability_zone = "ap-northeast-2a"
+ availability_zone_id = (known after apply)
+ cidr_block = "172.21.0.0/22"
+ 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" = "Develop"
+ "Name" = "dev-public-a"
+ "Project" = "IaC-Terraform"
}
+ tags_all = {
+ "Environment" = "Develop"
+ "Name" = "dev-public-a"
+ "Project" = "IaC-Terraform"
}
+ vpc_id = (known after apply)
}
# module.vpc.aws_subnet.subnets_public["public_b"] will be created
+ resource "aws_subnet" "subnets_public" {
+ arn = (known after apply)
+ assign_ipv6_address_on_creation = false
+ availability_zone = "ap-northeast-2b"
+ availability_zone_id = (known after apply)
+ cidr_block = "172.21.4.0/22"
+ 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" = "Develop"
+ "Name" = "dev-public-b"
+ "Project" = "IaC-Terraform"
}
+ tags_all = {
+ "Environment" = "Develop"
+ "Name" = "dev-public-b"
+ "Project" = "IaC-Terraform"
}
+ vpc_id = (known after apply)
}
# module.vpc.aws_subnet.subnets_public["public_c"] will be created
+ resource "aws_subnet" "subnets_public" {
+ arn = (known after apply)
+ assign_ipv6_address_on_creation = false
+ availability_zone = "ap-northeast-2c"
+ availability_zone_id = (known after apply)
+ cidr_block = "172.21.8.0/22"
+ 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" = "Develop"
+ "Name" = "dev-public-c"
+ "Project" = "IaC-Terraform"
}
+ tags_all = {
+ "Environment" = "Develop"
+ "Name" = "dev-public-c"
+ "Project" = "IaC-Terraform"
}
+ vpc_id = (known after apply)
}
위 예시처럼 Resource 블록의 반복되는 사용을 줄이기 위해 count, for_each 을 알아보았다.
요약을 하자만 count 는 사용할 때 순서가 중요하며, 리소스에 영향을 줄 수 있다. 순서와 인덱스에 주의가 필요하지만 사용이 좀 더 간단하다.
for_each 는 리소스의 키에 따라 독립적으로 관리되므로, 한 리소스의 변경이 다른 리소스의 영향을 주지 않는다. 안정적이며 예측 가능한 리소스 관리가 필요 권장된다.