반보문 & 조건문
소개
테라폼은 유형을 반복 Loops, if 문 If-Statements을 사용하거나 무중단 배포를 할 수 있도록 count 메타 변수, for_each 표현식, create_before_destroy 생명 주기 블록 등 함수를 제공
- 반복문 Loops
- 조건문 Conditionals
- 무중단 배포 Zero-downtime deployment
- 테라폼의 주의 사항 Terraform gotchas
아래 구문 함수 표현식등에 대한 연습은 terraform console 에서 간편하고 빠르게 테스트 해 볼 수 있음
반복문 Loops
테라폼이 제공하는 반복문 구성 looping constructs
- count 매개변수 parameter : 리소스와 모듈의 반복
- for_each 표현식 expressions : 리소스 내에서 리소스 및 인라인 블록, 모듈을 반복
- for 표현식 expressions : 리스트 lists 와 맵 maps 을 반복
- for 문자열 지시어 string directive : 문자열 내에서 리스트 lists 와 맵 maps 을 반복
count 매개 변수
실습1
: IAM 사용자 3명 생성
- IAM 사용자 1명 생성 코드
provider "aws" {
region = "us-east-2"
}
resource "aws_iam_user" "example" {
name = "neo"
}
- 범용 프로그래밍 언어 for 반복문 사용 ⇒ 테라폼은 for 반복문 또는 언어에 내장된 절차 논리가 없어서 아래 구문 동작 불가능
# This is just pseudo code. It won't actually work in Terraform.
for (i = 0; i < 3; i++) {
resource "aws_iam_user" "example" {
name = "neo"
}
}
- 테라폼에서 count 를 사용하여 3명 IAM 사용자 생성 → 3명의 IAM 사용자 이름 중복으로 오류 발생 ⇒ for 반복문의 인덱스를 사용하여 각 사용자에게 고유한 이름 지정 가능
resource "aws_iam_user" "example" {
count = 3
name = "neo"
}
실습 시작
: iam.tf
- 아래 처럼 count.index 를 사용하여 반복문 안에 있는 각각의 반복 ieration 을 가리키는 인덱스를 얻을 수 있음
NICKNAME=<각자 닉네임>
NICKNAME=gasida
cat <<EOT > iam.tf
provider "aws" {
region = "ap-northeast-2"
}
resource "aws_iam_user" "myiam" {
**count = 3**
name = "$NICKNAME.**\${count.index}**"
}
EOT
- init & plan 실행 및 apply
#
**terraform init && terraform plan**
...
# aws_iam_user.myiam[0] will be created
# aws_iam_user.myiam[1] will be created
# aws_iam_user.myiam[2] will be created
# apply
terraform apply -auto-approve
# 확인
**terraform state list**
aws_iam_user.myiam[0]
aws_iam_user.myiam[1]
aws_iam_user.myiam[2]
**aws iam list-users | jq**
...
- 다음 실습을 위해 iam user 삭제
terraform destroy -auto-approve
실습2
: 입력 변수를 통해 IAM 사용자 생성
- 입력 변수 코드 생성 variables.tf
cat <<EOT > variables.tf
variable "user_names" {
description = "Create IAM users with these names"
type = list(string)
default = ["**gasida**", "**akbun**", "**fullmoon**"]
}
EOT
- 테라폼에서 count 와 함께 배열 조회 구문과 length 함수를 사용해서 사용자들 생성 가능
- 배열 조회 구문 Array lookup syntax
- ARRAY[
] - 예를 들어 다음은 var.user_names 의 인덱스 1에서 요소를 찾는 방법
- var.user_names[1]
- ARRAY[
- length (내장) 함수 built-on function
- length(
) - 주어진 ARRAY 의 항목 수를 반환하는 함수. 문자열 및 맵을 대상으로도 동작
- length(
- 배열 조회 구문 Array lookup syntax
- 위 적용한 코드 생성 iam.tf
cat <<EOT > iam.tf
provider "aws" {
region = "ap-northeast-2"
}
resource "aws_iam_user" "myiam" {
**count = length(var.user_names)
name = var.user_names[count.index]**
}
EOT
- init & plan : 리소스에 count 사용한 후에는 하나의 리소스가 아니라 리소스의 배열이 됩니다.
#
**terraform plan**
...
# aws_iam_user.myiam[0] will be created
# aws_iam_user.myiam[1] will be created
# aws_iam_user.myiam[2] will be created
#
- .
- aws_iam_user.myiam 은 이제 IAM 사용자의 배열이므로 표준 구문을 사용하여 해당 리소스인
_ . . 에서 속성을 읽은 대신 배열에서 인덱스를 지정해서 IAM 사용자를 명시해야합니다. -
_ . [INDEX].ATTRIBUTE
- aws_iam_user.myiam 은 이제 IAM 사용자의 배열이므로 표준 구문을 사용하여 해당 리소스인
- IAM 사용자 한명의 ARN과 사용자 전체의 ARN 을 출력 변수 outputs 으로 제공 : output.tf
- IAM 사용자 전체의 ARN을 원하면 인덱스 대신 스플랫 splat 연산자인 * 를 사용
cat <<EOT > outputs.tf
output "first_arn" {
value = aws_iam_user.myiam[0].arn
description = "The ARN for the first user"
}
output "all_arns" {
value = aws_iam_user.myiam[*].arn
description = "The ARNs for all users"
}
EOT
- apply
#
**terraform apply -auto-approve**
Outputs:
all_arns = [
"arn:aws:iam::911283464785:user/gasida",
"arn:aws:iam::911283464785:user/akbun",
"arn:aws:iam::911283464785:user/fullmoon",
]
first_arn = "arn:aws:iam::911283464785:user/gasida"
**terraform state list**
**terraform output**
- 다음 실습을 위해 iam user 삭제 및 코드 파일 삭제
terraform destroy -auto-approve
rm <코드 파일 삭제>.tf
- (참고) terrraform 0.13 부터는 모듈에 count 매개변수 사용 가능
#
**As of Terraform 0.13, the count parameter can also be used on modules.
For example, imagine you had a module at modules/landing-zone/iam-user that can create a single IAM user:**
resource "aws_iam_user" "example" {
name = var.user_name
}
**The username is passed into this module as an input variable:**
variable "user_name" {
description = "The user name to use"
type = string
}
**And the module returns the ARN of the created IAM user as an output variable:**
output "user_arn" {
value = aws_iam_user.example.arn
description = "The ARN of the created IAM user"
}
**You could use this module with a count parameter to create three IAM users as follows:**
module "users" {
source = "../../../modules/landing-zone/iam-user"
count = length(var.user_names)
user_name = var.user_names[count.index]
}
**The preceding code uses count to loop over this list of usernames:**
variable "user_names" {
description = "Create IAM users with these names"
type = list(string)
default = ["neo", "trinity", "morpheus"]
}
**And it outputs the ARNs of the created IAM users as follows:**
output "user_arns" {
value = module.users[*].user_arn
description = "The ARNs of the created IAM users"
}
**Just as adding count to a resource turns it into an array of resources, adding count to a module turns it into an array of modules.
If you run apply on this code, you’ll get the following output:**
$ terraform apply
(...)
Apply complete! Resources: 3 added, 0 changed, 0 destroyed.
Outputs:
all_arns = [
"arn:aws:iam::123456789012:user/neo",
"arn:aws:iam::123456789012:user/trinity",
"arn:aws:iam::123456789012:user/morpheus",
]
count 두 가지 제약 사항 & 실습3
1. 전체 리소스를 반복할 수는 있지만 리소스 내에서 인라인 블록을 반복할 수는 없습니다.
- 예를 들어 아래 ASG 리소스에 태그 설정 방법에서 생각해보자
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
}
}
- 각각의 tag 를 사용하려면 key, value, propagate_at_launch 에 대한 값으로 새 인라인 블록을 만들어야 합니다.
- 따라서 count 매개변수를 사용해서 이러한 태그를 반복하고 동적인 인라인 tag 블록을 생성하려고 시도할 수도 있지만, 인라인 블록 내에서는 count 사용은 지원하지 않습니다.
2. 변경하려고 할 때 발생합니다.
- 실습을 위해 다시 IAM 사용자 생성
**terraform apply -auto-approve
...**
aws_iam_user.myiam[2]: Refreshing state... [id=fullmoon]
aws_iam_user.myiam[1]: Refreshing state... [id=akbun]
aws_iam_user.myiam[0]: Refreshing state... [id=gasida]
...
- IAM 사용자 생성 목록에서 생각
variable "user_names" {
description = "Create IAM users with these names"
type = list(string)
default = ["**gasida**", "**akbun**", "**fullmoon**"]
}
- 중간에 있는 akbun 을 제거하고 plan & apply
cat <<EOT > variables.tf
variable "user_names" {
description = "Create IAM users with these names"
type = list(string)
default = ["**gasida**", "**fullmoon**"]
}
EOT
# plan : 출력 내용 확인!
**terraform plan**
...
**~ update in-place
- destroy**
Terraform will perform the following actions:
# aws_iam_user.myiam[1] will be **updated in-place**
~ resource "aws_iam_user" "myiam" {
id = "akbun"
~ name = "**akbun**" -> "**fullmoon**"
tags = {}
# (5 unchanged attributes hidden)
}
# aws_iam_user.myiam[2] will be **destroyed**
# (because index [2] is out of range for count)
- resource "aws_iam_user" "myiam" {
- arn = "arn:aws:iam::911283464785:user/fullmoon" -> null
- force_destroy = false -> null
- id = "fullmoon" -> null
- name = "**fullmoon**" -> null
- path = "/" -> null
- tags = {} -> null
- tags_all = {} -> null
- unique_id = "AIDA5ILF2FJI2ELMQRCZV" -> null
}
Plan: 0 to add, 1 to change, 1 to destroy.
Changes to Outputs:
~ all_arns = [
# (1 unchanged element hidden)
"arn:aws:iam::911283464785:user/**akbun**",
- "arn:aws:iam::911283464785:user/**fullmoon**",
]
- 배열의 중간에 항목을 제거하면 모든 항목이 1칸씩 앞으로 당겨짐.
- 테라폼이 인덱스 번호를 리소스 식별자로 보기 때문에 ‘인덱스 1에서는 버킷을 바꾸고, 인덱스2에서는 버킷을 삭제한다’라고 해석합니다.
- 즉 count 사용 시 목록 중간 항목을 제거하면 테라폼은 해당 항목 뒤에 있는 모드 리소스를 삭제한 다음 해당 리소스를 처음부터 다시 만듬.
#
**terraform apply -auto-approve**
aws_iam_user.myiam[2]: Refreshing state... [id=fullmoon]
aws_iam_user.myiam[1]: Refreshing state... [id=akbun]
aws_iam_user.myiam[0]: Refreshing state... [id=gasida]
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
~ update in-place
- destroy
Terraform will perform the following actions:
# aws_iam_user.myiam[1] will be updated in-place
~ resource "aws_iam_user" "myiam" {
id = "akbun"
~ name = "akbun" -> "fullmoon"
tags = {}
# (5 unchanged attributes hidden)
}
# aws_iam_user.myiam[2] will be destroyed
# (because index [2] is out of range for count)
- resource "aws_iam_user" "myiam" {
- arn = "arn:aws:iam::911283464785:user/fullmoon" -> null
- force_destroy = false -> null
- id = "fullmoon" -> null
- name = "fullmoon" -> null
- path = "/" -> null
- tags = {} -> null
- tags_all = {} -> null
- unique_id = "AIDA5ILF2FJI2ELMQRCZV" -> null
}
Plan: 0 to add, 1 to change, 1 to destroy.
Changes to Outputs:
~ all_arns = [
# (1 unchanged element hidden)
"arn:aws:iam::911283464785:user/akbun",
- "arn:aws:iam::911283464785:user/fullmoon",
]
aws_iam_user.myiam[2]: Destroying... [id=fullmoon]
aws_iam_user.myiam[1]: Modifying... [id=akbun]
aws_iam_user.myiam[2]: Destruction complete after 1s
╷
│ Error: Error updating IAM User akbun: EntityAlreadyExists: User with name fullmoon already exists.
│ status code: 409, request id: 899d7c85-3bd0-4cc0-a556-6426f6466ea1
│
│ with aws_iam_user.myiam[1],
│ on iam.tf line 5, in resource "aws_iam_user" "myiam":
│ 5: resource "aws_iam_user" "myiam" {
│
- 다음 실습을 위해 iam user 삭제
terraform destroy -auto-approve
aws iam list-users | jq
for_each 표현식
for_each 표현식을 사용한 반복문 처리 & 실습4
배열 ARRAY 와 리스트 LIST 의 차이점은 무엇인가요?
- for_each 표현식을 사용하면 리스트 lists, 집합 sets, 맵 maps 를 사용하여 전체 리소스의 여러 복사본 또는 리소스 내 인라인 블록의 여러 복사본, 모듈의 복사본을 생성 할 수 있음
- 먼저 for_each 를 사용하여 리소스의 여러 복사본을 만드는 구문
resource "<PROVIDER>_<TYPE>" "<NAME>" {
**for_each = <COLLECTION>**
**[CONFIG ...]**
}
- .
- COLLECTION 은 루프를 처리할 집합 sets 또는 맵 maps
- 리소스에 for_each 를 사용할 때에는 리스트는 지원하지 않습니다.
- 그리고 CONFIG 는 해당 리소스와 관련된 하나 이상의 인수로 구성되는데 CONFIG 내에서 each.key 또는 each.value 를 사용하여 COLLECTION 에서 현재 항목의 키와 값에 접근할 수 있습니다.
- 예를 들어 다음은 for_each 를 사용하여 3명의 IAM 사용자를 생성하는 방법
- var.user_names 리스트를 집합(set)으로 변환하기 위해 toset 사용. for_each 는 리소스에 사용될 때는 집합과 맵만 지원.
- for_each 가 이 집합을 반복하면 each.value 에서 각 사용자 이름을 사용할 수 있습니다.
- 일반적으로는 each.key 는 키/값 쌍 맵에서만 사용가능하지만, 사용자 이름은 each.key 에서도 사용할 수 있습니다.
cat <<EOT > iam.tf
provider "aws" {
region = "ap-northeast-2"
}
resource "aws_iam_user" "myiam" {
**for_each = toset(var.user_names)**
**name = each.value**
}
EOT
cat <<EOT > variables.tf
variable "user_names" {
description = "Create IAM users with these names"
type = list(string)
default = ["**gasida**", "**akbun**", "**fullmoon**"]
}
EOT
- for_each 를 사용한 후에는 하나의 리소스 또는 count 를 사용한 것과 같은 리소스 배열이 되는 것이 아니리 리소스 맵 list into a set 이 됩니다.
- 이 의미를 확인하려면 원래의 outputs 을 제거하고 새로운 all_users 출력 변수를 추가하자
cat <<EOT > outputs.tf
output "all_users" {
value = aws_iam_user.myiam
}
EOT
- plan & apply
#
**terraform plan && terraform apply -auto-approve**
Outputs:
all_users = {
"akbun" = {
"arn" = "arn:aws:iam::911283464785:user/akbun"
"force_destroy" = false
"id" = "akbun"
"name" = "akbun"
"path" = "/"
"permissions_boundary" = tostring(null)
"tags" = tomap(null) /* of string */
"tags_all" = tomap({})
"unique_id" = "AIDA5ILF2FJI7S7KCZYGA"
}
"fullmoon" = {
"arn" = "arn:aws:iam::911283464785:user/fullmoon"
"force_destroy" = false
"id" = "fullmoon"
"name" = "fullmoon"
"path" = "/"
"permissions_boundary" = tostring(null)
"tags" = tomap(null) /* of string */
"tags_all" = tomap({})
"unique_id" = "AIDA5ILF2FJI4MUNCWZC2"
}
"gasida" = {
"arn" = "arn:aws:iam::911283464785:user/gasida"
"force_destroy" = false
"id" = "gasida"
"name" = "gasida"
"path" = "/"
"permissions_boundary" = tostring(null)
"tags" = tomap(null) /* of string */
"tags_all" = tomap({})
"unique_id" = "AIDA5ILF2FJIWDUCOXT2E"
}
}
# 확인
terraform state list
aws_iam_user.myiam["akbun"]
aws_iam_user.myiam["fullmoon"]
aws_iam_user.myiam["gasida"]
terraform output
all_users = {
"akbun" = {
"arn" = "arn:aws:iam::911283464785:user/akbun"
"force_destroy" = false
"id" = "akbun"
"name" = "akbun"
"path" = "/"
"permissions_boundary" = tostring(null)
"tags" = tomap(null) /* of string */
"tags_all" = tomap({})
"unique_id" = "AIDA5ILF2FJI7S7KCZYGA"
}
..
- .
- all_users 출력 변수가 for_each 의 키, 즉 사용자 이름을 키로 가지며 값이 해당 리소스의 전체 출력인 맵을 포함합니다.
- 만약 arn 만 가져오려면 맵에서 값만 반환하는 내장 함수 values 를 이용해 ARN을 추출하고 splat 표현식을 사용하면 됩니다.
cat <<EOT > outputs.tf
output "all_users" {
value = values(aws_iam_user.myiam)[*].arn
}
EOT
- .
- apply
#
terraform apply -auto-approve
# 확인
terraform output
all_users = [
"arn:aws:iam::911283464785:user/akbun",
"arn:aws:iam::911283464785:user/fullmoon",
"arn:aws:iam::911283464785:user/gasida",
]
- for_each 를 사용해 리소스를 맵으로 처리하면 컬렉션 중간의 항목도 안전하게 제거할 수 있어서, count 로 리소스를 배열 처리보다 이점이 큽니다.
- var.user_names 리스트 중간에 값을 제거하고 plan 으로 확인
cat <<EOT > variables.tf
variable "user_names" {
description = "Create IAM users with these names"
type = list(string)
default = ["**gasida**", "**fullmoon**"]
}
EOT
**terraform plan**
aws_iam_user.myiam["akbun"]: Refreshing state... [id=akbun]
aws_iam_user.myiam["fullmoon"]: Refreshing state... [id=fullmoon]
aws_iam_user.myiam["gasida"]: Refreshing state... [id=gasida]
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.myiam["akbun"] will be destroyed
# (because key ["akbun"] is not in for_each map)
- resource "aws_iam_user" "myiam" {
- arn = "arn:aws:iam::911283464785:user/akbun" -> null
- force_destroy = false -> null
- id = "akbun" -> null
- name = "akbun" -> null
- path = "/" -> null
- tags = {} -> null
- tags_all = {} -> null
- unique_id = "AIDA5ILF2FJI7S7KCZYGA" -> null
}
Plan: 0 to add, 0 to change, 1 to destroy.
Changes to Outputs:
~ all_users = [
- "arn:aws:iam::911283464785:user/akbun",
"arn:aws:iam::911283464785:user/fullmoon",
# (1 unchanged element hidden)
]
- .
- 이제 주변 모든 리소스를 옮기지 않고 정확히 목표한 리소스만 삭제가 됩니다.
- 따라서 리소스의 여러 복사본을 만들 때는 count 대신 for_each 를 사용하는 것이 바람직합니다.
-
for_each 인라인 블록에 반복문 사용
for_each 또 다른 장점은 리소스 내에서 여러 개의 인라인 블록을 만들 수 있다는 점입니다.
- 예를 들어 for_each 를 사용하여 ASG 에 tag 인라인 블록을 동적으로 생성 할 수 있습니다.
- 먼저 사용자가 사용자 정의 태그를 지정할 수 있게 modules/services/webserver-cluster/variables.tf에 custom_tags 라는 새로운 맵 입력 변수를 추가합니다.
variable "custom_tags" {
description = "Custom tags to set on the Instances in the ASG"
type = map(string)
default = {}
}
- 다음으로 프로덕션 환경의 live/prod/services/webserver-cluster/main.tf에서 아래와 같이 일부 사용자 정의 태그를 설정합니다.
module "webserver_cluster" {
source = "../../../../modules/services/webserver-cluster"
cluster_name = "webservers-prod"
db_remote_state_bucket = "(YOUR_BUCKET_NAME)"
db_remote_state_key = "prod/data-stores/mysql/terraform.tfstate"
instance_type = "m4.large"
min_size = 2
max_size = 10
**custom_tags = {
Owner = "team-foo"
ManagedBy = "terraform"
}**
}
- 앞 코드에서 몇 가지 유용한 태그가 설정되어 있습니다.
- Owner 태그는 이 ASG를 소유한 팀을 명시하고, ManagedBy 는 이 인프라가 테라폼을 사용하여 배포되었음을 며잇하빈다.
- 이 인프라를 수동으로 수정해서는 안 되는 의미입니다. 이 부분은 뒤에 ‘유효한 계획의 실패’에서 다룹니다.
- 태그를 사용할 때는 일반적으로 팀의 태그 지정 표준을 제시하고 이 표준을 코드로 적용하게 하는 테라폼 모듈을 만드는 것이 바람직합니다.
- 이제 이것을 ASG 리소스에 설정합니다. 아래 코드는 pseudo code 로 테라폼에서는 동작하지 않습니다.
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
}
# This is just pseudo code. It won't actually work in Terraform.
for (tag in var.custom_tags) {
tag {
key = tag.key
value = tag.value
propagate_at_launch = true
}
}
}
- for_each 를 사용해 인라인 블록을 동적으로 생성하는 구문은 아래와 같습니다.
dynamic "<VAR_NAME>" {
for_each = <COLLECTION>
content {
[CONFIG...]
}
}
- .
- 여기서 VAR_NAME 은 각 ‘반복’의 값을 저장할 변수에 사용할 이름이고, COLLECTION 은 반복되는 리스트 또는 맵이며, content 블록은 각 반복에서 생성되는 항목입니다.
- content 블록 내에서
.key 및 .value 를 사용해 COLLECTION 에 있는 현재 항목의 키와 값에 각각 액세스할 수 있습니다. - for_each 를 리스트와 함께 사용하는 경우 key는 인덱스가 되고 value는 해당 인덱스 목록에 있는 항목이 된다는 점을 기억합니다.
- 그리고 for_each 를 맵과 함께 사용하는 경우 key와 value는 맵의 키-값 쌍 중 하나가 된다는 점도 유의합니다.
- 종합하면 ASG 리소스에서 for_each 를 사용하여 tag 블록을 동적으로 생성하는 방법은 아래 코드와 같습니다.
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
}
dynamic "tag" {
for_each = var.custom_tags
content {
key = tag.key
value = tag.value
propagate_at_launch = true
}
}
}
- .
- plan
$ terraform plan
Terraform will perform the following actions:
# aws_autoscaling_group.example will be updated in-place
~ resource "aws_autoscaling_group" "example" {
(...)
tag {
key = "Name"
propagate_at_launch = true
value = "webservers-prod"
}
+ tag {
+ key = "Owner"
+ propagate_at_launch = true
+ value = "team-foo"
}
+ tag {
+ key = "ManagedBy"
+ propagate_at_launch = true
+ value = "terraform"
}
}
Plan: 0 to add, 1 to change, 0 to destroy.
- (참고) ENFORCING TAGGING STANDARDS
provider "aws" {
region = "us-east-2"
# Tags to apply to all AWS resources by default
default_tags {
tags = {
Owner = "team-foo"
ManagedBy = "Terraform"
}
}
}
- (참고) for_each 를 모듈에서 활용
for_each works with modules in a more or less identical fashion. Using the iam-user module from earlier,
you can create three IAM users with it using for_each as follows:
module "users" {
source = "../../../modules/landing-zone/iam-user"
for_each = toset(var.user_names)
user_name = each.value
}
And you can output the ARNs of those users as follows:
output "user_arns" {
value = values(module.users)[*].user_arn
description = "The ARNs of the created IAM users"
}
When you run apply on this code, you get the expected output:
$ terraform apply
(...)
Apply complete! Resources: 3 added, 0 changed, 0 destroyed.
Outputs:
all_arns = [
"arn:aws:iam::123456789012:user/morpheus",
"arn:aws:iam::123456789012:user/neo",
"arn:aws:iam::123456789012:user/trinity",
]
for 표현식
- for 표현식 Expressions 을 이용한 반복문 &
실습5
- 리소스와 인라인 블록을 반복하는 법을 알아보았습니다. 하지만 단일 값을 생성하기 위해 반복이 필요한 경우에는 어떻게 해야 할까요?
- names 의 리스트를 가진 테라폼 코드를 작성했다고 가정합니다 → 모든 이름을 대문자로 변환하려면 어떻게 해야 할까요?
cat <<EOT > main.tf
variable "**names**" {
description = "A list of names"
type = list(string)
default = ["**gasida**", "**akbun**", "**fullmoon**"]
}
EOT
- 테라폼은 for 표현식으로 유사한 기능을 제공합니다. 기본 구문은 아래와 같습니다. (for_each 표현식과 다릅니다!)
[for <ITEM> in <LIST> : <OUTPUT>]
- .
- 여기서 LIST 는 반복할 리스트이고 ITEM은 LIST의 각 항목에 할당할 변수의 이름이며 OUTPUT은 ITEM을 어떤 식으로든 변환하는 표현식입니다.
-
예를 들어 var.names 의 이름 목록을 대문자로 변환하는 테라폼 코드는 다음과 같습니다.
cat <<EOT > main.tf variable "names" { description = "A list of names" type = list(string) default = ["gasida", "akbun", "fullmoon"] } output "upper_names" { value = [for name in var.names : upper(name)] } EOT
- init & plan & apply
#
terraform init && terraform plan && terraform apply -auto-approve
Outputs:
upper_names = [
"GASIDA",
"AKBUN",
"FULLMOON",
]
# 확인
**terraform output
terraform state list**
- 파이썬의 리스트 내포 구문과 마찬가지로 조건을 지정해서 결과 리스트를 필터링할 수 있습니다.
cat <<EOT > main.tf
variable "names" {
description = "A list of names"
type = list(string)
default = ["gasida", "akbun", "fullmoon"]
}
output "upper_names" {
value = [for name in var.names : upper(name)]
}
output "short_upper_names" {
value = [for name in var.names : upper(name) if length(name) < 6]
}
EOT
#
**terraform apply -auto-approve**
Outputs:
short_upper_names = [
"AKBUN",
]
upper_names = [
"GASIDA",
"AKBUN",
"FULLMOON",
]
- 다음과 같은 구문을 사용하여 맵을 반복할 수 있습니다.
[for <KEY>, <VALUE> in <MAP> : <OUTPUT>]
- .
- 여기서 MAP은 반복되는 맵이고, KEY와 VALUE는 MAP의 각 키-값 쌍에 할당할 로컬 변수의 이름입니다.
- OUTPUT은 KEY와 VALUE를 어떤 식으로든 변환하는 표현식입니다. 예를 들면 아래와 같습니다.
cat <<EOT > main.tf
variable "names" {
description = "A list of names"
type = list(string)
default = ["gasida", "akbun", "fullmoon"]
}
output "upper_names" {
value = [for name in var.names : upper(name)]
}
output "short_upper_names" {
value = [for name in var.names : upper(name) if length(name) < 5]
}
variable "hero_thousand_faces" {
description = "map"
type = map(string)
default = {
gasida = "hero"
akbun = "love interest"
fullmoon = "mentor"
}
}
output "bios" {
value = [for name, role in var.hero_thousand_faces : "\${name} is the \${role}"]
}
EOT
- .
- apply
#
terraform apply -auto-approve
Outputs:
bios = [
"akbun is the love interest",
"fullmoon is the mentor",
"gasida is the hero",
]
...
- for 표현식을 리스트가 아닌 맵을 출력하는 데 사용할 수도 있습니다.
# 리스트를 반복하고 맵을 출력 Loop over a list and output a map
{for <ITEM> in <LIST> : <OUTPUT_KEY> => <OUTPUT_VALUE>}
# 맵을 반복하고 리스트를 출력 Loop over a map and output a map
{for <KEY>, <VALUE> in <MAP> : <OUTPUT_KEY> => <OUTPUT_VALUE>}
- 식을 대괄호 대신 중괄호로 묶고 각 반복마다 단일 값을 출력하는 대신 키와 값을 화살표로 구분하여 출력하는 것이 유일한 차이점입니다.
- 예를 들어 다음은 맵을 변환하여 모든 키와 값을 대문자로 만드는 방법입니다.
cat <<EOT > main.tf
variable "names" {
description = "A list of names"
type = list(string)
default = ["gasida", "akbun", "fullmoon"]
}
output "upper_names" {
value = [for name in var.names : upper(name)]
}
output "short_upper_names" {
value = [for name in var.names : upper(name) if length(name) < 5]
}
variable "hero_thousand_faces" {
description = "map"
type = map(string)
default = {
gasida = "hero"
akbun = "love interest"
fullmoon = "mentor"
}
}
output "bios" {
value = [for name, role in var.hero_thousand_faces : "\${name} is the \${role}"]
}
output "upper_roles" {
value = {for name, role in var.hero_thousand_faces : upper(name) => upper(role)}
}
EOT
- .
- apply
#
terraform apply -auto-approve
...
upper_names = [
"GASIDA",
"AKBUN",
"FULLMOON",
]
upper_roles = {
"AKBUN" = "LOVE INTEREST"
"FULLMOON" = "MENTOR"
"GASIDA" = "HERO"
}
문자열 지시자
- 문자열 지시자를 사용하는 반복문 Loops with the for String Derective &
실습6
- 앞에서 문자열 내에 테라폼 코드를 참조할 수 있는 문자열 보간법을 배웠습니다.
"Hello, ${var.name}"
- 문자열 지시자를 사용하면 문자열 보간과 유사한 구문으로 문자열 내에서 for 반복문, if문 같은 제어문을 사용할 수 있습니다.
- 다만 달러 부호와 중괄호 ${..} 대신 백분율 부호 %{}.. 를 사용한다는 차이가 있습니다.
- 테라폼은 두 가지 유형의 문자열 지시자, for 반복문과 조건문을 지원합니다.
- 이번 절에서는 for 반복문을 살펴보고 이 장의 뒷부분에서 조건문을 다룹니다.
- for 문자열 지시자는 다음 구문을 사용합니다.
%{ for <ITEM> in <COLLECTION> }<BODY>%{ endfor }
- 여기서 COLLECTION 은 반복할 리스트 또는 맵이고 ITEM은 COLLECTION 의 각 항목에 할당할 로컬 변수의 이름이며 BODY는 ITEM을 참조할 수 있는 각각의 반복을 렌더링하는 대상입니다. 예를 들면 아래와 같습니다.
cat <<EOT > main.tf
variable "names" {
description = "Names to render"
type = list(string)
default = ["gasida", "akbun", "fullmoon"]
}
output "for_directive" {
value = "%{ for name in var.names }\${name}, %{ endfor }"
}
EOT
- .
- apply
#
terraform apply -auto-approve
Outputs:
for_directive = "gasida, akbun, fullmoon, "
- .
- 인덱스를 추가
%{ for <INDEX>, <ITEM> in <COLLECTION> }<BODY>%{ endfor }
- .
- 코드 수정
cat <<EOT > main.tf
variable "names" {
description = "Names to render"
type = list(string)
default = ["gasida", "akbun", "fullmoon"]
}
output "for_directive" {
value = "%{ for name in var.names }\${name}, %{ endfor }"
}
output "for_directive_index" {
value = "%{ for i, name in var.names }(\${i}) \${name}, %{ endfor }"
}
EOT
- .
- apply
#
terraform apply -auto-approve
Outputs:
for_directive = "gasida, akbun, fullmoon, "
for_directive_index = "(0) gasida, (1) akbun, (2) fullmoon, "
- .
- 다음 실습을 위해 삭제
terraform destroy -auto-approve
rm <코드 파일 삭제>.tf
- 그외 표현식 코드 참고
terraform-up-and-running-code/main.tf at master · brikis98/terraform-up-and-running-code
조건문 Conditionals
테라폼이 제공하는 조건문 구성 conditionals
- count 매개 변수 parameter : 조건부 리소스에서 사용
- for_each 와 for 표현식 expressions : 리소스 내의 조건부 리소스 및 인라인 블록에 사용
- If 문자열 지시자 if string directive : 문자열 내의 조건문에 사용
count 매개 변수
count 매개변수를 사용하면 기본 반복문을 수행할 수 있습니다. 이를 응용하여 기본 조건문 작업을 수행할 수 있습니다. 먼저 if문을 살펴보고 이어서 if-else문으로 넘어가겠습니다.
count 매개 변수를 사용한 if문 if-statements with the count parameter
- 챕터 4 ASG 환경에서 조건부로 일부 사용자에게는 모듈을 생성해주고 나머지 사용자에게는 생성해주지 않을 방법이 있을까요?
- 이렇듯 조건부로 모듈을 생성하는 방법을 분기 처리라고 합니다.
- 분기 처리 첫 번째 단계는 모듈의 오토스케일링 사용 여부를 지정하는데 사용할 Boolean 입력 변수 를 modules/services/webserver-cluster/variables.tf에 추가하는 것입니다.
- 코드 - 링크
variable "enable_autoscaling" {
description = "If set to true, enable auto scaling"
type = bool
}
<CONDITION> ? <TRUE_VAL> : <FALSE_VAL>
- count 매개변수와 두 가지 특성을 활용하여 동작 수행할 수 있다
- 리소스에 count 를 1로 설정하면 해당 리소스의 사본 하나를 얻습니다. count 를 0으로 설정하면 해당 리소스가 만들어지지 않습니다.
- 테라폼은 **
? : ** 형식의 조건 표현식 conditional expression 을 지원합니다. - CONDITION 에서 boolean logic 를 평가하고 결과가 ture dㅣ며 TRUE_VAL을 반환하고, 결과가 false이면 FALSE_VAL을 반환
- 위 2가지 특성을 종합하여 아래 코드 처럼 webserver-cluster 모듈을 업데이트 할 수 있다
resource "aws_autoscaling_schedule" "scale_out_during_business_hours" {
count = var.enable_autoscaling ? 1 : 0
scheduled_action_name = "${var.cluster_name}-scale-out-during-business-hours"
min_size = 2
max_size = 10
desired_capacity = 10
recurrence = "0 9 * * *"
autoscaling_group_name = aws_autoscaling_group.example.name
}
resource "aws_autoscaling_schedule" "scale_in_at_night" {
count = var.enable_autoscaling ? 1 : 0
scheduled_action_name = "${var.cluster_name}-scale-in-at-night"
min_size = 2
max_size = 10
desired_capacity = 2
recurrence = "0 17 * * *"
autoscaling_group_name = aws_autoscaling_group.example.name
}
- .
- var.enable_autoscaling 가 true 인 경우 각각의 aws_autoscaling_schedule 리소스에 대한 count 매개 변수가 1로 설정되므로 리소스가 각각 하나씩 생성됩니다.
- var.enable_autoscaling 가 false 인 경우 각각의 aws_autoscaling_schedule 리소스에 대한 count 매개 변수가 0로 설정되므로 리소스가 생성되지 않습니다.
- enable_autoscaling 가 false 로 설정하여 오토스케일링을 비활성화하기 위해, live/stage/services/webserver-cluster/main.tf에 있는 스테이징에서 다음과 같이 이 모듈의 사용법을 업데이트 할 수 있습니다.
- 코드 - 링크
module "webserver_cluster" {
source = "../../../../modules/services/webserver-cluster"
cluster_name = "webservers-stage"
db_remote_state_bucket = "(YOUR_BUCKET_NAME)"
db_remote_state_key = "**stage**/data-stores/mysql/terraform.tfstate"
instance_type = "t2.micro"
min_size = 2
max_size = 2
**enable_autoscaling = false**
}
- 마찬가지로 enable_autoscaling 가 true 로 설정하여 오토스케일링을 활성화하기 위해, live/prod/services/webserver-cluster/main.tf에 있는 프로덕션에서 이 모듈의 사용법을 업데이트 할 수 있습니다.
- 코드 - 링크
module "webserver_cluster" {
source = "../../../../modules/services/webserver-cluster"
cluster_name = "webservers-prod"
db_remote_state_bucket = "(YOUR_BUCKET_NAME)"
db_remote_state_key = "**prod**/data-stores/mysql/terraform.tfstate"
instance_type = "m4.large"
min_size = 2
max_size = 10
**enable_autoscaling = true**
custom_tags = {
Owner = "team-foo"
ManagedBy = "terraform"
}
}
count 매개 변수를 사용한 if-else문 if-else-statements with the count parameter
- IAM 사용자 neo 에 클라우드워치에 대한 액세스 권한을 부여하려고 합니다.
- 그런데 이때 테라폼 구성을 적용하는 사람이 neo에게 읽기 권한만 부여할 것인지 아니면 읽기와 쓰기 권한을 모두 부여할 것인지 결정하게 한다고 가정하겠습니다.
- 읽기 전용 IAM 정책
resource "aws_iam_policy" "**cloudwatch_read_only**" {
name = "**cloudwatch-read-only**"
policy = data.aws_iam_policy_document.cloudwatch_read_only.json
}
data "aws_iam_policy_document" "cloudwatch_read_only" {
statement {
effect = "Allow"
actions = [
"cloudwatch:Describe*",
"cloudwatch:Get*",
"cloudwatch:List*"
]
resources = ["*"]
}
}
- .
- 읽기/쓰기 IAM 정책
resource "aws_iam_policy" "**cloudwatch_full_access**" {
name = "**cloudwatch-full-access**"
policy = data.aws_iam_policy_document.cloudwatch_full_access.json
}
data "aws_iam_policy_document" "cloudwatch_full_access" {
statement {
effect = "Allow"
actions = ["cloudwatch:*"]
resources = ["*"]
}
}
- if 나 else 중 하나가 실행되면 나머지 테라폼 코드는 둘 중 어느 것이 실행되었는지도 알 필요가 없습니다.
- give_neo_cloudwatch_full_access라는 새로운 입력 변수의 값에 기반해 이러한 IAM 정책 중 하나를 neo에 연결하는 것이 목표입니다.
variable "give_neo_cloudwatch_full_access" {
description = "If true, neo gets full access to CloudWatch"
type = bool
}
- 테라폼에서 이 동작 수행을 위해서 count 매개변수와 각 리소스에 대한 조건 표현식을 사용할 수 있습니다.
resource "aws_iam_user_policy_attachment" "neo_cloudwatch_full_access" {
count = var.give_neo_cloudwatch_full_access ? 1 : 0
user = aws_iam_user.example[0].name
policy_arn = aws_iam_policy.cloudwatch_full_access.arn
}
resource "aws_iam_user_policy_attachment" "neo_cloudwatch_read_only" {
count = var.give_neo_cloudwatch_full_access ? 0 : 1
user = aws_iam_user.example[0].name
policy_arn = aws_iam_policy.**cloudwatch_read_only**.arn
}
- .
- 위 코드에는 2개의 aws_iam_user_policy_attachment리소스가 포함되어 있습니다.
- 윗 부분은 전체 액세스 권한을 첨부하는 if절 이고, 아래는 읽기 전용 권한을 첨부하는 else 절입니다.
- 위 접근 방식은 테라폼 코드가 if 나 else 절을 알필요가 없습니다.
- 그러나 if 나 else 절에서 나오는 리소스의 출력 속성에 액세스 해야 하는 경우 어떻게 해야 할까요?
- 예를 들어 webserver-cluster 모듈에서 서로 다른 2개의 사용자 데이터 스크립트를 제공하고 사용자가 어느 스크립트를 실행할지 선택할 수 있게 하려면 어떻게 해야 할까요?
for_each 와 for 표현식
for_each 와 for 표현식을 사용한 조건문 Conditionals with for_each and for Expressions
- 이제 for_each 표현식으로 유사한 전략을 사용하여 조건 논리를 수행할 수 있습니다.
- for_each 표현식을 빈 컬렉션으로 전달하면 0개의 리소스 또는 0개의 인라인 블록을 생성합니다.
- 비어 있지 않은 컬렉션을 전달하면 하나 이상의 리소스 또는 인라인 블록을 만듭니다.
- 그렇다면 컬렉션이 비어 있는지 여부를 조건부로 어떻게 결정할 수 있을까요? ⇒ for_each 표현식과 for 표현식의 결합
- 예를 들어 modules/services/webserver-cluster/main.tf 의 webserver-cluster 모듈이 태그를 어떻게 설정하는지 다시 확인합니다.
dynamic "tag" {
for_each = var.custom_tags
content {
key = tag.key
value = tag.value
propagate_at_launch = true
}
}
- var.custom_tags 가 비어 있으면 for_each 표현식에는 반복할 항목이 없으므로 태그가 설정되지 않습니다.
- 다시 말해 여기에는 이미 몇 가지 조건 논리가 있습니다.
- 그러나 아래와 같이 for_each 표현식을 for 표현식과 결합하여 더 발전시킬 수 있습니다.
dynamic "tag" {
for_each = {
for key, value in var.custom_tags:
key => upper(value)
if key != "Name"
}
content {
key = tag.key
value = tag.value
propagate_at_launch = true
}
}
- 중첩된 for 표현식은 일관성을 위해 var.custom_tags 를 반복하며 각 값을 대문자로 변환하고 모듈이 이미 자체 Name 태그를 설정했으므로,
- for 표현식의 조건을 사용하여 key 집합을 Name 으로 필터링합니다.
- for 표현식에서 값을 필터링하여 임의 조건부 논리를 구현할 수 있습니다.
- 리소스의 복사본을 여러 개 만들 때는 count 보다 for_each 를 사용하는 것이 더 낫지만,
- 조건 논리의 경우 비어 있지 않은 컬렉션에 for_each 를 설정하는 것보다 count 를 0 또는 1로 설정하는 것이 간단합니다.
- 즉, 리소스를 조건부로 생성할 때는 count 를 사용할 수 있지만, 그 외 모든 유형의 반복문 또는 조건문에는 for_each 를 사용합니다.
if 문자열 지시자
if 문자열 지시자가 있는 조건문 Conditionals with the if String Directive & 실습7
- if 문자열 지시자를 살펴보겠습니다
%{ if <CONDITION> }<TRUEVAL>%{ endif }
- .
- CONDITION은 boolean 으로 평가되는 표현식이고, TRUEVAL은 CONDITION이 True로 평가되면 렌더링할 표현식입니다.
- 다음과 같이 else절을 선택적으로 포함할 수 있습니다.
%{ if <CONDITION> }<TRUEVAL>%{ else }<FALSEVAL>%{ endif }
- .
- FALSEVAL은 CONDITION이 false로 평가되면 렌더링할 표현식입니다.
- 예시 코드 입니다.
cat <<EOT > main.tf
variable "names" {
description = "Names to render"
type = list(string)
default = ["gasida", "akbun", "fullmoon"]
}
output "for_directive" {
value = "%{ for name in var.names }\${name}, %{ endfor }"
}
output "for_directive_index" {
value = "%{ for i, name in var.names }(\${i}) \${name}, %{ endfor }"
}
output "for_directive_index_if" {
value = <<EOF
%{ for i, name in var.names }
\${name}%{ if i < length(var.names) - 1 }, %{ endif }
%{ endfor }
EOF
}
EOT
- init & plan & apply
#
terraform init && terraform plan && terraform apply -auto-approve
Outputs:
for_directive = "gasida, akbun, fullmoon, "
for_directive_index = "(0) gasida, (1) akbun, (2) fullmoon, "
for_directive_index_if = <<EOT
gasida,
akbun,
fullmoon
EOT
# 확인
terraform output
- .
- 줄 바꿈 추가됨, 스페이스나 줄 바꿈 같은 공백을 없애기 위해 문자열 지시자의 앞이나 뒤에 물결표(~)를 사용할 수 있다.
- 문자열 지시자 시작에 공백이 있으며 지시자 앞에, 문자열 지시자 끝에 공객이 있으면 지시자 뒤에 물결표를 사용합니다.
cat <<EOT > main.tf
variable "names" {
description = "Names to render"
type = list(string)
default = ["gasida", "akbun", "fullmoon"]
}
output "for_directive" {
value = "%{ for name in var.names }\${name}, %{ endfor }"
}
output "for_directive_index" {
value = "%{ for i, name in var.names }(\${i}) \${name}, %{ endfor }"
}
output "for_directive_index_if" {
value = <<EOF
%{ for i, name in var.names }
\${name}%{ if i < length(var.names) - 1 }, %{ endif }
%{ endfor }
EOF
}
output "for_directive_index_if_strip" {
value = <<EOF
%{~ for i, name in var.names ~}
\${name}%{ if i < length(var.names) - 1 }, %{ endif }
%{~ endfor ~}
EOF
}
EOT
- .
- apply
#
terraform apply -auto-approve
for_directive_index_if = <<EOT
gasida,
akbun,
fullmoon
EOT
for_directive_index_if_strip = " gasida, akbun, fullmoon"
- else 를 활용하여 끝에 마침표(.)를 찍어보자
cat <<EOT > main.tf
variable "names" {
description = "Names to render"
type = list(string)
default = ["gasida", "akbun", "fullmoon"]
}
output "for_directive" {
value = "%{ for name in var.names }\${name}, %{ endfor }"
}
output "for_directive_index" {
value = "%{ for i, name in var.names }(\${i}) \${name}, %{ endfor }"
}
output "for_directive_index_if" {
value = <<EOF
%{ for i, name in var.names }
\${name}%{ if i < length(var.names) - 1 }, %{ endif }
%{ endfor }
EOF
}
output "for_directive_index_if_strip" {
value = <<EOF
%{~ for i, name in var.names ~}
\${name}%{ if i < length(var.names) - 1 }, %{ endif }
%{~ endfor ~}
EOF
}
output "for_directive_index_if_else_strip" {
value = <<EOF
%{~ for i, name in var.names ~}
\${name}%{ if i < length(var.names) - 1 }, %{ else }.%{ endif }
%{~ endfor ~}
EOF
}
EOT
#
terraform apply -auto-approve
...
for_directive_index_if_else_strip = " gasida, akbun, fullmoon"
for_directive_index_if_strip = " gasida, akbun, fullmoon"
- 실습 리소스 삭제
terraform destroy -auto-approve
rm <코드 파일 삭제>.tf