본문 바로가기
  • lakescript
스터디 이야기/Terraform

[T101] 1-4. Terraform - Resource

by lakescript 2024. 6. 16.

 

더보기

이 스터디는 CloudNet@에서 진행하는 T101 스터디를 참여하면서 공부하는 내용을 기록하는 블로그 포스팅입니다.

CloudNet@에서 제공해주는 자료들과 테라폼으로 시작하는 IaC 를 바탕으로 작성되었습니다.

 

리소스

리소스는 테라폼이 프로비저닝 도구라는 측면에서 가장 중요한 요소이며, 리소스 블록은 선언된 항목을 생성하는 동작을 수행합니다. 

 

 

리소스 구성

리소스 블록은 resource로 시작합니다. 

resource "<리소스 유형>" "<이름>" {
  <인수> = <값>
}

 

- 리소스 이름은 첫번째 _를 기준으로 앞은 프로바이더 이름을 나타내고, 뒤는 프로바이더에서 제공하는 리소스 유형을 의미합니다. 

- 리소스 유형이 선언되면 뒤에는 고유한 이름을 붙입니다. 이름은 동일한 유형에 대해 식별자 역할을 하기 때문에 유형이 같은 경우에는 같은 이름을 사용할 수 없습니다.

- 이름 뒤에는 리소스 유형에 대한 구성 인수들이 중괄호 내에 선언됩니다. 

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

 

위 예제에서 local_file의 경우 local 프로바이더에 속한 리소스 유형임을 알 수 있습니다. 그리고 abc는 이 리소스의 이름을 나타내고, 중괄호 안에 있는 값인 content와 filename은 리소스 유형에 대한 구성 인수들입니다.

 

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

resource "aws_instance" "web" {
  ami = "ami-a1b2c3d4"
  instance_type = "t2.micro"  
}

 

위와 같은 main.tf를 생성하여 terraform init을 실행해보겠습니다.

 

aws 프로바이더 선언이 없어도 리소스를 추가한 코드로 인해 자동으로 인식되어 AWS 프로바이더가 설치되는 것을 확인하실 수 있습니다.

실제로 .terraform/providers의 디렉토리 구조를 확인해보면 aws와 local 프로바이더가 설치되었습니다. 하지만 프로바이더에 따라 접속 정보나 필수 인수를 선언해야 하는 경우가 있으므로 일반적으로는 프로바이더 구성과 함께 사용해야 합니다. 

 

종속성

테라폼의 종속성은 resource, module 선언으로 프로비저닝되는 각 요소의 생성 순서를 구분 짓습니다. 기본적으로 다른 리소스에서 값을 참조해 불러올 경우 생성 선후 관계에 따라 작업자가 의도하지는 않았지만 자동으로 연관 관계가 정의되는 임시적 종속성을 갖게 되고, 강제로 리소스 간 명시적 종속성을 부여할 경우에는 메타인수인 depends_on을 활용합니다. 

 

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

resource "local_file" "def" {
  content  = "456!"
  filename = "${path.module}/def.txt"
}

 

두 리소스는 서로 생성 선후 관계가 없는 동일한 수준의 리소스이므로 테라폼의 병렬 실행 방식에 따라 terraform apply를 수행하면 동시에 생성됩니다.

terraform init
terraform apply --auto-approve

 

terraform destroy

 

종속성의 동작을 확인하기 위해 terraform destory을 진행합니다. 

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

resource "local_file" "def" {
  content  = local_file.abc.content   # local_file.abc의 속성 값을 대신 넣어줌 (123!)
  filename = "${path.module}/def.txt"
}

 

아까의 main.tf 파일에서 다른 리소스에 주입해보고 다시 terraform apply를 실행해보겠습니다.

 

terraform apply --auto-approve

 

 

결과를 보면 리소스 간 종속성이 없는 동일 수준의 구성 요소를 실행하는 경우 동시에 생성되었지만 특정 리소스의 속성값이 필요한 경우 해당 리소스가 우선 생성되어야 하므로 생성에 대한 종속성이 생겨 프로비저닝의 순서가 생겼습니다. 

 

depends_on

테라폼으로 인프라와 서비스를 운영하다보면 리소스의 속성을 주입하지 않아도 두 리소스간에 종속성이 필요한 경우가 있습니다. 

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

resource "local_file" "def" {
  depends_on = [
    local_file.abc
  ]

  content  = "456!"
  filename = "${path.module}/def.txt"
}

 

리소스 속성 참조

리소스 구성에서 참조 가능한 값은 인수와 속성입니다.

  • 인수 : 리소스 생성 시 사용자가 선언하는 값
  • 속성 : 사용자가 설정하는 것은 불가능하지만 리소스 생성 이후 획득 가능한 리소스 고유의 값

리소스 인수의 선언과 참조 가능한 인수 및 속성 패턴

# 테라폼 코드
resource "<리소스 유형>" "<이름>" {
  <인수> = <값>
}


# 리소스 참조
<리소스 유형>.<이름>.<인수>
<리소스 유형>.<이름>.<속성>

 

리소스 참조 예시

쿠버네티스 프로바이더의 Namespace 리소스를 생성하고 그 이후 Secret을 해당 Namespace에 생성하는 종속성을 리소스 인수 값으로 생성하는 코드입니다. 

resource "kubernetes_namespace" "example" {
  metadata {
    annotations = {
      name = "example-annotation"
    }
    name = "terraform-example-namespace"
  }
}

