이 내용은 CloudNet@ 에서 진행하는 테라폼 기초 입문 스터디에 대한 연재글입니다.
스터디에서 사용하는 교재는 Terraform Up & Running 2nd Edition 입니다.
count
, for_each
는 resource
내에서 사용될 수 있습니다.for
표현식은 복잡한 타입을 또다른 복잡한 타입으로 변환하는데 쓰입니다.아래에서 5주차 스터디 내용을 공유합니다.
교재의 5장 내용을 다루고 있습니다.
테라폼을 통해 마치 프로그램을 작성하듯 코드를 작성할 수 있습니다. 이번 챕터에서는 아래의 내용을 학습할 예정입니다:
테라폼이 제공하는 반복문 구성은 아래와 같습니다:
count
구문for_each
표현식for
표현식for
문자열 지시어count
구문count
구문을 사용한 반복에 대해 살펴봅시다.
count
구문을 사용한 반복문 (1)IAM 사용자를 3명 생성하는 예시로 살펴보겠습니다. 리소스는 aws_iam_user
를 사용합니다. name
필드는 Required 입니다.
전체 예시코드는 chapter05/example01/count/index
디렉토리를 살펴봐주세요.
iam.tf
파일 전체provider "aws" {
region = "ap-northeast-2"
}
resource "aws_iam_user" "ch05-iam" {
count = 3
# 0부터 시작, 3번 반복합니다.
name = "s3ich4n.${count.index}"
}
name
값에 인덱스를 사용하지 않는다면, 3명의 IAM 사용자 이름 중복으로 오류가 발생합니다. 따라서, 반복문의 인덱스값을 사용하여 각 사용자에게 고유한 이름을 사용하도록 코드를 작성하였습니다.예시를 구동해봅시다.
`$ terraform init
Initializing the backend...
Initializing provider plugins...
- Finding latest version of hashicorp/aws...
- Installed hashicorp/aws v4.39.0 (signed by HashiCorp)
Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.
Terraform has been successfully initialized!
(중략)
$ terraform apply
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:
# aws_iam_user.ch05-iam[0] will be created
+ resource "aws_iam_user" "ch05-iam" {
(생략)
+ name = "s3ich4n.0"
}
# aws_iam_user.ch05-iam[1] will be created
+ resource "aws_iam_user" "ch05-iam" {
(생략)
+ name = "s3ich4n.1"
}
# aws_iam_user.ch05-iam[2] will be created
+ resource "aws_iam_user" "ch05-iam" {
(생략)
+ name = "s3ich4n.2"
}
Plan: 3 to add, 0 to change, 0 to destroy.
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value: yes
aws_iam_user.ch05-iam[1]: Creating...
aws_iam_user.ch05-iam[2]: Creating...
aws_iam_user.ch05-iam[0]: Creating...
aws_iam_user.ch05-iam[1]: Creation complete after 1s [id=s3ich4n.1]
aws_iam_user.ch05-iam[2]: Creation complete after 1s [id=s3ich4n.2]
aws_iam_user.ch05-iam[0]: Creation complete after 1s [id=s3ich4n.0]
Apply complete! Resources: 3 added, 0 changed, 0 destroyed.
#
# 반복문 구동을 확인했습니다. 삭제합시다.
#
$ terraform destroy -auto-approve
aws_iam_user.ch05-iam[2]: Refreshing state... [id=s3ich4n.2]
aws_iam_user.ch05-iam[0]: Refreshing state... [id=s3ich4n.0]
aws_iam_user.ch05-iam[1]: Refreshing state... [id=s3ich4n.1]
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
- destroy
Terraform will perform the following actions:
# aws_iam_user.ch05-iam[0] will be destroyed
- resource "aws_iam_user" "ch05-iam" {
(생략)
}
# aws_iam_user.ch05-iam[1] will be destroyed
- resource "aws_iam_user" "ch05-iam" {
(생략)
}
# aws_iam_user.ch05-iam[2] will be destroyed
- resource "aws_iam_user" "ch05-iam" {
(생략)
}
Plan: 0 to add, 0 to change, 3 to destroy.
aws_iam_user.ch05-iam[2]: Destroying... [id=s3ich4n.2]
aws_iam_user.ch05-iam[0]: Destroying... [id=s3ich4n.0]
aws_iam_user.ch05-iam[1]: Destroying... [id=s3ich4n.1]
aws_iam_user.ch05-iam[1]: Destruction complete after 1s
aws_iam_user.ch05-iam[2]: Destruction complete after 1s
aws_iam_user.ch05-iam[0]: Destruction complete after 1s
Destroy complete! Resources: 3 destroyed.
0부터 2까지 반복되어 유저 이름에 값이 붙은 것을 확인할 수 있습니다.
count
구문을 사용한 반복문 (2)마찬가지로, IAM 사용자를 3명 생성하는 예시로 살펴보겠습니다. 리소스는 aws_iam_user
를 사용합니다. name
필드는 Required 입니다.
전체 예시코드는 chapter05/example01/count/list_and_default
디렉토리를 살펴봐주세요.
variables.tf
파일variable "user_names" {
description = "Create IAM users with these names"
# 문자열을 담는 "리스트" 타입입니다.
type = list(string)
default = ["alice", "bob", "charlie"]
}
iam.tf
파일provider "aws" {
region = "ap-northeast-2"
}
resource "aws_iam_user" "ch05-ex01-count" {
# 이런 식으로, user_names내 리스트 값의 길이를 알 수 있지요.
count = length(var.user_names)
name = var.user_names[count.index]
}
outputs.tf
파일output "first_arn" {
# 첫번째 값을 가져옵니다.
value = aws_iam_user.ch05-ex01-count[0].arn
description = "The ARN for the first user"
}
output "all_arns" {
# 전체 값을 가져오는건 이렇게 사용합니다.
value = aws_iam_user.ch05-ex01-count[*].arn
description = "The ARNs for all users"
}
어떤 코드인지 개념을 살펴봅시다.
count
와 함께 배열 조회 구문과 length
함수 를 사용해서 반복을 할 수 있습니다.
0
에서 요소를 찾는 방법: ch05-ex01-count[0]
ch05-ex01-count[*]
이 예제를 스스로 terraform init && terraform apply -auto-approve
해보시기 바랍니다.
(-auto-approve
플래그는, 자동으로 프로비저닝하라는 키워드입니다.)
$ terraform init && terraform apply -auto-approve
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:
# aws_iam_user.ch05-ex01-count[0] will be created
+ resource "aws_iam_user" "ch05-ex01-count" {
(생략)
+ name = "alice"
(생략)
}
# aws_iam_user.ch05-ex01-count[1] will be created
+ resource "aws_iam_user" "ch05-ex01-count" {
(생략)
+ name = "bob"
(생략)
}
# aws_iam_user.ch05-ex01-count[2] will be created
+ resource "aws_iam_user" "ch05-ex01-count" {
(생략)
+ name = "charlie"
(생략)
}
Plan: 3 to add, 0 to change, 0 to destroy.
(생략)
Apply complete! Resources: 3 added, 0 changed, 0 destroyed.
Outputs:
all_arns = [
"arn:aws:iam::240962124292:user/alice",
"arn:aws:iam::240962124292:user/bob",
"arn:aws:iam::240962124292:user/charlie",
]
first_arn = "arn:aws:iam::240962124292:user/alice"
count
를 사용한 반복문의 제약사항위의 예제를 통해 계정을 만들고, 아래의 예시를 통해 제약사항을 살펴봅시다.
전체 리소스를 반복할 수는 있지만 리소스 내에서 인라인 블록을 반복할 수는 없습니다. 아래 예시로 살펴봅시다.
resource "aws_autoscaling_group" "example" {
launch_configuration = aws_launch_configuration.example.name
vpc_zone_identifier = data.aws_subnets.default.ids
target_group_arns = [aws_lb_target_group.asg.arn]
health_check_type = "ELB"
min_size = var.min_size
max_size = var.max_size
tag {
key = "Name"
value = var.cluster_name
propagate_at_launch = true
}
}
key
, value
, propagate_at_launch
에 대한 값으로 새 인라인 블록을 만들어야 합니다.count
구문을 사용해서 이러한 태그를 반복하여 동적인 인라인 tag
블록을 생성하려고 시도할 수도 있지만, 인라인 블록 내에서는 count 사용은 지원하지 않습니다.코드 재사용시 치명적인 문제가 있습니다. 아래 예시로 살펴봅시다.
variables.tf
variable "user_names" {
description = "Create IAM users with these names"
type = list(string)
default = ["alice", "charlie"]
}
배열의 중간에 항목을 제거하면 모든 항목이 1칸씩 앞으로 당겨질 것입니다.
테라폼은 인덱스 번호를 리소스 식별자로 봅니다. 따라서, ‘인덱스 1에서는 계정을 만들고, 인덱스2에서는 버킷을 삭제한다’라고 해석합니다.
$ terraform plan
aws_iam_user.ch05-ex01-count[2]: Refreshing state... [id=charlie]
aws_iam_user.ch05-ex01-count[1]: Refreshing state... [id=bob]
aws_iam_user.ch05-ex01-count[0]: Refreshing state... [id=alice]
Note: Objects have changed outside of Terraform
Terraform detected the following changes made outside of Terraform since the last "terraform apply" which may have affected this plan:
# aws_iam_user.ch05-ex01-count[0] has changed
~ resource "aws_iam_user" "ch05-ex01-count" {
id = "alice"
name = "alice"
+ tags = {}
# (5 unchanged attributes hidden)
}
# aws_iam_user.ch05-ex01-count[1] has changed
~ resource "aws_iam_user" "ch05-ex01-count" {
id = "bob"
name = "bob"
+ tags = {}
# (5 unchanged attributes hidden)
}
# aws_iam_user.ch05-ex01-count[2] has changed
~ resource "aws_iam_user" "ch05-ex01-count" {
id = "charlie"
name = "charlie"
+ tags = {}
# (5 unchanged attributes hidden)
}
(plan 경고문은 생략. 현재 테라폼 코드로는 이런 식으로 프로비저닝 될 것이다 하는 내용이 써져있습니다.)
Terraform will perform the following actions:
# aws_iam_user.ch05-ex01-count[1] will be updated in-place
~ resource "aws_iam_user" "ch05-ex01-count" {
id = "bob"
~ name = "bob" -> "charlie"
tags = {}
# (5 unchanged attributes hidden)
}
# aws_iam_user.ch05-ex01-count[2] will be destroyed
# (because index [2] is out of range for count)
- resource "aws_iam_user" "ch05-ex01-count" {
- arn = "arn:aws:iam::240962124292:user/charlie" -> null
- force_destroy = false -> null
- id = "charlie" -> null
- name = "charlie" -> null
- path = "/" -> null
- tags = {} -> null
- tags_all = {} -> null
- unique_id = "AIDATQGTWHICFTYJYIOOQ" -> null
}
Plan: 0 to add, 1 to change, 1 to destroy.
Changes to Outputs:
~ all_arns = [
# (1 unchanged element hidden)
"arn:aws:iam::240962124292:user/bob",
- "arn:aws:iam::240962124292:user/charlie",
]
count
사용 시 목록 중간 항목을 제거하면 테라폼은 해당 항목 뒤에 있는 모든 리소스를 삭제한 다음 해당 리소스를 처음부터 다시 만듭니다… 😱😱
for_each
표현식for_each
표현식을 이용한 반복문을 사용해봅시다. 먼저 for_each
표현식에 대해 알아봅시다.
for_each
구문은 테라폼 문법에 정의된 meta-argument
입니다. 모듈, 모든 리소스 타입에 응용될 수 있습니다.
list
, set
, map
을 사용하여 전체 리소스의 복사본, 리소스 내 인라인 블록의 복사본, 모듈의 복사본을 만들 수 있습니다. 문법은 아래와 같습니다:
resource "<PROVIDER>_<TYPE>" "<NAME>" {
for_each = <COLLECTION>
[CONFIG ...]
}
each.key
, each.value
로 키/밸류에 접근할 수 있습니다.for_each
표현식을 이용한 예시 (1)전체 예시코드는 chapter05/example01/for_each
디렉토리를 살펴봐주세요.
iam.tf
provider "aws" {
region = "ap-northeast-2"
}
resource "aws_iam_user" "ch05-foreach" {
for_each = toset(var.user_names)
name = each.value
}
variables.tf
variable "user_names" {
description = "Create IAM users with these names"
type = list(string)
default = ["alice", "bob", "charlie"]
}
outputs.tf
output "all_users" {
value = aws_iam_user.ch05-foreach
}
terraform init && terraform apply -auto-approve
를 통해, output에 나오는 for_each
는 어떤식인지 살펴봅시다.
$ terraform init
(생략)
$ terraform apply -auto-approve
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:
# aws_iam_user.ch05-foreach["alice"] will be created
+ resource "aws_iam_user" "ch05-foreach" {
(생략)
+ name = "alice"
(생략)
}
# aws_iam_user.ch05-foreach["bob"] will be created
+ resource "aws_iam_user" "ch05-foreach" {
(생략)
+ name = "bob"
(생략)
}
# aws_iam_user.ch05-foreach["charlie"] will be created
+ resource "aws_iam_user" "ch05-foreach" {
(생략)
+ name = "charlie"
(생략)
}
Plan: 3 to add, 0 to change, 0 to destroy.
Changes to Outputs:
+ all_users = {
+ alice = {
(생략)
+ name = "alice"
(생략)
}
+ bob = {
(생략)
+ name = "bob"
(생략)
}
+ charlie = {
(생략)
+ name = "charlie"
(생략)
}
}
(생략)
Apply complete! Resources: 3 added, 0 changed, 0 destroyed.
Outputs:
all_users = {
"alice" = {
"arn" = "arn:aws:iam::REDACTED:user/alice"
"force_destroy" = false
"id" = "alice"
"name" = "alice"
"path" = "/"
"permissions_boundary" = tostring(null)
"tags" = tomap(null) /* of string */
"tags_all" = tomap({})
"unique_id" = "REDACTED"
}
"bob" = {
"arn" = "arn:aws:iam::REDACTED:user/bob"
"force_destroy" = false
"id" = "bob"
"name" = "bob"
"path" = "/"
"permissions_boundary" = tostring(null)
"tags" = tomap(null) /* of string */
"tags_all" = tomap({})
"unique_id" = "REDACTED"
}
"charlie" = {
"arn" = "arn:aws:iam::REDACTED:user/charlie"
"force_destroy" = false
"id" = "charlie"
"name" = "charlie"
"path" = "/"
"permissions_boundary" = tostring(null)
"tags" = tomap(null) /* of string */
"tags_all" = tomap({})
"unique_id" = "REDACTED"
}
}
all_users
출력 변수가 for_each
의 키, 즉 사용자 이름을 키로 가지며 값이 해당 리소스의 전체 출력인 맵을 포함합니다.
for
표현식복잡한 타입을 또다른 복잡한 타입으로 변환하는데 쓰입니다. for
표현식은 아래와 같이 사용합니다. (공식링크)
for
표현식을 이용한 예시 (1): 컨테이너에 값을 추가하기list 내의 모든 이름을 대문자로 변환하는 예제를 통해 알아봅시다. 사용은 아래와 같이 할 수 있습니다.
# 결과를 list 형식으로 리턴합니다.
[for <ITEM> in <LIST> : <OUTPUT>]
[for <ITEM> in <LIST> : <OUTPUT> if <EXPRESSION>] # 조건문 절에서 다시 살펴봅시다!
# 결과를 map 형식으로 리턴합니다.
{for <ITEM> in <LIST> : <OUTPUT_KEY> => <OUTPUT_VALUE>}
{for <ITEM> in <LIST> : <OUTPUT_KEY> => <OUTPUT_VALUE> if <EXPRESSION>} # 조건문 절에서 다시 살펴봅시다!
if
구문(optional): 조건(<EXPRESSION>
)에 맞으면 값을 추가variable "names" {
description = "A list of names"
type = list(string)
default = ["alice", "bob", "charlie"]
}
output "upper_names" {
# names 안의 값에 대해 반복을 진행하며, 결과로는 upper(이름)의 값이 들어갑니다.
value = [for name in var.names : upper(name)]
}
output "short_upper_names" {
# 조건을 기재할 수도 있습니다.
value = [for name in var.names : upper(name) if length(name) < 6]
}
variable "prize_level" {
description = "map"
type = map(string)
default = {
alice = "#1"
bob = "#2"
charlie = "#3"
}
}
output "prize_status" {
# prize_status의 value는 키, 밸류 쌍을 꺼낸 문자열을 리턴할 수 있습니다.
value = [for name, role in var.prize_level : "\${name} goes to \${role} prize"]
}
output "upper_prize_status" {
# name, role에 해당하는 map을 리턴할 수도 있습니다.
value = {for name, role in var.prize_level : upper(name) => upper(role)}
}
for
표현식을 이용한 예시 (2): 문자열로 리턴하기앞서서 문자열 내에서 테라폼 코드를 참조하는 보간법 예시는 사용해본 적이 있습니다. "Hello, ${var.name}!"
과 같은 방식으로요.
문자열 지시자를 사용하면, 문자열 보간처럼 for
반복문, if
제어문에서도 사용할 수 있습니다. 어떻게 사용하는지 살펴봅시다.
# 반복문의 기본입니다.
%{ for <ITEM> in <COLLECTION> }<BODY>%{ endfor }
# 인덱스를 추가할 수도 있습니다.
%{ for <INDEX>, <ITEM> in <COLLECTION> }<BODY>%{ endfor }
예시를 보면서 함께 살펴봅시다.
variable "names" {
description = "A list of names"
type = list(string)
default = ["alice", "bob", "charlie"]
}
output "for_directive" {
# 끝의 endfor directive가 붙는다는 점을 제외하면 쉽게 이해할 수 있을 듯 합니다.
value = "%{ for name in var.names }${name}, %{ endfor }"
}
output "for_directive_index" {
# 파이썬의 enumerate() 을 쓰듯 사용할 수도 있군요!
value = "%{ for i, name in var.names }(${i}) ${name}, %{ endfor }"
}
terraform init && terraform apply
를 수행하면 아래와 같은 결과값을 보실 수 있습니다.
$ terraform init && terraform apply
(생략)
Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
Outputs:
for_directive = "alice, bob, charlie, "
for_directive_index = "(0) alice, (1) bob, (2) charlie, "
제 5장에서는 아래의 내용을 반드시 기억하셨으면 좋겠습니다.
(중요!) Prerequisite에서, 테라폼의 타입과 값에 대한 내용은 이미 알고있어야 하는 주요한 내용이라고 봅니다.
반복문의 사용방법과 주의사항에 대해 배웠습니다.
조건문의 사용방법과 주의사항에 대해 배웠습니다.
이것으로 제 5장, 반복문 설명을 마칩니다. 긴 글 읽어주셔서 감사합니다.