🍀 Cloud Architect/IaC

[Terraform] 테라폼 데이터 관리

나리 집사 2024. 6. 22. 23:47
‘테라폼으로 시작하는 IaC’ 책을 기준하여 정리하였습니다.

3.5 데이터 소스

데이터 소스 구성

데이터 소스는 테라폼으로 정의되지 않은 외부 리소스 또는 저장된 정보를 테라폼 내에서 참조할 때 사용한다

데이터 소스 블록은 data로 시작하고 이후' 데이터 소스 유형'을 정의한다.

 

데이터 소스를 정의할 때 사용 가능한 메타인수는 다음과 같다.

  • depends_on : 종속성을 선언하며, 선언된 구성요소와의 생성 시점에 대해 정의
  • count : 선언된 개수에 따라 여러 리소스를 생성
  • for_each : map 또는 set 타입의 데이터 배열의 값을 기준으로 여러 리소스를 생성
  • lifecycle : 리소스의 수명주기 관리

데이터 소스 참조

데이터 소스로 읽은 대상을 참조하는 방식은 리소스와 구별되게 data가 앞에 붙는다. 속성 값은 다음과 같이 접근할 수 있다.

# Terraform Code
data "<리소스 유형>" "<이름>" {
  <인수> = <값>
}

# 데이터 소스 참조
data.<리소스 유형>.<이름>.<속성>
# Declare the data source
data "aws_availability_zones" "available" {
  state = "available"
}

resource "aws_subnet" "primary" {
  availability_zone = data.aws_availability_zones.available.names[0]
  # e.g. ap-northeast-2a
}

resource "aws_subnet" "secondary" {
  availability_zone = data.aws_availability_zones.available.names[1]
  # e.g. ap-northeast-2b
}

데이터 가져오기 실습

resource "local_file" "abc" {
  content  = "123!"
  filename = "${path.module}/abc.txt"
}

data "local_file" "abc" {
  filename = local_file.abc.filename
}

resource "local_file" "def" {
  content  = data.local_file.abc.content
  filename = "${path.module}/def.txt"
}
# 적용
$ terraform apply -auto-approve
$ terraform state list

# 파일 확인
$ ls *.txt
$ diff abc.txt def.txt

# graph 확인
$ terraform graph > graph.dot

# 테라폼 콘솔 : 데이터 소스 참조 확인
$ echo "data.local_file.abc.content" | terraform console

 VPC 생성 실습

# vpc
data "aws_availability_zones" "available" {
  state = "available"
}

resource "aws_vpc" "main_vpc" {
  cidr_block       = "10.10.0.0/16"
  enable_dns_hostnames = true
  enable_dns_support =  true

  tags = {
    Name = "main_vcp"
  }
}

resource "aws_subnet" "subnet_primary" {
  vpc_id = resource.aws_vpc.main_vpc.id
  availability_zone = data.aws_availability_zones.available.names[0]
  cidr_block = "10.10.1.0/24"
}

resource "aws_subnet" "subnet_secondary" {
  vpc_id = resource.aws_vpc.main_vpc.id
  availability_zone = data.aws_availability_zones.available.names[1]
  cidr_block = "10.10.2.0/24"
}

 

3.6 입력 변수

입력 변수는 인프라를 구성하는 데 필요한 속성 값을 정의해 코드의 변경 없이 여러 인프라를 생성하는 데 목적이 있다.

테라폼에서는 이것을 입력 변수 Input Variables 로 정의한다.

변수의 선언 방식

변수는 variable로 시작되는 블록으로 구성된다. 변수 블록 뒤의 이름 값은 동일 모듈 내 모든 변수 선언에서 고유해야 하며, 이 이름으로 다른 코드 내에서 참조된다.

  • 변수 정의 시 사용 가능한 메타인수
    • default : 변수 값을 전달하는 여러 가지 방법을 지정하지 않으면 기본값이 전달됨, 기본값이 없으면 대화식으로 사용자에게 변수에 대한 정보를 물어봄
    • type : 변수에 허용되는 값 유형 정의, string number bool list map set object tuple 와 유형을 지정하지 않으면 any 유형으로 간주
    • description : 입력 변수의 설명
    • validation : 변수 선언의 제약조건을 추가해 유효성 검사 규칙을 정의 - 링크
    • sensitive : 민감한 변수 값임을 알리고 테라폼의 출력문에서 값 노출을 제한 (암호 등 민감 데이터의 경우) - 링크
    • nullable : 변수에 값이 없어도 됨을 지정 - Link
