반복문
반복문을 통해 테라폼 구성 정의를 반복하지 않고 관리할 수 있다. (List 형태 값, key:value 형태)
- count : 정수 값만큼 리소스나 모듈을 생성
resource "local_file" "name" {
count = 5
content = "abc"
filename = "${path.module}/abc.txt"
}
output "filecontent" {
value = local_file.name.*.content
}
output "fileid" {
value = local_file.name.*.id
}
output "filename" {
value = local_file.name.*.filename
}
배포하면 정상적으로 배포된 것으로 보이지만 실제로는 파일이 한개 생겼다.
count는 부여된 인수를 기준으로 인덱스가 부여되는데 코드에서는 인덱스에 대한 할당이 없어,
파일이 1개만 생긴 것이다.
- count.index 추가
resource "local_file" "name" {
count = 5
content = "abc"
filename = "${path.module}/abc${count.index}.txt"
}
output "filecontent" {
value = local_file.name.*.content
}
output "fileid" {
value = local_file.name.*.id
}
output "filename" {
value = local_file.name.*.filename
}
count.index를 추가하고 배포하면, 각 인덱스 번호에 맞게 파일이 생성된 것을 확인할 수 있다.
count의 값은 정수가 되어야 하지만, 외부 변수를 사용하여 정수로 치환시켜 사용할 수 있다.
variable "names" {
type = list(string)
default = ["a", "b", "c"]
}
resource "local_file" "abc" {
count = length(var.names)
content = "abc"
# 변수 인덱스에 직접 접근
filename = "${path.module}/abc-${var.names[count.index]}.txt"
}
resource "local_file" "def" {
count = length(var.names)
content = local_file.abc[count.index].content
# element function 활용
filename = "${path.module}/def-${element(var.names, count.index)}.txt"
}
- count로 생성되는 리소스는 <리소스 타입>.<이름>.[<인덱스 번호>], 모듈의 경우 module.<모듈 이름>[<인덱스 번호>]로 리소스 값을 참조한다.
- provider 블록이 포함되어 있을 경우에는 count 사용이 불가능하다.
- element 는 리스트의 특정 위치의 요소를 반환하는 함수이며, 리스트와 인덱스를 인자로 받는다.
list와 다른 점은 범위를 초과하는 경우 순환 시켜 해당 위치를 찾아간다.
ex) [a,b,c] 인 경우 인자가 4라면 list에서는 오류가 발생하지만 element에서는 a를 찾아간다.
- 반복문 실습
- IAM 사용자 생성
- 단일 사용자 생성
provider "aws" {
region = "ap-northeast-2"
}
resource "aws_iam_user" "test" {
name = "ssungz"
}
- 범용 프로그래밍 for 구문 사용
for (i = 0; i < 3; i++) {
resource "aws_iam_user" "test" {
name = "ssungz"
}
}
테라폼은 for 반복문 또는 언어에 내장된 절차가 없어 구문 배포 시 오류 발생 (동작 불가능)
- Count를 사용한 반복문 처리
맨 위의 실습과 동일하게 index 처리가 되지 않아, 계정 중복으로 오류가 발생한다.
provider "aws" {
region = "ap-northeast-2"
}
resource "aws_iam_user" "test" {
count = 3
name = "ssungz"
}
- Count.index를 사용하여 iam 계정 생성
provider "aws" {
region = "ap-northeast-2"
}
resource "aws_iam_user" "test" {
count = 3
name = "ssungz.${count.index}"
}
index 번호에 맞게 iam user가 생성
- 입력 변수를 통해 IAM 사용자 생성
1) 입력 변수 코드 생성
# variable.tf
variable "user_names" {
description = "Create IAM Users"
type = list(string)
default = ["ssungz", "won", "hoon"]
}
# iam.tf
provider "aws" {
region = "ap-northeast-2"
}
resource "aws_iam_user" "ssungz-iam" {
count = length(var.user_names)
name = var.user_names[count.index]
}
테라폼에서 count 와 함께 배열 조회 구문과 length 함수를 사용해서 사용자 생성 가능
- 배열 조회 구문
- ARRAY[<INDEX>]
- var.user_names 의 인데스 1에서 요소를 찾는 방법
var.user_names[1]
- length(내장) 함수
- length(<ARRAY>)
- 주어진 ARRAY의 항목 수를 반환하는 함수, 문자열 및 맵을 대상으로도 동작
- 배포 확인
- 유저의 arn을 output 출력
# 첫번째 유저 arn 출력 케이스
output "first_arn" {
value = aws_iam_user.ssungz-iam[0].arn
description = "first user arn"
}
# 전체 유저 인덱스 출력 케이스
output "all_arns" {
value = aws_iam_user.ssungz-iam[*].arn
description = "all users arn"
}
# 인덱스 0~1 유저 arn 출력 케이스
output "select_arns" {
value = slice(aws_iam_user.ssungz-iam[*].arn, 0, 2)
description = "select users arn"
}
# 인덱스 0번 2번 arn 출력 케이스
output "select_arns2" {
value = [
element(aws_iam_user.ssungz-iam[*].arn, 0),
element(aws_iam_user.ssungz-iam[*].arn, 2)
]
}
- count + variable 실습
실습 목적 : 요청에 따라 terraform 코드를 고도화하고, 장애 상황을 재현
(악분님 유튜브 : https://youtu.be/enhSdIJ9xxQ)
1) 실습 vpc 환경 생성
git clone https://github.com/sungwook-practice/t101-study.git
cd t101-study/count_vs_foreach
cat Readme.md
tree -L 1
cd template
ls
cat terraform.tfvars
terraform init && terraform plan && terraform apply -auto-approve
terraform state list
2) subnet 생성 (첫 템플릿 코드에는 subnet 생성 구문이 없기 때문에 생성을 위해 구문을 수정해준다.)
# main.tf
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
tags = {
Name = "terraform VPC"
}
}
# 코드 변경의 편리를 위해 cidr 변수 처리
resource "aws_subnet" "main" {
vpc_id = aws_vpc.main.id
cidr_block = var.subnet_cidr
}
output "ssungzvpc_id" {
value = aws_vpc.main.id
}
# variable.tf
variable "vpc_cidr" {
type = string
}
variable "subnet_cidr" {
type = string
}
# terraform.tfvars
vpc_cidr = "192.168.0.0/16"
subnet_cidr = "192.168.1.0/24"
수정 된 코드 배포 후 생성된 subnet 확인
3) subnet cidr 변수 값을 여러 개 입력 받아 1개 이상의 subnet 생성 (count 반복 사용)
# variables.tf 수정
variable "vpc_cidr" {
type = string
}
# count 사용을 위해 list 선언
variable "subnet_cidr" {
type = list(string)
}
# terraform.tfvars 수정
vpc_cidr = "192.168.0.0/16"
# variables.tf에서 list로 읽을 수 있도록 subnet cidr 추가하여 list로 묶음
subnet_cidr = ["192.168.1.0/24", "192.168.2.0/24"]
# main.tf 수정
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
tags = {
Name = "terraform VPC"
}
}
resource "aws_subnet" "main" {
# 반복문을 수행할 수 있도록 count 구문 선언
count = length(var.subnet_cidr)
vpc_id = aws_vpc.main.id
# element 함수를 통해 index 번호에 해당하는 cidr 참조
cidr_block = element(var.subnet_cidr, count.index)
}
output "ssungzvpc_id" {
value = aws_vpc.main.id
}
배포 후 subnet 생성 확인
a) terraform state list
b) aws cli 확인
$ aws ec2 describe-subnets --filters "Name=vpc-id,Values=$(terraform output -raw ssungzvpc_id)" --query "Subnets[*].{ID:SubnetId,CIDR:CidrBlock,AZ:AvailabilityZone}"
4) subnet이 배치되는 AZ 설정 (AZ는 변수 입력)
# variabels.tf 수정
variable "vpc_cidr" {
type = string
}
variable "subnet_cidr" {
type = list(string)
}
variable "subnet_az" {
type = list(string)
}
# terraform.tfvars 수정
vpc_cidr = "192.168.0.0/16"
subnet_cidr = ["192.168.1.0/24", "192.168.2.0/24"]
subnet_az = [ "ap-northeast-2a", "ap-northeast-2c" ]
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
tags = {
Name = "terraform VPC"
}
}
resource "aws_subnet" "main" {
count = length(var.subnet_cidr)
vpc_id = aws_vpc.main.id
cidr_block = element(var.subnet_cidr, count.index)
availability_zone = element(var.subnet_az, count.index)
}
output "ssungzvpc_id" {
value = aws_vpc.main.id
}
배포 후 az 생성 확인
a) terraform console
b) aws cli
$ aws ec2 describe-subnets --filters "Name=vpc-id,Values=$(terraform output -raw ssungzvpc_id)" --query "Subnets[*].{ID:SubnetId,CIDR:CidrBlock,AZ:AvailabilityZone,Name:Tags[?Key=='Name']|[0].Value}" --output table
5) 생성된 subnet이 늘어날 수록 구분이 어려우므로 vpc tag 값 추가 (실습 생략)
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
tags = {
Name = "terraform VPC"
}
}
resource "aws_subnet" "main" {
count = length(var.subnet_cidr)
vpc_id = aws_vpc.main.id
cidr_block = element(var.subnet_cidr, count.index)
availability_zone = element(var.subnet_az, count.index)
tags = {
# count.index를 통해 terraform vpc-0, terraform vpc-1 tag 설정
Name = "terraform VPC-${count.index}"
}
}
output "myvpc_id" {
value = aws_vpc.main.id
}
6) 각 subnet에 tag 설정
# variabels.tf
variable "vpc_cidr" {
type = string
}
variable "subnet_cidr" {
type = list(string)
}
variable "subnet_az" {
type = list(string)
}
# subnet_tag 변수 추가
variable "subnet_tag" {
type = list(map(string))
}
# terraform.tfvars
vpc_cidr = "192.168.0.0/16"
subnet_cidr = ["192.168.1.0/24", "192.168.2.0/24"]
subnet_az = [ "ap-northeast-2a", "ap-northeast-2c" ]
# variables.tf에 변수가 list,map으로 선언되었으므로 선언에 맞춰 변수 추가
subnet_tag = [ {
Name = "public-subnet"
Environment = "Dev"
},
{
Name = "private-subnet"
Environment = "dev"
}
]
# main.tf
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
tags = {
Name = "terraform VPC"
}
}
resource "aws_subnet" "main" {
count = length(var.subnet_cidr)
vpc_id = aws_vpc.main.id
cidr_block = element(var.subnet_cidr, count.index)
availability_zone = element(var.subnet_az, count.index)
# element 함수를 사용해 tag 값 참조
tags = element(var.subnet_tag, count.index)
}
output "ssungzvpc_id" {
value = aws_vpc.main.id
}
배포 후 subnet_tag 값 확인
a) terraform console
b) aws cli
- subnet 의 추가 삭제가 용이하도록 하나의 변수로 설정하는 리팩토링
변수 | 타입 정의 | 타입 보장 | 유효성 검사 |
object | key의 이름과 타입을 명시적 정의 | value 고정으로 안정성 높음 | key / value에 대한 유효성 검사 |
map | 동일한 type의 value로 구성 (ex. map(string) ) |
동적으로 유연하게 관리할 수 있으나 안정성 낮음 | 개별 유효성 검사 하지 않음 map 전체 유효성 조건 적용 가능 |
# variables.tf
variable "vpc_cidr" {
type = string
}
variable "subnets" {
type = list(object({
cidr = string
az = string
tags = map(string)
}))
}
# terraform.tfvar 수정
vpc_cidr = "192.168.0.0/16"
subnets = [
{
cidr = "192.168.1.0/24",
az = "ap-northeast-2a",
tags = {
Name = "public-subnet"
Environment = "dev"
}
},
{
cidr = "192.168.2.0/24",
az = "ap-northeast-2c",
tags = {
Name = "private-subnet"
Environment = "dev"
}
}
]
# main.tf
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
tags = {
Name = "terraform VPC"
}
}
resource "aws_subnet" "main" {
count = length(var.subnets)
vpc_id = aws_vpc.main.id
cidr_block = var.subnets[count.index].cidr
availability_zone = var.subnets[count.index].az
tags = var.subnets[count.index].tags
}
output "ssungzvpc_id" {
value = aws_vpc.main.id
}
배포 후 subnet_tag 값 확인
a) terraform console
b) aws cli
7) 미사용 subnet 삭제 후, ec2 교체된 장애 재현
- 실습 환경 배포
# 코드 파일 확인
cd ..
cd step5_count_invalid_example
ls
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
tags = {
Name = "terraform VPC"
}
}
resource "aws_subnet" "main" {
count = length(var.subnets)
vpc_id = aws_vpc.main.id
cidr_block = var.subnets[count.index].cidr
availability_zone = var.subnets[count.index].az
tags = var.subnets[count.index].tags
}
resource "aws_instance" "server" {
ami = "ami-0e8bd0820b6e1360b"
instance_type = "t4g.nano"
subnet_id = aws_subnet.main[1].id
# index 접근 방법 오류 해결 코드
# subnet_id = index(aws_subnet.main.*.cidr_block, "192.168.2.0/24")
tags = {
Name = "Terraform demo"
}
}
output "myvpc_id" {
value = aws_vpc.main.id
}
[장애 재현] terraform.tfvars 수정
vpc_cidr = "192.168.0.0/16"
subnets = [
# (유투브 7번째 시나리오) terrafprm apply 이 후, 첫 번째 요소를 주석하세요
# cidr = "192.168.1.0/24",
#{
# az = "ap-northeast-2a",
# tags = {
# Name = "public-subnet"
# Environment = "dev"
# }
#},
# (유투브 8번째 시나리오) terrafprm apply 이 후, 주석을 해제하고 terraform apply해보세요
# {
# cidr = "192.168.5.0/24",
# az = "ap-northeast-2a",
# tags = {
# Name = "public-subnet"
# Environment = "dev"
# }
# },
{
cidr = "192.168.2.0/24",
az = "ap-northeast-2a",
tags = {
Name = "private-subnet"
Environment = "dev"
}
},
{
cidr = "192.168.3.0/24",
az = "ap-northeast-2a",
tags = {
Name = "public-subnet"
Environment = "dev"
}
},
{
cidr = "192.168.4.0/24",
az = "ap-northeast-2c",
tags = {
Name = "private-subnet"
Environment = "dev"
}
}
]
subnet 하나를 삭제하기 위해 주석을 하고 plan을 수행했으나,
ec2가 재생성되고, 대상이 아닌 subnet도 재생성된다고 나오고 있다.
이 오류는 리소스 생성 시 변수 배열의 index 값을 참조하고 있어서이다.
count가 참조하는 변수 배열 내 자원을 삭제하면 list의 인덱스 값이 한칸씩 당겨지기 때문에 count가 참조하는 index 값이 모두 바뀌고,
리소스는 계속 인덱스를 참조하고 있으므로 replace가 이뤄진다.
테라폼 docs에서도 이럴 경우에는 고유한 값을 관리하기 위해서는 for_each를 사용하는게 좋다고 나와있다.
'Cloud > Terraform' 카테고리의 다른 글
[T101] Terraform 101 Study 3주차 (2) (0) | 2024.06.30 |
---|---|
[T101] Terraform 101 Study 3주차 (1) (0) | 2024.06.26 |
[T101] Terraform 101 Study 2주차 (2) (0) | 2024.06.21 |
[T101] Terraform 101 Study 2주차 (1) (0) | 2024.06.18 |
[T101] Terraform 101 Study 1주차 (3) (0) | 2024.06.15 |