3장 기본 사용법 (2)

김진원·2023년 7월 14일

IaC

목록 보기
4/10

"테라폼으로 시작하는 IaC"책을 기준으로 2주차 정리 내용입니다.


3.5 데이터 소스

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

3.5.1 데이터 소스 구성

데이터 소스 블록은 data로 시작되고 이후 "데이터 소스 유형"을 정의한다. Resource 블록 정의와 유사하다.

data "local_file" "abc" {
  filename = "${path.module}/abc.txt"
}

3.5.2 데이터 소스 속성 참조

데이터 소스로 읽은 대상을 참조하는 방식은 리소스와 구별되게 data가 앞에 붙는다.
아래는 데이터 소스 인수 코드와 참조 코드이다.

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

# 데이터 소스 참조
data.<리소스 유형>.<이름>.<속성>

다음 코드는 리소스와 데이터 소스의 참조 방식을 확인한다.

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"
}


def 리소스 생성은 abc를 참조(data.local_file.abc.content)하여 만들었다. 결과로 abc.txt와 def.txt가 생성되고 내용이 같다.



3.6 입력 변수(variable)

입력 변수는 인프라를 구성하는 데 필요한 속성 값을 정의해 코드의 변경 없이 여러 인프라를 생성하는 데 목적이 있으며, 테라폼에서는 입력 변수(Input Variables)로 정의한다.

3.6.1 변수 선언 방식

변수는 variable로 시작되는 블록으로 구성되고 블록 뒤 이름은 동일 모듈선언에서 고유해야 한다.

variable "<이름>" {
  <인수> = <>
}

variable "image_id" {
  type = string
}

아래는 예약 변수 목록이다.

  • source
  • version
  • providers
  • count
  • for_each
  • lifecycle
  • depends_on
  • locals

변수 정의 시 사용 가능한 메타인수이다.

  • default: 변수에 할당되는 기본 값
  • type : 변수에 허용되는 값 유형
  • description : 입력 변수 설명
  • validation : 변수 선언의 제약조건으로 유효성 검사 규칙
  • sensitive : 민감한 변수 값을 알리며 출력문에서 노출 제한
  • nullable : null값 허용

3.6.2 변수 유형

📄기본 유형

  • string
  • number
  • bool
  • any : 모든 유형
variable "string" {
	type		= string
    description = "var String"
    default		= "myString"
}

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

variable "boolean" {
	default = true
}

📄집합 유형

  • list(<유형>) : 인덱스 기반 집합
  • map(<유형>) : 값 = 속성 기반 집합이며 키값 기준 정렬
  • set(<유형>) : 값 기반 집합이며 정렬 키값 기준 정렬
  • object({<인수 이름>=<유형>, ...})
  • tuple([<유형>, ...])
variable "list" {
	default = [
    	"google",
        "vmware"
    ]
}

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

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

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

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

3.6.3 유효성 검사

validation 블록에서 조건인 condition에 지정되는 규칙이 true/false를 반환하며, error_message는 false인 경우 출력되는 메시지이다. regex 함수는 대상의 문자열에 정규식을 적용하고 일치하는 문자열을 반환한다. validation은 중복으로 선언할 수도 있다.

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-\"."
  }
}

3.6.4 변수 참조

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

variable "my_password" {}

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

var.my_password로 변수를 참조하고 있다. 값이 없어서 입력하라 나온다.


3.6.5 민감한 변수 취급

sensitive를 추가하여 변수 값을 감출 수 있습니다.

variable "my_password" {
  default   = "password"
  sensitive = true
}

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


content에서 var 부분을 참조하는데, sensitive가 true로 돼 있기에 값을 볼 수 없다.
하지만 tfstate에는 평문으로 기록되므로 파일 보안이 필요하다.


3.6.6 변수 입력 방식과 우선순위

variable의 목적은 코드 내용을 수정하지 않고 테라폼의 모듈적 특성을 통해 입력되는 변수로 재사용성을 높이는 데 있다. 원하는 값으로 변수에 정의할 수 있다. 기본 코드로 확인해 보자.

📄우선순위 수준 1 : 실행 후 입력

variable "my_var" {}

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


var1을 입력하니 출력도 var1이 나왔다.



📄우선순위 수준 2 : variable 블록의 default 값

variable "my_var" {
  default = "var2"
}

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


default의 var2로 content가 replacement로 되었고 출력도 동일하다.



📄우선순위 수준 3 : 환경 변수