# variable 블록 선언의 예
variable "<이름>" {
 <인수> = <값>
}

variable "image_id" {
 type = string
}

변수의 유형

  • 기본 유형
    • string : 글자 유형
    • number : 숫자 유형
    • bool : true 또는 false
    • any : 명시적으로 모든 유형이 허용됨을 표시
  • 집합 유형
    • list (<유형>): 인덱스 기반 집합
    • map (<유형>): 값 = 속성 기반 집합이며 키값 기준 정렬
    • set (<유형>): 값 기반 집합이며 정렬 키값 기준 정렬
    • object ({<인수 이름>=<유형>, …})
    • tuple ([<유형>, …])
    • list와 set은 선언하는 형태가 비슷하지만 참조 방식이 인덱스와 키로 각각 차이가 있고, map와 set의 경우 선언된 값이 정렬되는 특징을 가진다.
  • 변수 유형 별 선언
variable "string" {
  type        = string
  description = "var String"
  default     = "myString"
}

variable "number" {
  type    = number
  default = 123
}

variable "boolean" {
  default = true
}

variable "list" {
  default = [
    "google",
    "vmware",
    "amazon",
    "microsoft"
  ]
}

output "list_index_0" {
  value = var.list.0
}

output "list_all" {
  value = [
    for name in var.list : upper(name)
  ]
}

variable "map" { # Sorting
  default = {
    aws   = "amazon",
    azure = "microsoft",
    gcp   = "google"
  }
}

variable "set" { # Sorting
  type = set(string)
  default = [
    "google",
    "vmware",
    "amazon",
    "microsoft"
  ]
}

variable "object" {
  type = object({ name = string, age = number })
  default = {
    name = "abc"
    age  = 12
  }
}

variable "tuple" {
  type    = tuple([string, number, bool])
  default = ["abc", 123, true]
}

variable "ingress_rules" { # optional ( >= terraform 1.3.0)
  type = list(object({
    port        = number,
    description = optional(string),
    protocol    = optional(string, "tcp"),
  }))
  default = [
    { port = 80, description = "web" },
  { port = 53, protocol = "udp" }]
}

유효성 검사

  • 변수 블록 내에 validation 블록에서 조건인 condition에 지정되는 규칙이 true 또는 false를 반환해야 하며, error_message는 condition 값의 결과가 false 인 경우 출력되는 메시지를 정의한다.
  • regex 함수는 대상의 문자열에 정규식을 적용하고 일치하는 문자열을 반환하는데, 여기에 can 함수를 함께 사용하면 정규식에 일치하지 않는 경우의 오류 검출한다.
  • validation 블록은 중복으로 선언할 수 있다.
  • 유효성 검사 예제
# main.tf
variable "image_id" {
  type        = string
  description = "The id of the machine image (AMI) to use for the server."

  validation {
    condition     = length(var.image_id) > 4
    error_message = "The image_id value must exceed 4."
  }

  validation {
    # regex(...) fails if it cannot find a match
    condition     = can(regex("^ami-", var.image_id))
    error_message = "The image_id value must starting with \"ami-\"."
  }
}
  • 수행 결과
#
terraform apply -auto-approve
var.image_id
  The id of the machine image (AMI) to use for the server.

  Enter a value: ami
...

#
terraform apply -auto-approve
var.image_id
  The id of the machine image (AMI) to use for the server.

  Enter a value: ami-
...


#
terraform apply -auto-approve
var.image_id
  The id of the machine image (AMI) to use for the server.

  Enter a value: ami-12345678
...

terraform apply -auto-approve

변수의 참조

variable은 코드 내에서 var.<이름>으로 참조된다.

  • main.tf
variable "my_password" {}

resource "local_file" "abc" {
  content  = var.my_password
  filename = "${path.module}/abc.txt"
}
  • 실행 
#
terraform init -upgrade && terraform apply -auto-approve
var.my_password
  Enter a value: qwe123
...

# 확인
terraform state list
terraform state show local_file.abc
cat abc.txt ; echo

# 해당 파일에 다른 내용으로 변경해보기
terraform apply -auto-approve
var.my_password
  Enter a value: t101mypss
