Terraform의 Workspace를 이용해 배포 환경 분리하기
최근 새로운 시스템을 구축하면서 Terraform을 도입해서 인프라스트럭처를 코드로 관리하고 있다. 이 글에서는 Terraform의 Workspace를 사용해서 서로 다른 배포 환경(dev, test, staging, prod 등)의 인프라를 관리하는 방법을 설명하겠다.
프로젝트를 분리
Terraform으로 서로 다른 배포 환경의 인프라스트럭처를 관리하는 가장 단순한 방법은 코드베이스를 환경별로 분리하는 것이다.
terraforming-dev/
└── aws
├── api-gateway.tf
├── lambda.tf
└── ...terraforming-prod/
└── aws
├── api-gateway.tf
├── lambda.tf
└── ...
서로 다른 프로젝트이기 때문에 credentials, backend 등의 config 를 포함해 모든 리소스를 완벽하게 격리할 수 있지만, (누구나 예상하듯이) 거의 동일한 코드를 복사-붙여넣기 해야해서 코딩하기 귀찮고 중복 코드가 늘어나 유지보수가 어렵기 때문에 좋은 방법은 아니다.
같은 프로젝트에서 디렉토리로 분리
하나의 프로젝트 안에서 (기본적인 config들은 공유하고) 환경별로 디렉토리를 구분하는 방법도 생각해볼 수 있다.
terraforming/
└── aws
├── env
│ ├── dev
│ ├── prod
│ │ ├── instance.tf
│ │ └── security_group.tf
│ ├── staging
│ └── testing
└── modules
└── module_name
├── main.tf
├── output.tf
└── variables.tfref) https://medium.com/@jyotirbhandari/overview-of-terraform-in-10-mins-9780d55bf6a3
하나의 프로젝트이기 때문에 config 설정을 공유한다(물론, 원한다면 분리하는 것도 가능함). 또 공통 코드가 있다면 별도 디렉토리(위에서는 modules)에 모듈로 만들어서 유지보수를 좀 더 쉽게 할 수도 있다.
그러나 여전히 코드를 어느 정도 복사-붙여넣기 해야하는 건 마찬가지이다. 환경별 인프라 구성 차이가 크다면 괜찮은 방법이지만, 보통은 환경별 인프라가 거의 유사하기 때문에 중복 코드가 많이 생긴다.
Terraform Workspace를 사용하여 분리
Terraform에서는 이런 요구 사항을 반영해 0.9.0 버전에 Environment 라는 기능을 출시했고, 0.10.0 버전부터는 Workspace로 이름을 변경했다.
Workspace는 Terraform state를 담는 그릇이라고 생각할 수 있다(자세한 설명은 링크 참고). 위 경우처럼 (작은 차이점만 있는) 거의 동일한 코드를 관리할 때 유용하다.
우리는 이제 코드베이스를 하나만 관리하면 된다.
terraforming/
└── aws
├── api-gateway.tf
├── lambda.tf
└── ...
개발 환경에 따라 Workspace를 만들고, 선택해 배포한다.
# Create a new workspace
$ terraform workspace new prod# Select a workspace (already created)
$ terraform workspace select prod# Plan or Apply
$ terraform plan
$ terraform apply
개발 환경 사이에 설정이 달라지는 부분은 Interpolation을 이용한다.
resource "aws_route53_zone" "primary" {
name = "${terraform.workspace == "prod" ? "salesbooster.io" : "dev.salesbooster.io"}"
}
Map 타입의 Variable을 이용하면 더 깔끔하게 변수들을 관리할 수 있다.
variable "root_domain_name" {
type = "map"
default = {
"prod" = "salesbooster.io"
"dev" = "dev.salesbooster.io"
}
}resource "aws_route53_zone" "primary" {
name = "${var.root_domain_name[terraform.workspace]}"
}
만약 특정 개발 환경에서 리소스를 아예 배포하고 싶지 않으면 count 변수에 0을 넘긴다.
resource "aws_route53_record" "example" {
count = "${terraform.workspace == "prod" ? 1 : 0}"
}
이 방법은 유지 보수가 쉽고 더 편리하기 때문에 거의 유사한 인프라를 서로 다른 환경으로 배포할 때 유용하게 사용할 수 있다.
Troubleshooting
만약 우리의 경우처럼 환경마다 서로 다른 AWS 계정을 사용하고 있다면 아래의 두 가지 문제를 겪을 것이다.
Workspace별로 다른 Backend 사용하기
Terraform의 구현상 여러 Workspace는 하나의 Backend 안에서 구성된다. 만약 Workspace 별로 Backend 설정도 분리하고 싶다면 지금 현재로는 설정이 쉽지 않다. (관련 이슈 참고)
우리의 경우, Workspace별로 서로 다른 AWS 계정에 있는 S3를 Backend로 사용해보려 했으나 몇 가지 편법 외에는 방법이 없었다. 그래서 dev용 AWS 계정에 있는 S3를 모든 Workspace가 공유하는 형태로 구성했다.
Workspace에 따라 Role 을 변경하여 배포하기
Workspace 별로 서로 다른 AWS 계정을 사용할 경우, 아래와 같이 설정하면 하나의 AWS Profile에 role만 변경하여 여러 환경을 배포할 수 있다. (아니, 그럴 것 같았다)
provider "aws" {
version = "~> 1.1"
region = "ap-northeast-2"
assume_role {
role_arn = "arn:aws:iam::${var.account_id[terraform.workspace]}:role/${var.aws_role}"
}
}
하지만 현재 버전의 Terraform에서 assume_role이 동작하지 않는 버그가 있다. (관련 이슈 참고) 따라서 아쉽게도 현재는, 아래와 같이 Workspace를 변경할 때마다 (그 Workspace에 대응되는) AWS 계정의 IAM 유저 프로필로 변경을 해야한다.
$ terraform workspace select prod
$ export AWS_PROFILE=terraform-prod
$ terraform plan