How to Terraform(7) - 코드 중복 최소화하기
목차
시리즈 목차⌗
- How to Terraform(1) - 테라폼과의 가벼운(?) 첫 만남
- How to Terraform(2) - 변수와 출력
- How to Terraform(3) - 상태 관리
- How to Terraform(4) - 상태관리 개선하기
- How to Terraform(5) - 모듈
- How to Terraform(6) - 모듈 버전관리
이전 글에서 우리는 모듈을 활용하여 코드를 재사용하는 방법을 알아봤습니다. 그 결과 동일한 리소스에 대해서 코드 중복을 많이 줄일 수 있었죠. 그런데 여전히 중복되는 코드가 있었습니다. 각 리소스마다 프로바이더와 백엔드는 어쩔 수 없이 매번 작성해야 했습니다. 이번 글에서는 이런 중복을 최소화하는 방법을 알아보겠습니다.
Terragrunt 설치하기⌗
Terragrunt는 Terraform의 래퍼(wrapper)로 동작하며, Terraform 코드에서 중복되는 부분을 많이 줄일 수 있도록 도와줍니다. 래퍼로 동작하기 때문에 Terraform으로 실행가능한 init, plan, apply, destroy등의 명령은 그대로 사용할 수 있습니다. 우선 Terragrunt를 설치해볼까요?
Homebrew로 설치하기⌗
리눅스와 맥에서는 Homebrew를 사용해서 Terragrunt를 설치할 수 있습니다.
> brew install terragrunt
> terragrunt --version
terragrunt version 0.54.5
Terraform과 Terragrunt는 다음과 같이 alias로 등록하여 각각 tf, tg로 사용할 수 있습니다.
alias tf="terraform" alias tg="terragrunt"
Terragrunt 설정하기⌗
먼저 이전 글에서 작업하던 폴더 구조를 다시 확인해볼까요?
terraform-live
└── dev
└── ec2
├── service1
| ├── main.tf
| └── outputs.tf
└── service2
├── main.tf
└── outputs.tf
terraform-modules 리포지토리에 정의되어 있는 모듈을 참조하여 service1, service2를 정의했었습니다. 각 서비스의 main.tf 파일에는 다음과 같은 내용이 공통으로 존재했습니다.
// AWS 프로바이더 설정
provider "aws" {
// 아시아 태평양(서울) 리전
region = "ap-northeast-2"
}
...
Terragrunt로 이 중복을 제거해보겠습니다.
terraform-live/terragrunt.hcl 파일을 생성하고 다음과 같이 작성합니다.
generate "providers" {
path = "providers.tf"
if_exists = "overwrite_terragrunt"
contents = <<EOF
provider "aws" {
// 아시아 태평양(서울) 리전
region = "ap-northeast-2"
}
EOF
}
그리고 service1의 모든 파일을 삭제하고 terragrunt.hcl 파일을 생성하고 다음과 같이 작성합니다.
terraform {
source = "github.com/netscout/terraform-modules//modules/ec2?ref=v0.0.1"
}
include {
path = find_in_parent_folders()
}
inputs = {
env = "dev"
port = 8080
}
현재까지의 내용을 정리해보자면, 폴더 구조는 다음과 같습니다.
terraform-live
├── terragrunt.hcl
└── dev
└── ec2
├── service1
| └── terragrunt.hcl
└── service2
├── main.tf
└── outputs.tf
그리고 Terragrunt의 plan 명령을 실행합니다. 참고로, Terragrunt는 plan 명령을 실행 할 때, init 명령을 자동으로 실행합니다.
> cd dev/ec2/service1
> tg plan
...
Plan: 2 to add, 0 to change, 0 to destroy.
Changes to Outputs:
+ public_ip = (known after apply)
...
provider 설정이 없는데도 정상적으로 동작합니다. 그리고 outputs.tf가 없어졌는데도 출력값을 확인할 수 있습니다. 어떻게 가능한지 확인해볼까요? service1 폴더를 다시 확인해보면, .terragrunt-cache 폴더가 생성되어 있습니다. 이 폴더에는 Terragrunt가 실행될 때 자동으로 생성된 Terraform 코드가 저장되어 있습니다. 폴더 구조를 볼까요?
service1
├── .terragrunt-cache
│ └── lIQuDV1aWBSG5nm4zKCulAdnsus
│ └── modules
│ └── ec2
│ ├── main.tf
│ ├── outputs.tf
│ ├── providers.tf
│ └── variables.tf
└── terragrunt.hcl
다른 파일들이 있긴 하지만, 핵심만 추리면 이렇습니다. 그러니까 모듈로 선언된 Terraform 코드가 그대로 다운로드 되어 있고, providers.tf 파일이 새로 생성되어 있습니다. 이 파일의 내용을 보면 다음과 같습니다.
# Generated by Terragrunt. Sig: nIlQXj57tbuaRZEa
provider "aws" {
// 아시아 태평양(서울) 리전
region = "ap-northeast-2"
}
provider 설정 없이도 plan 명령이 정상적으로 동작한 이유가 뭔지 이제 알 수 있죠? Terragrunt가 자동으로 생성해준 코드를 통해 provider 설정이 추가되었기 때문입니다. 다시 terraform-live/terragrunt.hcl 파일의 내용을 확인해볼까요?
generate "providers" {
path = "providers.tf"
if_exists = "overwrite_terragrunt"
contents = <<EOF
provider "aws" {
// 아시아 태평양(서울) 리전
region = "ap-northeast-2"
}
EOF
}
providers.tf 파일을 생성하도록 설정하였고, 파일이 존재한다면 덮어쓰도록 했습니다. 그리고 파일의 내용은 provider 블록을 통해 aws 프로바이더를 정의해두었습니다. 그래서 tg plan명령이 실행 될 때 Terragrunt가 providers.tf 파일을 생성하고, 이 파일을 통해 provider 설정이 추가되었던 것입니다.
좀 더 복잡한 경우⌗
겨우 이 몇 줄의 코드 때문에 Terragrunt를 써야 하는 거냐고 생각하실 수도 있지만 이 코드는 좀 더 길어질 수도 있습니다. 예를 들어 프로바이더 설정과 백엔드 설정을 추가한다면 어떻게 될까요?
# terragrunt가 각 모듈에 대해서 backend.tf를 생성한다.
remote_state {
backend = "s3"
generate = {
path = "backend.tf"
if_exists = "overwrite"
}
config = {
bucket = "terraform-state-example-20231216"
# 상태 파일이 저장될 경로
key = "${path_relative_to_include()}/terraform.tfstate"
region = "ap-northeast-2"
# 상태 파일을 락으로 사용할 DynamoDB 테이블
dynamodb_table = "terraform_locks"
# 상태 파일을 암호화
encrypt = true
}
}
generate "providers" {
path = "providers.tf"
if_exists = "overwrite_terragrunt"
contents = <<EOF
terraform {
# 필요한 프로바이더 목록과 버전 정보
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0" # 5.0 이상 버전 사용
}
}
}
// AWS 프로바이더 설정
provider "aws" {
// 아시아 태평양(서울) 리전
region = "ap-northeast-2"
}
EOF
}
remote_state 블록에는 모든 Terraform 리소스에 적용될 backend 설정을 정의했습니다. config 속성의 내용은 기존의 backend 속성의 내용을 그대로 옮긴 것이며, generate 속성에는 backend.tf 파일을 생성하고 기존에 존재하는 경우 덮어쓰도록 설정했습니다. 자세히 보시면 config.key 속성이 기존과 다릅니다.
key = "${path_relative_to_include()}/terraform.tfstate"
즉, 현재 이 파일의 위치를 기준으로 해서 Terragrunt 명령이 실행되는 폴더의 경로를 가져오도록 했습니다. 만약 dev/ec2/service1 폴더에서 tg plan 명령을 실행한다면, key 값은 dev/ec2/service1/terraform.tfstate가 됩니다. 매번 리소스를 정의할 때 마다 폴더 경로를 key 값으로 수정하지 않아도 알아서 해준다는 거죠!
그리고 generate 블록에는 프로바이더 버전 정보를 추가했습니다. 이렇게 하면 모든 리소스에 대해서 프로바이더 버전을 일괄적으로 관리할 수 있습니다.
정리⌗
이제 Terraform을 통해 IaC를 구현하는 방법의 기초적인 내용은 모두 알아보았습니다. 익숙해지기 전에는 예전에 하던 것 처럼 수작업으로 일일이 리소스를 생성하는 게 훨씬 빠르다는 생각도 들게 됩니다. 하지만, 한 번 잘 설정해두면 속도가 빨라지는 것 뿐만 아니라 리소스의 변경을 추적할 수 있고, 리소스를 정리할 때도 커맨드 한 번으로 정리할 수 있게 됩니다. 지금까지 읽어주셔서 감사드리며, 도움이 되셨기를 바랍니다.