...

# 확인
cat abc.txt ; echo

민감한 변수 취급

입력 변수의 민감 여부 선언 가능

  • main.tf
variable "my_password" {
  default   = "password"
  sensitive = true
}

resource "local_file" "abc" {
  content  = var.my_password
  filename = "${path.module}/abc.txt"
}
  • 수행 결과
# 출력부분에 내용 안보임!
terraform apply -auto-approve
...
 ~ content              = (sensitive value)
...

terraform state show local_file.abc
echo "local_file.abc.content" | terraform console
(sensitive value)

# 결과물 파일 확인
cat abc.txt ; echo

# terraform.tfstate 파일 확인 : VSCODE에서 terraform.tfstate 클릭 후 확인
cat terraform.tfstate | grep '"content":'
            "content": "password",

변수 입력 방식과 우선순위

  • variable의 목적은 코드 내용을 수정하지 않고 테라폼의 모듈적 특성을 통해 입력되는 변수로 재사용성을 높이는 데 있다.
  • 특히 입력 변수라는 명칭에 맞게 사용자는 프로비저닝 실행 시에 원하는 값으로 변수에 정의할 수 있다.
  • 선언되는 방식에 따라 변수의 우선순위가 있으므로, 이를 적절히 사용해 로컬 환경과 빌드 서버 환경에서의 정의를 다르게 하거나, 프로비저닝 파이프라인을 구성하는 경우 외부 값을 변수에 지정할 수 있다.
  • [우선순위 수준]의 숫자가 작을수록 우선순위도 낮다.

VPC & 보안그룹 & EC2 배포 실습

# vpc.tf
provider "aws" {
  region  = "ap-northeast-2"
}

resource "aws_vpc" "myvpc" {
  cidr_block       = "10.10.0.0/16"
  enable_dns_support   = true
  enable_dns_hostnames = true

  tags = {
    Name = "t101-study"
  }
}

resource "aws_subnet" "mysubnet1" {
  vpc_id     = aws_vpc.myvpc.id
  cidr_block = "10.10.1.0/24"

  availability_zone = "ap-northeast-2a"

  tags = {
    Name = "t101-subnet1"
  }
}

resource "aws_subnet" "mysubnet2" {
  vpc_id     = aws_vpc.myvpc.id
  cidr_block = "10.10.2.0/24"

  availability_zone = "ap-northeast-2c"

  tags = {
    Name = "t101-subnet2"
  }
}


resource "aws_internet_gateway" "myigw" {
  vpc_id = aws_vpc.myvpc.id

  tags = {
    Name = "t101-igw"
  }
}

resource "aws_route_table" "myrt" {
  vpc_id = aws_vpc.myvpc.id

  tags = {
    Name = "t101-rt"
  }
}

resource "aws_route_table_association" "myrtassociation1" {
  subnet_id      = aws_subnet.mysubnet1.id
  route_table_id = aws_route_table.myrt.id
}

resource "aws_route_table_association" "myrtassociation2" {
  subnet_id      = aws_subnet.mysubnet2.id
  route_table_id = aws_route_table.myrt.id
}

resource "aws_route" "mydefaultroute" {
  route_table_id         = aws_route_table.myrt.id
  destination_cidr_block = "0.0.0.0/0"
  gateway_id             = aws_internet_gateway.myigw.id
}

output "aws_vpc_id" {
  value = aws_vpc.myvpc.id
}

# sg.tf
resource "aws_security_group" "mysg" {
  vpc_id      = aws_vpc.myvpc.id
  name        = "T101 SG"
  description = "T101 Study SG"
}

resource "aws_security_group_rule" "mysginbound" {
  type              = "ingress"
  from_port         = 80
  to_port           = 80
  protocol          = "tcp"
  cidr_blocks       = ["0.0.0.0/0"]
  security_group_id = aws_security_group.**mysg**.id
}

resource "aws_security_group_rule" "mysgoutbound" {
  type              = "egress"
  from_port         = 0
  to_port           = 0
  protocol          = "-1"
  cidr_blocks       = ["0.0.0.0/0"]
  security_group_id = aws_security_group.mysg.id
}

output "aws_security_group_id" {
  value       = aws_security_group.mysg.id
}

