DDetB.Log
article thumbnail
Published 2023. 7. 29. 22:35
Terraform - Module DevOps/Terraform
이 포스팅은 CloudNet@ 팀이 진행하는 테라폼 기초 입문 스터디에 참여하며 ‘테라폼으로 시작하는 IaC’ 책을 기준하여 정리한 글입니다.

테라폼으로 인프라와 서비스를 관리하면 시간이 지날수록 구성이 복잡해지고 관리하는 리소스가 늘어나게 된다. 테라폼의 구성 파일과 디렉터리 구성에는 제약이 없기 때문에 단일 파일 구조상에서 지속적으로 업데이트할 수 있지만, 다음과 같은 문제가 발생한다.

  • 테라폼 구성에서 원하는 항목을 찾고 수정하는 것이 점점 어려워짐
  • 리소스들 간의 연관 관계가 복잡해질수록 변경 작업의 영향도를 분석하기 위한 노력이 늘어남
  • 개발/스테이징/프로덕션 환경으로 구분된 경우 비슷한 형태의 구성이 반복되어 업무 효율이 줄어듦
  • 새로운 프로젝트를 구성하는 경우 기존 구성에서 취해야 할 리소스 구성과 종속성 파악이 어려움

모듈 작성 기본 원칙

아래와 같은 기본 작성 원칙을 제안함

  • 모듈 디렉터리 형식을 terraform-<프로바이더 이름>-<모듈 이름> 형식을 제안한다. 이 형식은 Terraform Cloud, Terraform Enterprise에서도 사용되는 방식으로 1) 디렉터리 또는 레지스트리 이름이 테라폼을 위한 것이고, 2) 어떤 프로바이더의 리소스를 포함하고 있으며, 3) 부여된 이름이 무엇인지 판별할 수 있도록 한다.
  • 테라폼 구성은 궁극적으로 모듈화가 가능한 구조로 작성할 것을 제안한다. 처음부터 모듈화를 가정하고 구성파일을 작성하면 단일 루트 모듈이라도 후에 다른 모듈이 호출할 것을 예상하고 구조화할 수 있다. 또한 작성자는 의도한 리소스 묶음을 구상한 대로 논리적인 구조로 그룹화할 수 있다.
  • 각각의 모듈을 독립적으로 관리하기를 제안한다. 리모트 모듈을 사용하지 않더라도 처음부터 모듈화가 진행된 구성들은 떄로 루트 모듈의 하위 파일 시스템에 존재하는 경우가 있다. 하위 모듈 또한 독립적인 모듈이므로 루트 모듈 하위에 두기보다는 동일한 파일 시스템 레벨에 위치하거나 별도 모듈만을 위한 공간에서 불러오는 것을 권장한다. 이렇게 하면 VCS를 통해 관리하기가 더 수월하다.
  • 공개된 테라폼 레지스트리의 모듈을 참고하기를 제안한다. 대다수의 테라폼 모듈은 공개된 모듈이 존재하고 거의 모든 인수에 대한 변수 처리, 반복문 적용 리소스, 조건에 따른 리소스 활성/비활성 등을 모범 사례로 공개해두었다. 물론 그대로 가져다 사용하는 것보다는 프로비저닝하려는 상황에 맞게 참고하는 것을 권장한다.
  • 작성된 모듈은 공개 또는 비공개로 게시해 팀 또는 커뮤니티와 공유하기를 제안한다. 모듈의 사용성을 높이고 피드백을 통해 더 발전된 모듈을 구성할 수 있는 자극이 된다.

모듈화 실습

하나의 프로비저닝에서 사용자와 패스워드를 여러 번 구성해야 하는 경우를 가상의 시나리오로 삼아 모듈화를 진행한다.

  • random_pet는 이름을 자동으로 생성하고, random_password는 사용자의 패스워드를 설정한다.
  • random_password는 random 프로바이더 리소스로 난수 형태로 패스워드를 만들 수 있다.

자식 모듈 작성

main.tf

resource "random_pet" "name" {
  keepers = {
    ami_id = timestamp()
  }
}

resource "random_password" "password" {
  length           = var.isDB ? 16 : 10
  special          = var.isDB ? true : false
  override_special = "!#$%*?"
}

variable.tf

variable "isDB" {
  type        = bool
  default     = false
  description = "패스워드 대상의 DB 여부"
}

output.tf

output "id" {
  value = random_pet.name.id
}

output "pw" {
  value = nonsensitive(random_password.password.result) 
}

실행

자식 모듈 동작 테스트

$ ls *.tf
$ terraform init && terraform plan

# 테스트를 위해 apply 시 변수 지정
$ terraform apply -auto-approve -var=isDB=true
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
Outputs:
id = "knowing-aardvark"
pw = "Y5eeP0i2KLLE9gBa"

# 확인
$ terraform state list
$ terraform state show random_pet.name
$ terraform state show random_password.password

