본문 바로가기
terraform

[terraform] resource를 여러개 생성하는 올바른 방법(count보다는 for_each)

by devjh 2022. 11. 3.
반응형

이번 게시글에서는 resource를 여러개 생성하는 방법에 대해 정리합니다.

 

테라폼에 익숙하지 않다면 count를 사용하기 쉬운데 좋은 방법이 아닙니다.

 

잘못된 예시와 올바른 예시를 정리합니다.

 

복잡한 aws를 몰라도 쉽게 적용이 가능한 s3 bucket으로 예시를 만들었습니다.

 

전체 소스코드는 아래의 github에서 확인할 수 있습니다.

 

prod, stag, dev등의 여러 환경을 위한 모듈구성방법도 포함되어있으니 github 레포를 참고해주세요

 

 

GitHub - jaeho310/multi-resource-terraform

Contribute to jaeho310/multi-resource-terraform development by creating an account on GitHub.

github.com

1. 잘못된 예

(1). multi-resource/modules/list-s3

resource "aws_s3_bucket" "list_bucket" {
  count = length(var.list_bucket)
  bucket = var.list_bucket[count.index].name
  tags = var.list_bucket[count.index].tags
  force_destroy = var.list_bucket[count.index].force_destroy
}
variable "list_bucket" {
  type = list(
    object({
      name = string
      tags = object({})
      force_destroy = bool
    })
  )
}

terraform list for loop를 구글링하면 나오는 방식입니다.

for 루프를 만들기 위해 resource에 count를 만들고, count.index로 list를 순회하여 버킷을 생성 합니다.

 

(2). multi-resource/main

locals {
  // 잘못된 방식
  list_bucket = [
    {
      name = "${var.workspace.phase}-blue-bucket-test"
      tags = {
        service = "${var.workspace.phase}-coupon"
        terraform = "true"
      }
      force_destroy = true
    },
    {
      name = "${var.workspace.phase}-red-bucket-test"
      tags = {
        service = "${var.workspace.phase}-coupon"
        terraform = "true"
      }
      force_destroy = true
    },
    {
      name = "${var.workspace.phase}-black-bucket-test"
      tags = {
        service = "${var.workspace.phase}-coupon"
        terraform = "true"
      }
      force_destroy = false
    },
  ]
}

module "s3_list_bucket" {
  source = "../modules/list-s3"
  list_bucket = local.list_bucket
}
variable "workspace" {
  type = object({
    phase = string
  })
}

모듈로 resource를 당겨오고, local변수로 list_bucket을 만들어 필요한 내용을 채워줬습니다.

dev, stag, prod등의 환경에 맞게 사용하기 위해 variables를 만들어서 한번 더 모듈화를 해줬습니다.

 

(3). multi-resource/dev

module "dev" {
  source = "../main"
  workspace = local.workspace
}
provider "aws" {
  region = "ap-northeast-2"
  profile = "your-profile"
}
locals {
  workspace = {
    phase = "dev"
  }
}

해당 모듈을 당겨와서 phase라는 변수를 채워줬습니다.

main에는 환경마다 비슷한 local 변수를 채우고, 각 환경에 맞게 module로 당겨가 provider를 다르게 가져가면 각 dev, prod, stag 등의 환경을 편하게 구축할 수 있습니다.

$ terraform apply 를 해당 위치에서 진행시키면 provider에 정의된 위치로 버킷이 생성됩니다.

 

잘 만든 테라폼 모듈인것 같으나 치명적인 문제점이 존재합니다.

 

(4) 문제점1

$ terraform state list를 입력하면 아래와 같이 state가 확인됩니다.

module.dev.module.s3_list_bucket.aws_s3_bucket.list_bucket[0]
module.dev.module.s3_list_bucket.aws_s3_bucket.list_bucket[1] 
module.dev.module.s3_list_bucket.aws_s3_bucket.list_bucket[2]

state 이름을 보고서는 이 버킷이 무슨 버킷인지 알수가 없습니다.

내가 일부 항목을 수정한 후 -target 옵션을 걸어서 apply를 하고싶은경우