# EC2.tf
data "aws_ami" "my_amazonlinux2" {
  most_recent = true
  filter {
    name   = "owner-alias"
    values = ["amazon"]
  }

  filter {
    name   = "name"
    values = ["amzn2-ami-hvm-*-x86_64-ebs"]
  }

  owners = ["amazon"]
}

resource "aws_instance" "myec2" {

  depends_on = [
    aws_internet_gateway.myigw
  ]

  ami                         = data.aws_ami.my_amazonlinux2.id
  associate_public_ip_address = true
  instance_type               = "t2.micro"
  vpc_security_group_ids      = ["${aws_security_group.mysg.id}"]
  subnet_id                   = aws_subnet.mysubnet1.id

  user_data = <<-EOF
              #!/bin/bash
              wget https://busybox.net/downloads/binaries/1.31.0-defconfig-multiarch-musl/busybox-x86_64
              mv busybox-x86_64 busybox
              chmod +x busybox
              RZAZ=$(curl http://169.254.169.254/latest/meta-data/placement/availability-zone-id)
              IID=$(curl 169.254.169.254/latest/meta-data/instance-id)
              LIP=$(curl 169.254.169.254/latest/meta-data/local-ipv4)
              echo "<h1>RegionAz($RZAZ) : Instance ID($IID) : Private IP($LIP) : Web Server</h1>" > index.html
              nohup ./busybox httpd -f -p 80 &
              EOF

  user_data_replace_on_change = true

  tags = {
    Name = "t101-myec2"
  }
}

output "myec2_public_ip" {
  value       = aws_instance.myec2.public_ip
  description = "The public IP of the Instance"
}
# 적용
ls *.tf
terraform plan && terraform apply -auto-approve
terraform state list
data.aws_ami.my_amazonlinux2
aws_instance.myec2
...

terraform state show data.aws_ami.my_amazonlinux2
terraform state show aws_instance.myec2

# 데이터소스 값 확인
terraform console
> 
data.aws_ami.my_amazonlinux2
data.aws_ami.my_amazonlinux2.id
"ami-01c81850a6167bb81"
data.aws_ami.my_amazonlinux2.image_id
data.aws_ami.my_amazonlinux2.name
data.aws_ami.my_amazonlinux2.owners
data.aws_ami.my_amazonlinux2.platform_details
data.aws_ami.my_amazonlinux2.hypervisor
data.aws_ami.my_amazonlinux2.architecture
exit

# graph 확인 > graph.dot 파일 선택 후 오른쪽 상단 DOT 클릭
terraform graph > graph.dot

# 출력된 EC2 퍼블릭IP로 cul 접속 확인
terraform output -raw myec2_public_ip
52.79.154.3

MYIP=$(terraform output -raw myec2_public_ip)
while true; do curl --connect-timeout 1  http://$MYIP/ ; echo "------------------------------"; date; sleep 1; done

# 삭제
terraform destroy -auto-approve

 

3.7 local 지역 값

코드 내에서 사용자가 지정한 값 또는 속성 값을 가공해 참조 가능한 local (지역 값)은 외부에서 입력되지 않고, 코드 내에서만 가공되어 동작하는 값을 선언한다.

‘local’은 입력 변수와 달리 선언된 모듈 내에서만 접근 가능하고, 변수처럼 실행 시에 입력받을 수 없다.

로컬은 사용자가 테라폼 코드를 구현할 때 이나 표현식을 반복적으로 사용할 수 있는 편의를 제공한다. 하지만 빈번하게 여러 곳에서 사용되는 경우 실제 값에 대한 추적이 어려워져 유지 관리 측면에서 부담이 발생할 수 있으므로 주의해야 한다.

local의 선언과 참조 실습

  • 로컬이 선언되는 블록은 locals로 시작한다. 선언되는 인수에 표현되는 값은 상수만이 아닌 리소스의 속성, 변수의 값들도 조합해 정의할 수 있다.
    • 동일한 tf 파일 내에서 여러 번 선언하는 것도 가능하고 여러 파일에 걸쳐 만드는 것도 가능하다.
    • 다만 locals에 선언한 로컬 변수 이름은 전체 루트 모듈 내에서 유일해야 한다.
    • 정의되는 속성 값은 지정된 값의 형태에 따라 다양한 유형으로 정의할 수 있다.
  • 선언된 local 값은 local.<이름>으로 참조할 수 있다.
    • 테라폼 구성 파일을 여러 개 생성해 작업하는 경우 서로 다른 파일에 선언되어 있더라도 다른 파일에서 참조할 수 있다.
# main.tf
variable "prefix" {
  default = "hello"
}

locals {
  name    = "terraform"
}

resource "local_file" "abc" {
  content  = local.content
  filename = "${path.module}/abc.txt"
}

# sub.tf
locals {
  content = "${var.prefix} ${local.name}"
}
ls *.tf
terraform init -upgrade
terraform apply -auto-approve

terraform state list
terraform state show local_file.abc

echo "local.content" | terraform console
"hello terraform"

이 예제에서는 서로 다른 테라폼 구성 파일에서도 로컬 값을 참조할 수 있다는 가능성을 확인할 수 있지만, 관리 측면에서는 서로 참조하는 로컬값이 파편화되어 유지 보수가 어려워질 수 있으므로 주의가 필요하다.

 

local 사용 사례

로컬 변수가 많아질 경우 구조화된 로컬 변수, 별도의 파일로 분리

  • 구조화된 로컬 변수: 로컬 변수를 구조화하여 관련된 값들을 그룹화
locals {
  app_config = {
    instance_type = "t2.micro"
    ami_id        = "ami-12345678"
    tags = {
      environment = "production"
      team        = "devops"
    }
  }
}

resource "aws_instance" "example" {
  ami           = local.app_config.ami_id
  instance_type = local.app_config.instance_type

  tags = local.app_config.tags
}
  • 별도의 파일로 분리: 로컬 변수가 많아질 경우 별도의 파일로 분리하여 관리. 예를 들어, locals.tf 파일을 만들어 모든 로컬 변수를 그 파일에 정의할 수 있다.
# locals.tf
locals {
  instance_type = "t2.micro"
  ami_id        = "ami-12345678"
  common_tags = {
    environment = "production"
    team        = "devops"
  }
}

# main.tf
resource "aws_instance" "example" {
  ami           = local.ami_id
  instance_type = local.instance_type

  tags = local.common_tags
}
  • 의미 있는 네이밍: 로컬 변수의 이름을 명확하고 의미 있게 지어 코드의 가독성을 높인다.
locals {
  database_instance_type = "db.t2.micro"
  web_server_instance_type = "t2.micro"
}

resource "aws_instance" "db_instance" {
  instance_type = local.database_instance_type
  // 기타 설정
}

resource "aws_instance" "web_server" {
  instance_type = local.web_server_instance_type
  // 기타 설정
}
  • 모듈화: 공통으로 사용되는 설정이나 변수를 모듈화하여 재사용합니다. 이를 통해 코드의 중복을 줄이고 유지보수성을 높일 수 있다.
locals {
  database_instance_type = "db.t2.micro"
  web_server_instance_type = "t2.micro"
}

resource "aws_instance" "db_instance" {
  instance_type = local.database_instance_type
  // 기타 설정
}

resource "aws_instance" "web_server" {
  instance_type = local.web_server_instance_type
  // 기타 설정
}

AWS IAM User 생성

# iamuser.tf
provider "aws" {
  region = "ap-northeast-2"
}

locals {
  name = "mytest"
  team = {
    group = "dev"
  }
}

resource "aws_iam_user" "myiamuser1" {
  name = "${local.name}1"
  tags = local.team
}

resource "aws_iam_user" "myiamuser2" {
  name = "${local.name}2"
  tags = local.team
}
# 적용
terraform init && terraform apply -auto-approve
terraform state list
terraform state show aws_iam_user.myiamuser1
terraform state show aws_iam_user.myiamuser2

# graph 확인 > graph.dot 파일 선택 후 오른쪽 상단 DOT 클릭
terraform graph > graph.dot

# iam 사용자 리스트 확인
aws iam list-users | jq

# 삭제
terraform destroy -auto-approve -target=aws_iam_user.myiamuser1
terraform state list
terraform destroy -auto-approve
terraform state list

 

3.8 출력 output

출력 값은 주로 테라폼 코드의 프로비저닝 수행 후의 결과 속성 값을 확인하는 용도로 사용된다.

또한 프로그래밍 언어에서 코드 내 요소 간에 제한된 노출을 지원하듯, 테라폼 모듈 간, 워크스페이스 간 데이터 접근 요소로도 활용할 수 있다.

  • 루트 모듈에서 사용자가 확인하고자 하는 특정 속성 출력
  • 자식 모듈의 특정 값을 정의하고 루트 모듈에서 결과를 참조
  • 서로 다른 루트 모듈의 결과를 원격으로 읽기 위한 접근 요소
output "instance_ip_addr" {
  value = "http://${aws_instance.server.private_ip}"
}
  • 출력되는 값은 value의 값이며 테라폼이 제공하는 조합과 프로그래밍적인 기능들에 의해 원하는 값을 출력할 수 있다.
  • 주의할 점은 output 결과에서 리소스 생성 후 결정되는 속성 값은 프로비저닝이 완료되어야 최종적으로 결과를 확인할 수 있고 terraform plan 단계에서는 적용될 값이 출력하지 않는다는 것이다.
  • 변수 정의 시 사용 가능한 메타인수는 다음과 같다.
    • description : 출력 값 설명
    • sensitive : 민감한 출력 값임을 알리고 테라폼의 출력문에서 값 노출을 제한
    • depends_on : value에 담길 값이 특정 구성에 종속성이 있는 경우 생성되는 순서를 임의로 조정
    • precondition : 출력 전에 지정된 조건을 검증

output 선언과 활용 실습

# main.tf
resource "local_file" "abc" {
  content  = "abc123"
  filename = "${path.module}/abc.txt"
}

output "file_id" {
  value = local_file.abc.id
}

output "file_abspath" {
  value = abspath(local_file.abc.filename)
}
# plan 실행 시, 이미 정해진 속성은 출력을 예측하지만 아직 생성되지 않은 file id는 예측 불가
terraform init && terraform plan
...
Changes to Outputs:
  + file_abspath = "/Users/gasida/Downloads/workspaces/3.8/abc.txt"
  + file_id      = (known after apply)


# 적용
terraform apply -auto-approve
...
Outputs:
file_abspath = "/Users/gasida/Downloads/workspaces/3.8/abc.txt"
file_id = "6367c48dd193d56ea7b0baad25b19455e529f5ee"

# graph 확인 > graph.dot 파일 선택 후 오른쪽 상단 DOT 클릭
terraform graph > graph.dot

# 파일 경로 비교 확인
terraform state list
terraform state show local_file.abc
echo "local_file.abc" | terraform console
echo "local_file.abc.filename" | terraform console
terraform output -raw file_abspath ; echo

 

3.9 반복문

list 형태의 값 목록이나 Key-Value 형태의 문자열 집합인 데이터가 있는 경우 동일한 내용에 대해 테라폼 구성 정의를 반복적으로 하지 않고 관리할 수 있다.

count 반복문 실습

반복문에 지정된 정수 값 만큼 반복해서 리소스나 모듈을 생성한다. count는 정수를 명시하기도 하지만 리스트를 활용해서 반복문의 동작을 구성 할 수 있다.

# 확인
terraform init && terraform apply -auto-approve
terraform state list
echo "local_file.abc" | terraform console
echo "local_file.abc[0]" | terraform console
echo "local_file.abc[4]" | terraform console
terraform state show 'local_file.abc[0]'
terraform state show 'local_file.abc[4]'
ls *.txt

# output
terraform output
terraform output filename
terraform output fileid
terraform output filecontent

 

1) count 반복문 활용

  • 리소스 또는 모듈 블록에 count 값이 정수인 인수가 포함된 경우 선언된 정수 값만큼 리소스나 모듈을 생성하게 된다.
  • count에서 생성되는 참조값은 count.index이며, 반복하는 경우 0부터 1씩 증가해 인덱스가 부여된다.