resource "kubernetes_secret" "example" {
  metadata {
    namespace = kubernetes_namespace.example.metadata.0.name # namespace 리소스 인수 참조
    name      = "terraform-example"
  }
  data = {
    password = "P4ssw0rd"
  }
}

 

이 코드를 보시면, Namespace의 이름만 변경해도 해당 Namespace를 참조하는 모든 리소스가 없데이트되어 영향을 받는다는 것을 할 수 있습니다.

즉, 리소스가 생성될 때, 사용자가 입력한 [인수]를 받아 실제 리소스가 생성되면 일부 리소스는 자동으로 기본값이나 추가되는 [속성]이 부여됩니다. 각 리소스마다 문서를 확인해보시면 인수는 Arguments로 표현되어 있으며, 리소스 생성 후 추가되는 속성 값으로 Attributes에 안내되어 있습니다. 리소스 속성을 참조하는 다른 리소스 또는 구성요소에서는 생성 후의 속성 값들도 인수로 가져올 수 있습니다.

 

수명 주기

lifecycle은 리소스이 기본 수명주기를 작업자가 의도적으로 변경하는 메타인수입니다. 

  • create_before_destroy (bool): 리소스 수정 시 신규 리소스를 우선 생성하고 기존 리소스를 삭제
  • prevent_destroy (bool): 해당 리소스를 삭제 Destroy 하려 할 때 명시적으로 거부
  • ignore_changes (list): 리소스 요소에 선언된 인수의 변경 사항을 테라폼 실행 시 무시
  • precondition: 리소스 요소에 선언해 인수의 조건을 검증
  • postcondition: Plan과 Apply 이후의 결과를 속성 값으로 검증

create_before_destroy

resource "local_file" "abc" {
  content  = "lifecycle - step 2"
  filename = "${path.module}/abc.txt"

  lifecycle {
    create_before_destroy = true
  }
}

리소스 요소 특성에 따라 선언한 특정 인수 값을 수정하고 프로비저닝을 수행하면 대상을 삭제하고 다시 생성해야 하는 경우가 있습니다. (대표적으로 클라우드 자원의 image가 변경되는 경우에는 해당 VM 리소스를 삭제하고 다시 생성합니다.) 테라폼의 기본 수명주기는 삭제 후 생성이기 때문에 작업자가 의도적으로 수정된 리소스를 먼저 생성하기를 원할 수 있습니다. 이 경우 create_before_destroy를 true로 설정하면 의도한대로 생성 후 삭제하는 동작으로 설정할 수 있습니다.

- 잘못된 사례 : 리소스의 명시적 구분이 사용자가 지정한 특정 이름이나 ID인 경우 기존 리소스에 할당되어 있기 때문에 생성 실패

- 잘못된 사례 : 생성 후 삭제 시 동일한 리소스에 대한 삭제 명령이 수행되어 리소스가 모두 삭제

prevent_destroy

resource "local_file" "abc" {
  content  = "lifecycle - step 3"
  filename = "${path.module}/abc.txt"

  lifecycle {
    prevent_destroy = true
  }
}

작업자가 의도적으로 특정 리소스의 삭제를 방지하고 싶은 경우에 사용됩니다. lifecycle에 prevent_destory를 true로 설정하고 terraform apply를 진행하게 되면 테라폼 수명주기 (삭제-> 생성)에 따라 수행되는 리소스에 prevent_destroy가 활성화 되어있어 삭제가 일어나지 않고 실패하게 됩니다.

ignore_changes

resource "local_file" "abc" {
  content  = "lifecycle - step 5"
  filename = "${path.module}/abc.txt"

  lifecycle {
    ignore_changes = [
      content
    ]
  }
}

리소스 요소의 인수를 지정해 수정 계획에 변경 사항이 반영되지 않도록 하는 lifecycle입니다. 

precondition

variable "file_name" {
  default = "step0.txt"
}

resource "local_file" "abc" {
  content  = "lifecycle - step 6"
  filename = "${path.module}/${var.file_name}"

  lifecycle {
    precondition {
      condition     = var.file_name == "step6.txt"
      error_message = "file name is not \"step6.txt\""
    }
  }
}

 

리소스 생성 이전에 입력된 인수 값을 검증해야 하는데 사용하여 프로비저닝 이전에 미리 약속된 값 이외에 값 또는 필수로 명시해야 하는 인수 값을 검증할 수 있습니다. 실제로 프로비저닝해야 하는 클라우드 인프라의 VM을 생성할 때 내부적으로 검증된 이미지 아이디를 사용하는지, 스토리지의 암호화 설정이 되어 있는지 등과 같은 구성을 미리 확인하고 사전에 잘못된 프로비저닝을 실행할 수 없도록 구성할 수 있습니다.

 

postcondition

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

  lifecycle {
    postcondition {
      condition     = self.content != ""
      error_message = "content cannot empty"
    }
  }
}

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

종속성을 갖는 여러 리소스를 구성하는 경우, 리소스의 데이터가 다른 리소스 생성 시 활용될 때 원하는 속성이 정의되어야 하는 경우를 확인할 수 있습니다. 특히, 프로비저닝 이후에 생성되는 속성 값이 있으므로 영향을 받는 다른 리소스가 생성되기 전에 예상되지 않은 프로비저닝 작업을 방지할 수 있습니다.