# tfstate에 모듈 정보 확인
$ cat terraform.tfstate | grep module

자식모듈 호출 실습

다수의 리소스를 같은 목적으로 여러 번 반복해서 사용하려면 리소스 수만큼 반복해 구성 파일을 정의해야 하고 이름도 고유하게 설정해줘야 하는 부담이 있지만, 모듈을 활용하면 반복되는 리소스 묶음을 최소화할 수 있다.

main.tf

module "mypw1" {
  source = "../modules/terraform-random-pwgen"
}

module "mypw2" {
  source = "../modules/terraform-random-pwgen"
  isDB   = true
}

output "mypw1" {
  value  = module.mypw1
}

output "mypw2" {
  value  = module.mypw2
}

실행 및 확인

$ terraform init && terraform plan && terraform apply -auto-approve
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
Outputs:
mypw1 = {
  "id" = "equipped-mustang"
  "pw" = "OXST1EYqQc"
}
mypw2 = {
  "id" = "diverse-impala"
  "pw" = "y8mEbOJhS6dCTiK#"
}

# 확인
$ terraform state list

# tfstate에 모듈 정보 확인
$ cat terraform.tfstate | grep module

# terraform init 시 생성되는 modules.json 파일 확인
$ tree .terraform
.terraform
├── modules
│   └── modules.json
...

## 모듈로 묶여진 리소스는 module이라는 정의를 통해 단순하게 재활용하고 반복 사용할 수 있다.
## 모듈의 결과 참조 형식은 module.<모듈 이름>.<output 이름>으로 정의된다.
$ cat .terraform/modules/modules.json | jq
{
  "Modules": [
    {
      "Key": "",
      "Source": "",
      "Dir": "."
    },
    {
      "Key": "mypw1",
      "Source": "../modules/terraform-random-pwgen",
      "Dir": "../modules/terraform-random-pwgen"
    },
    {
      "Key": "mypw2",
      "Source": "../modules/terraform-random-pwgen",
      "Dir": "../modules/terraform-random-pwgen"
    }
  ]
}

모듈 사용 방식

모듈에서 사용되는 모든 리소스는 관련 프로바이더의 정의가 필요하다. 여기서 사용자는 프로바이더 정의를 모듈 안 or 밖 고민해야한다.

유형 1. 자식 모듈에서 프로바이더 정의

  • 프로바이더 버전과 구성에 민감하거나, 루트 모듈에서 프로바이더 정의 없이 자식 모듈이 독립적인 구조일 때 고려할 방법이다
  • 하지만 동일한 프로바이더가 루트와 자식 양쪽에 또는 서로 다른 자식 모듈에 버전 조건 합의가 안 되면, 오류가 발생하고 모듈에 반복문을 사용할 수 없다는 단점이 있으므로 잘 사용하지 않는다.모듈에서 사용하는 프로바디어 버전과 구성 상세를 자식 모듈에서 고정하는 방법이다.

유형 2. 루트 모듈에서 프로바이더 정의

  • 자식 모듈루트 모듈프로바이더 구성에 종속되는 방식이다.
  • 디렉터리 구조로는 분리되어 있지만 테라폼 실행 단계에서 동일 계층으로 해석되므로 프로바이더 버전과 구성은 루트 모듈의 설정이 적용된다. 프로바이더를 모듈 내 리소스와 데이터 소스에 일괄 적용하고, 자식 모듈에 대한 반복문 사용에 자유로운 것이 장점이다. 자식 모듈에 특정 프로바이더 구성의 종속성은 반영할 수 없으므로 자식 모듈을 프로바이더 조건에 대해 기록하고, 자식 모듈을 사용하는 루트 모듈에서 정의하는 프로바이더에 맞게 업데이트 해야 한다.
  • 다음은 동일한 모듈에 사용되는 프로바이더 조건이 다른 경우 각 모듈별로 프로바이더를 맵핑하는 방안이다.
  • 리소스와 데이터 소스에 provider 메타인수로 지정하는 방식과 비슷하나 모듈에는 다수의 프로바이더가 사용될 가능성이 있으므로 map 타입으로 구성하는 provider로 정의한다. 실습을 위한 디렉터리 구성의 예는 다음과 같다.

모듈 소스 관리

  • Module 블록에 정의된 소스 구성으로 모듈의 코드 위치를 정의한다. terraform init을 수행할 때 지정된 모듈을 다운로드해 사용한다.
    • 로컬 디렉터리 경로
    • 테라폼 레지스트리 Terraform Registry
    • 깃허브 Github
    • 비트버킷 Bitbucket
    • 깃 Generic Git Repository
    • HTTP URLs
    • S3 Bucket
    • GCS Bucket google cloud storage
  • 테라폼 공식 문서 안내와 같이 루트 경로에 모듈을 배치하는 것 외에 패키지 하위 디렉터리 경로를 참조하는 것도 하나의 방안이다.
profile

DDetB.Log

@DDetMok

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!