# main.tf
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
  filename = "${path.module}/def-${var.names[count.index]}.txt"
}

결과:  5개의 파일이 생성되어야 하지만 파일명이 동일하여 결과적으로 하나의 파일만 존재 ← count 사용 시 주의

 

2) 파일명에 count.index 추가

# main.tf
resource "local_file" "abc" {
  count    = 5
  content  = "This is filename abc${count.index}.txt"
  filename = "${path.module}/abc${count.index}.txt"
}

output "fileid" {
  value = local_file.abc.*.id
}

output "filename" {
  value = local_file.abc.*.filename
}

output "filecontent" {
  value = local_file.abc.*.content
}

결과: 5개의 파일이 각각 생성됨

 

3) count 값을 외부 변수에 식별되도록 구성

때때로 여러 리소스나 모듈의 count로 지정되는 수량이 동일해야 하는 상황이 있다. 이 경우 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"
}
  • 실행 후 확인 : local_file.abc와 local_file.def는 var.name에 선언되는 값에 영향을 받아 동일한 갯수만큼 생성하게 된다.
    • local_file.def의 경우 local_file.abc와 개수가 같아야 content에 선언되는 인수 값에 오류가 없을 것이므로 서로 참조되는 리소스와 모듈의 반복정의에 대한 공통의 영향을 주는 변수로 관리할 수 있다.
  • count로 생성되는 리소스의 경우 <리소스 타입>.<이름>[<인덱스 번호>], 모듈의 경우 module.<모듈 이름>[<인덱스 번호>]로 해당 리소스의 값을 참조한다.
  • 단, 모듈 내에 count 적용이 불가능한 선언이 있으므로 주의해야 한다.
    • 예를 들어 provider 블록 선언부가 포함되어 있는 경우에는 count 적용이 불가능하다 → provider 분리
    • 또한 외부 변수가 list 타입인 경우 중간에 값이 삭제되면 인덱스가 줄어들어 의도했던 중간 값에 대한 리소스만 삭제되는 것이 아니라 이후의 정의된 리소스들도 삭제되고 재생성된다.