몇번째 리소스인지 직접 세며 확인해야 합니다.(리소스가 수십개인경우 몇번째인지 세기 굉장히 힘들어집니다)

 

(5) 문제점2

문제점1보다 더더욱 치명적인 문제가 있습니다.

red bucket을 지우고 싶어서 주석처리 해보면

locals {
  // 잘못된 방식
  list_bucket = [
    {
      name = "${var.workspace.phase}-blue-bucket-test"
      tags = {
        service = "${var.workspace.phase}-coupon"
        terraform = "true"
      }
      force_destroy = true
    },
#    {
#      name = "${var.workspace.phase}-red-bucket-test"
#      tags = {
#        service = "${var.workspace.phase}-coupon"
#        terraform = "true"
#      }
#      force_destroy = true
#    },
    {
      name = "${var.workspace.phase}-black-bucket-test"
      tags = {
        service = "${var.workspace.phase}-coupon"
        terraform = "true"
      }
      force_destroy = false
    },
  ]
$ terraform plan

~~~

Plan: 1 to add, 0 to change, 2 to destroy.

버킷이 하나 삭제되길 기대했으나, 2개가 삭제되고 1개가 추가된다고 나옵니다.

 

before

0번 인덱스는 red 버킷

1번 인덱스는 blue 버킷

2번 인덱스는 black 버킷

 

after

0번 인덱스는 red버킷

1번 인덱스는 black버킷

 

1번인덱스 제거, 2번인덱스 제거, 1번인덱스 state에 black을 저장하며 resource 생성

중간에 있는 리소스가 필요하지 않아 삭제하려고하면 인덱스가 한개씩 당겨지게되고 state는 모두 깨지게 됩니다.

 

2. 올바른 예시

(1). muti-resource/modules/map-s3

resource "aws_s3_bucket" "map_bucket" {
  bucket = var.bucket_name
  tags = var.bucket_tags
  force_destroy = var.force_destroy
}
variable "bucket_name" {
  type = string
}

variable "bucket_tags" {
  type = object({})
}

variable "force_destroy" {
  type = bool
}

resource는 버킷을 하나만 생성할 수 있도록 작성합니다.

 

(2). multi-resource/main

locals {
  // 올바른 방식
  map_bucket = {
    "${var.workspace.phase}-brown-bucket-test"= {
      tags = {
        service = "${var.workspace.phase}-coupon"
        terraform = "true"
      }
      force_destroy = false
    },
    "${var.workspace.phase}-pink-bucket-test"= {
      tags = {
        service = "${var.workspace.phase}-coupon"
        terraform = "true"
      }
      force_destroy = false
    },
    "${var.workspace.phase}-yellow-bucket-test"= {
      tags = {
        service = "${var.workspace.phase}-coupon"
        terraform = "true"
      }
      force_destroy = false
    }
  }
}

module "s3_map_bucket" {
  source = "../modules/map-s3"
  for_each = local.map_bucket
  bucket_name = each.key
  bucket_tags = each.value.tags
  force_destroy = each.value.force_destroy
}

for_each를 module에서 돌려줍니다.

for_each는 list형식이 아닌 map 형식의 루프를 돌릴때 사용하므로

local 변수를 map 형식으로 작성합니다.

 

(3) 개선된 점

$ terraform state list 를 입력하면 아래와 같이 state가 잡혀있습니다.

state 이름만으로 해당 리소스가 무슨 리소스인지 명확하게 구분할 수 있습니다.

module.main.module.s3_map_bucket["dev-brown-bucket-test"].aws_s3_bucket.map_bucket 
module.main.module.s3_map_bucket["dev-pink-bucket-test"].aws_s3_bucket.map_bucket 
module.main.module.s3_map_bucket["dev-yellow-bucket-test"].aws_s3_bucket.map_bucket

resource의 삭제가 필요한경우

local.map_bucket에서 제거해도 list처럼 index가 밀려 state가 깨지는 일 없이 효율적으로 리소스를 관리할 수 있게 됩니다.

반응형

댓글