시스템 환경 변수의 접두사에 TFVAR 가 포함되면 그 뒤의 문자열을 변수 이름으로 인식한다.

export TF_VAR_my_var=var3
terraform apply -auto-approve


export로 TF_VAR를 붙인 var를 선언하여 다른 값을 입력하니 값이 변경되었다.



📄우선순위 수준 4 : terraform.tfvars에 정의된 변수 선언

루트 모듈의 main.tf 파일과 같은 위치에 terraform.tfvars 파일을 생성해 변수에 대한 값을 추가하고 앞서 선언한 변수 선언과 비교해 우선순위를 확인하자.

echo 'my_var="var4"' > terraform.tfvars


후에 추가한 tfvars에 있는 "var4"가 출력된다.



📄우선순위 수준 5 : *.auto.tfvars에 정의된 변수 선언

echo 'my_var="var5_a"' > a.auto.tfvars


4 수준보다 높은 auto의 var5_a가 출력된다.



📄우선순위 수준 6 : *.auto.tfvars.json에 정의된 변수 선언

# a.auto.tfvars.json 파일 생성
cat <<EOF > a.auto.tfvars.json
{
  "my_var" : "var6_a"
}
EOF


더 높은 json인 var6_a가 출력된다.



📄우선순위 수준 7 : CLI 실행 시 -var 인수에 지정 또는 -var-file로 파일 지정

변수 값을 선언해 우선순위를 확인하며, 여러 인수가 선언되는 경우 나중에 선언된 변수의 순위가 높다.

terraform apply -auto-approve -var=my_var=var7

terraform apply -auto-approve -var=my_var=var7 -var=my_var=var8


두 번째로 선언한 변수 "var8"이 출력된다.


  • *.tfvars와 같은 형식의 내용의 파일이라면 -var-file로 지정할 수 있다.
# var9.txt 파일 생성
echo 'my_var="var9"' > var9.txt
terraform apply -auto-approve -var=my_var=var7 -var-file="var9.txt"


마지막 변수 "var9"이 적용되어 출력되었다. 이로써 각 방식마다 우선순위가 있음을 예제를 통해서 확인하였다. 다양한 이유는 테라폼 실행 환경, 방식에 따라 입력 변수 값을 선언하도록 해 동일한 코드로 다수의 프로비저닝을 수행하도록 디자인하기 위함이다.
.tfvars 확장자로 생성된 파일에 변수를 미리 기입하면 실행 시 입력해야 하는 변수 값을 하나의 파일에서 관리할 수 있는 장점이 있다.




3.7 local

코드 내에서 사용자가 지정한 값 또는 속상 값을 가공해 참조 가능한 local은 외부에서 입력되지 않고, 코드 내에서만 가공되어 동작하는 값을 선언한다. 선언된 모듈 내에서만 접근 가능하고, 변수처럼 실행 시에 입력받을 수 없다.

3.7.1 local 선언

선언 블록은 locals로 시작한다. 상수만이 아닌 리소스의 속성, 변수의 값들도 조합해 정의할 수 있다.

variable "prefix" {
  default = "hello"
}

locals {
  name    = "terraform"
  content = "${var.prefix} ${local.name}"
  my_info = {
    age    = 20
    region = "KR"
  }
  my_nums = [1, 2, 3, 4, 5]
}

locals {
  content = "content2" # 중복 선언되었으므로 오류가 발생한다.
}

3.7.2 local 참조

선언된 local 값은 local.<이름>으로 참조할 수 있다.

  • main.tf와 sub.tf로 동작을 확인하자.
# main.tf
variable "prefix" {
  default = "hello"
}

locals {
  name    = "terraform"
}

resource "local_file" "abc" {
  content  = local.content             # sub.tf에서 정의된 local값
  filename = "${path.module}/abc.txt"
}

# sub.tf
locals {
  content = "${var.prefix} ${local.name}"
}


main의 content 내용 값으로 local.content를 참조하여 다른 구성 파일에 있지만 실행에서는 마치 하나의 구성 파일처럼 표기, 출력됨을 확인할 수 있다.



3.8 출력(output)

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

  • 루트 모듈에서 사용자가 확인하고자 하는 특정 속성 출력
  • 자식 모듈의 특정 값을 정의하고 루트 모듈에서 결과를 참조
  • 서로 다른 루트 모듈의 결과를 원격으로 읽기 위한 접근 요소

3.8.1 output 선언