IAM 사용자 3명 생성 실습

# iam.tf
provider "aws" {
  region = "ap-northeast-2"
}

resource "aws_iam_user" "myiam" {
  count = 3
  name  = "myuser.${count.index}"
}
# 적용
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]

echo "aws_iam_user.myiam" | terraform console
echo "aws_iam_user.myiam[0]" | terraform console

terraform state show 'aws_iam_user.myiam[0]'
terraform state show 'aws_iam_user.myiam[2]'

aws iam list-users | jq
...

# 삭제
terraform destroy -auto-approve
aws iam list-users | jq

count 변수를 이용한 IAM 사용자 생성 실습

  • 테라폼에서 count 와 함께 배열 조회 구문과 length 함수를 사용해서 사용자들 생성 가능
    • 배열 조회 구문 Array lookup syntax
      • ARRAY[<INDEX>]
      • 예를 들어 다음은 var.user_names 의 인덱스 1에서 요소를 찾는 방법
      • var.user_names[1]
    • length (내장) 함수 built-on function
      • length(<ARRAY>)
      • 주어진 ARRAY 의 항목 수를 반환하는 함수. 문자열 및 맵을 대상으로도 동작
# variables.tf
variable "user_names" {
  description = "Create IAM users with these names"
  type        = list(string)
  default     = ["gasida", "akbun", "hyungwook"]
}

# iam.tf
provider "aws" {
  region = "ap-northeast-2"
}

resource "aws_iam_user" "myiam" {
  count = length(var.user_names)
  name  = var.user_names[count.index]
}
  • init & plan : 리소스에 count 사용한 후에는 하나의 리소스가 아니라 리소스의 배열이 됨.
    • aws_iam_user.myiam 은 이제 IAM 사용자의 배열이므로 표준 구문을 사용하여 해당 리소스인 <PROVIDER>_<TYPE>.<NAME>.<ATTRIBUTE> 에서 속성을 읽은 대신 배열에서 인덱스를 지정해서 IAM 사용자를 명시해야한다.
    • <PROVIDER>_<TYPE>.<NAME>[INDEX].ATTRIBUTE
  • IAM 사용자 한명의 ARN과 사용자 전체의 ARN 을 출력 변수 outputs 으로 제공
    • output.tf : IAM 사용자 전체의 ARN을 원하면 인덱스 대신 스플랫 splat 연산자인 * 를 사용
# output.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"
}
# 적용
terraform apply -auto-approve
terraform state list

terraform output
terraform output first_arn
terraform output -raw first_arn

terraform output all_arns
terraform output -raw all_arns

count 제약사항

인라인 블록 반복 X, 배열 중간 값을 변경할 때 의도하지 않은 결과 발생

전체 리소스를 반복할 수는 있지만 리소스 내에서 인라인 블록을 반복할 수는 없다.

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 사용은 지원하지 않는다.