모듈 내에서 생성되는 속성 값들은 output 블록에 정의된다.
output 결과에서 리소스 생성 후 결정되는 속성 값은 프로비저닝이 완료되어야 최종적으로 결과를 확인할 수 있고 terraform plan 단계에서는 적용될 값이 출력하지 않는다는 것이다.

  • 사용 가능한 메타인수는 아래와 같다.
    • description : 출력 값 설명
    • sensitive : 민감한 출력 값임을 알리고 테라폼의 출력문에서 값 노출을 제한
    • depends_on : value에 담길 값이 특정 구성에 종속성이 있는 경우 생성되는 순서를 임의로 조정
    • precondition : 출력 전에 지정된 조건을 검증
output "instance_ip_addr" {
  value = "http://${aws_instance.server.private_ip}"
}

3.8.2 output 활용

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)
}


이미 정해진 속성에 대해서는 출력을 예측하지만 아직 생성되지 않은 file_id 같은 값의 경우는 예측이 되지 않아 "known after apply"로 표기된다.

apply 이후에 file_id가 output에서 출력되었다.



3.9 반복문

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

3.9.1 count

count 인수가 포함된 경우 선언된 정수 값만큼 리소스나 모듈을 생성하게 된다. 참조값은 count.index로 반복하는 경우 0부터 1씩 증가해 인덱스가 부여된다.

  • main.tf 구성
resource "local_file" "abc" {
  count    = 5
  content  = "abc"
  filename = "${path.module}/abc.txt"
}

의도대로라면 다섯 개의 파일이 생성되어야 하지만 파일명이 다르지 않기에 결론적으로 하나만 존재하게 됩니다.

  • 참조값 count.index를 활용한 main.tf 구성
resource "local_file" "abc" {
  count    = 5
  content  = "abc${count.index}"
  filename = "${path.module}/abc${count.index}.txt"
}


이전과 다르게 다수의 파일이 생성되었다. 때때로 여러 리소스나 모듈의 count로 지정되는 수량이 동일해야 하는 상황이 있다. 이 경우 count에 부여되는 정수 값을 외부 변수에 식별되도록 구성할 수 있다.

  • list형태의 배열을 활용한 반복문 동작 구성
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"
}


list의 variable 길이 3을 참조하였기에 각각 abc, def에서 a/b/c가 붙은 총 6개 파일이 생성되었다.


3.9.2 for_each

리소스 또는 모듈 블록에서 for_each에 입력된 데이터 형태가 map 또는 set이면, 선언된 key 값 개수만큼 리소스를 생성하게 된다.

resource "local_file" "abc" {
  for_each = {
    a = "content a"
    b = "content b"
  }
  content  = each.value
  filename = "${path.module}/${each.key}.txt"
}


key 값이 2개이므로 a, b 총 2개의 파일이 생성되었다.


  • for_each가 설정된 블록에서는 each 속성을 사용해 구성을 수정할 수 있다
    • each.key : 이 인스턴스에 해당하는 map 타입의 key 값
    • each.value : 이 인스턴스에 해당하는 map의 value 값

생성되는 리소스의 경우 <리소스 타입>.<이름>[<"key">], 모듈의 경우 module.<모듈 이름>[<"key">]로 해당 리소스의 값을 참조한다.

variable "names" {
  default = {
    a = "content a"
    b = "content b"
    c = "content c"
  }
}

resource "local_file" "abc" {
  for_each = var.names
  content  = each.value
  filename = "${path.module}/abc-${each.key}.txt"
}

resource "local_file" "def" {
  for_each = local_file.abc
  content  = each.value.content
  filename = "${path.module}/def-${each.key}.txt"
}

key 값은 count의 index와는 달리 고유하므로 중간에 값을 삭제한 후 다시 적용해도 삭제한 값에 대해서만 리소스를 삭제한다.


3.9.3 for

for문은 복합 형식 값의 형태를 변환하는 데 사용된다.

  • 예를 들어 list 값의 포맷을 변경하거나 특정 접두사 prefix를 추가할 수도 있고, output에 원하는 형태로 반복적인 결과를 표현할 수 도 있다.
    • list 타입의 경우 값 또는 인덱스와 값을 반환
    • map 타입의 경우 키 또는 키와 값에 대해 반환
    • set 타입의 경우 키 값에 대해 반환
  • list의 내용을 담는 리소스를 생성 main.tf
variable "names" {
  default = ["a", "b", "c"]
}

resource "local_file" "abc" {
  content  = jsonencode(var.names) # 결과 : ["a", "b", "c"]
  filename = "${path.module}/abc.txt"

  • content의 값 정의에 for 구문을 사용하여 내부 값을 일괄적으로 변경
variable "names" {
  default = ["a", "b", "c"]
}

resource "local_file" "abc" {
  content  = jsonencode([for s in var.names : upper(s)]) # 결과 : ["A", "B", "C"]
  filename = "${path.module}/abc.txt"
}


소문자가 대문자로 변경된 결과를 볼 수 있습니다.


for 구문을 사용하는 몇 가지 규칙.

  • list 유형의 경우 반환 받는 값이 하나로 되어 있으면 을, 두 개의 경우 앞의 인수가 인덱스를 반환하고 뒤의 인수가 을 반환
    • 관용적으로 인덱스는 i, 값은 v로 표현
  • map 유형의 경우 반환 받는 값이 하나로 되어 있으면 를, 두 개의 경우 앞의 인수가 를 반환하고 뒤의 인수가 을 반환
    • 관용적으로 키는 k, 값은 v로 표현
  • 결과 값은 for 문을 묶는 기호가 [ ]인 경우 tuple로 반환되고 { }인 경우 object 형태로 반환
  • object 형태의 경우 에 대한 쌍은 기호로 구분
  • { } 형식을 사용해 object 형태로 결과를 반환하는 경우 키 값은 고유해야 하므로 값 뒤에 그룹화 모드 심볼(…)를 붙여서 키의 중복을 방지(SQL의 group by 문 또는 Java의 MultiValueMap과 같은 개념)
  • if 구문을 추가해 조건 부여 가능
  • list 유형에 대한 for 구문 규칙 검층을 위한 구성 코드
variable "names" {
  type    = list(string)
  default = ["a", "b"]
}

output "A_upper_value" {
  value = [for v in var.names : upper(v)]
}

output "B_index_and_value" {
  value = [for i, v in var.names : "${i} is ${v}"]
}

output "C_make_object" {
  value = { for v in var.names : v => upper(v) }
}

output "D_with_filter" {
  value = [for v in var.names : upper(v) if v != "a"]
}


  • map 유형의 for 구문 규칙 검증을 위한 구성
variable "members" {
  type = map(object({
    role = string
  }))
  default = {
    ab = { role = "member", group = "dev" }
    cd = { role = "admin", group = "dev" }
    ef = { role = "member", group = "ops" }
  }
}

output "A_to_tupple" {
  value = [for k, v in var.members : "${k} is ${v.role}"]
}

output "B_get_only_role" {
  value = {
    for name, user in var.members : name => user.role
    if user.role == "admin"
  }
}

output "C_group" {
  value = {
    for name, user in var.members : user.role => name...
  }
}


3.9.4 dynamic

count 나 for_each 구문을 사용한 리소스 전체를 여러 개 생성하는 것 이외도 리소스 내에 선언되는 구성 블록을 다중으로 작성해야 하는 경우가 있다.

resource "aws_security_group" "example" {
  name        = "example-security-group"
  description = "Example security group"
  vpc_id.     = aws_vpc.main.id

  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    ipv6_cidr_blocks = ["::/0"]
  }
}

리소스 내의 블록 속성(Attributes as Blocks)은 리소스 자체의 반복 선언이 아닌 내부 속성 요소 중 블록으로 표현되는 부분에 대해서만 반복 구문을 사용해야 하므로, 이때 dynamic 블록을 사용해 동적인 블록을 생성 할 수 있다.


  • archive 프로바이더의 archive_file에 source 블록 선언을 반복 예시
data "archive_file" "dotfiles" {
  type        = "zip"
  output_path = "${path.module}/dotfiles.zip"

  source {
    content  = "hello a"
    filename = "${path.module}/a.txt"
  }

  source {
    content  = "hello b"
    filename = "${path.module}/b.txt"
  }

  source {
    content  = "hello c"
    filename = "${path.module}/c.txt"
  }
}

  • dynamic 블록으로 재구성한 예시
variable "names" {
  default = {
    a = "hello a"
    b = "hello b"
    c = "hello c"
  }
}

data "archive_file" "dotfiles" {
  type        = "zip"
  output_path = "${path.module}/dotfiles.zip"

  dynamic "source" {
    for_each = var.names
    content {
      content  = source.value
      filename = "${path.module}/${source.key}.txt"
    }
  }
}

동일한 파일들을 볼 수 있습니다.

0개의 댓글