모듈 리팩터링하기
모듈 리팩터링하기 (Refactor modules)
기본적으로 Terraform은 변경을 기존 리소스를 파괴하고 새 주소에 새 리소스를 만들라는 지시로 해석해요. moved 블록을 사용하면 리소스를 파괴하지 않고 리소스 주소를 업데이트할 수 있어요.
출처: 문서
본문
Hands On: 구성으로 리소스 이동하기 (Use Configuration to Move Resources) 튜토리얼을 시도해 보세요.
개요 (Overview)
객체를 이동하거나 이름을 바꾸려면 구성에 moved 블록을 추가하세요. to 인자에 지정된 리소스에 대한 새 플랜을 만들기 전에 Terraform은 from 주소에 기존 리소스가 있는지 스테이트를 확인해요. Terraform은 리소스 주소에 대한 업데이트를 다음 실행 계획에 포함해요. 주소 변경은 리소스를 파괴하지 않아요.
다음 사용 사례에 moved 블록을 사용할 수 있어요:
모듈이나 리소스가 더 이상 필요하지 않으면 구성에서 moved 블록을 제거할 수 있어요. moved 블록을 제거하는 것은 파괴적인 변경(breaking change)이라는 점에 유의하세요.
요구 사항 (Requirements)
moved 블록으로 모듈 주소를 명시적으로 리팩터링하려면 Terraform v1.1 이상이 필요해요. 그 대신 terraform state mv CLI 명령을 사용하세요.
리소스 또는 모듈 이동하기 (Move a resource or module)
구성에 moved 블록을 추가하고 다음 인자를 지정하세요:
다음 예시에서 aws_instance.a라는 리소스가 aws_instance.b로 이동됐어요:
moved {
from = aws_instance.a
to = aws_instance.b
}
aws_instance.b에 대한 새 플랜을 만들기 전에 Terraform은 먼저 스테이트에 aws_instance.a에 대한 기존 객체가 있는지 확인해요. 기존 객체가 있으면 Terraform은 그 객체의 이름을 aws_instance.b로 바꾼 다음 플랜 만들기를 진행해요. 결과 플랜은 객체가 원래 aws_instance.b에서 생성된 것처럼 되며, apply 중에 파괴할 필요가 없어요.
from과 to 주소는 둘 다 모듈, 리소스, 하위 모듈 안의 리소스를 선택할 수 있는 특별한 주소 구문을 사용해요.
리소스 이름 바꾸기 (Rename a resource)
리소스 구성이 있는 다음 예시 모듈을 생각해 보세요:
resource "aws_instance" "a" {
count = 2
# (resource-type-specific configuration)
}
이 구성을 처음 적용하면 Terraform이 aws_instance.a[0]와 aws_instance.a[1]을 만들어요. 나중에 이 리소스에 다른 이름을 선택한다면, resource 블록의 이름 라벨을 바꾸고 이전 이름을 moved 블록 안에 기록할 수 있어요:
resource "aws_instance" "b" {
count = 2
# (resource-type-specific configuration)
}
moved {
from = aws_instance.a
to = aws_instance.b
}
이 모듈을 사용하는 각 구성에 대한 다음 플랜을 만들 때, Terraform은 aws_instance.a에 속하는 기존 객체를 마치 aws_instance.b를 위해 생성된 것처럼 취급해요: aws_instance.a[0]는 aws_instance.b[0]으로, aws_instance.a[1]은 aws_instance.b[1]으로 취급돼요. aws_instance.a가 결코 없었던 새 모듈 인스턴스는 moved 블록을 무시하고 평소처럼 aws_instance.b[0]와 aws_instance.b[1]을 만들 것을 제안해요.
이 예시의 두 주소는 리소스를 전체로 참조했으므로, Terraform은 리소스의 모든 인스턴스에 대해 이동을 인식해요. 즉 각각을 개별적으로 식별할 필요 없이 aws_instance.a[0]와 aws_instance.a[1] 둘 다를 포함해요.
각 리소스 타입에는 별도의 스키마가 있어서 다른 타입의 객체는 일반적으로 호환되지 않아요. moved 블록으로 리소스의 이름을 바꾸는 것은 항상 가능하지만, 일부 제공사는 객체를 한 리소스 타입에서 다른 타입으로 바꾸는 것도 허용해요. 어떤 리소스가 타입 간 이동이 가능한지에 대한 자세한 내용은 제공사 문서를 참고하세요. moved 블록으로 관리되는 리소스(resource 블록)를 데이터 리소스(data 블록)로 바꿀 수는 없어요.
여러 인스턴스 만들기 (Create multiple instances)
원래 인스턴스를 보존하면서 리소스 또는 모듈 호출의 여러 인스턴스를 만들 때 moved 블록을 사용할 수 있어요.
리소스에 count 또는 for_each 활성화하기
다음 예시 모듈은 aws_instance.a 주소에 바인딩된 단일 인스턴스 리소스를 포함해요:
resource "aws_instance" "a" {
# (resource-type-specific configuration)
}
나중에 이 리소스에 for_each을 사용해서 여러 인스턴스를 체계적으로 선언할 수 있어요. 이전에 aws_instance.a와 연결된 객체를 보존하려면, 그 객체가 새 구성에서 어떤 인스턴스 키를 가질지 지정하는 moved 블록을 추가해야 해요.
다음 예시에서 Terraform은 aws_instance.a의 기존 객체를 파괴할 계획을 세우지 않아요. 대신 Terraform은 그 객체를 마치 원래 aws_instance.a["small"]로 생성된 것처럼 취급해요:
locals {
instances = tomap({
big = {
instance_type = "m3.large"
}
small = {
instance_type = "t2.medium"
}
})
}
resource "aws_instance" "a" {
for_each = local.instances
instance_type = each.value.instance_type
# (other resource-type-specific configuration)
}
moved {
from = aws_instance.a
to = aws_instance.a["small"]
}
두 주소 중 적어도 하나가 ["small"] 같은 인스턴스 키를 포함하면, Terraform은 두 주소가 리소스 전체가 아니라 리소스의 특정 인스턴스를 참조하는 것으로 이해해요. 즉 moved를 사용해 키 사이를 전환하고, count와 for_each 또는 둘 다 없음 사이를 전환하면서 키를 추가하고 제거할 수 있어요.
다음 예시들도 비슷한 방식으로 리소스 인스턴스 키에 대한 변경을 기록하는 유효한 moved 블록이에요:
# Both old and new configuration used "for_each", but the
# "small" element was renamed to "tiny".
moved {
from = aws_instance.b["small"]
to = aws_instance.b["tiny"]
}
# The old configuration used "count" and the new configuration
# uses "for_each", with the following mappings from
# index to key:
moved {
from = aws_instance.c[0]
to = aws_instance.c["small"]
}
moved {
from = aws_instance.c[1]
to = aws_instance.c["tiny"]
}
# The old configuration used "count", and the new configuration
# uses neither "count" nor "for_each", and you want to keep
# only the object at index 2.
moved {
from = aws_instance.d[2]
to = aws_instance.d
}
이전에 없던 인자를 기존 리소스에 count로 추가하면, 그 리소스를 명시적으로 언급하는 moved 블록을 작성하지 않는 한 Terraform은 원래 객체를 인스턴스 0으로 이동할 것을 자동으로 제안해요. 하지만 모듈의 미래 독자에게 변경을 더 명확히 보여주기 위해 해당하는 moved 블록을 명시적으로 작성하는 것을 권장해요.
모듈 호출에 count 또는 for_each 활성화하기
다음 예시는 module.a로 시작하는 주소를 가진 객체를 만드는 단일 인스턴스 모듈이에요:
module "a" {
source = "../modules/example"
# (module arguments)
}
더 나중의 모듈 버전에서 이 리소스에 count을 사용해 여러 인스턴스를 체계적으로 선언해야 할 수 있어요. 이전에 aws_instance.a 단독과 연결된 객체를 보존하려면, 그 객체가 새 구성에서 어떤 인스턴스 키를 가질지 지정하는 moved 블록을 추가할 수 있어요.
아래 구성은 module.a의 모든 객체를 마치 원래 module.a[2]에서 생성된 것처럼 취급하라고 Terraform에 지시해요. 결과적으로 Terraform은 module.a[0]와 module.a[1]에 대해서만 새 객체를 만들 계획을 세워요:
module "a" {
source = "../modules/example"
count = 3
# (module arguments)
}
moved {
from = module.a
to = module.a[2]
}
두 주소 중 적어도 하나가 이전 예시의 [2] 같은 인스턴스 키를 포함하면, Terraform은 두 주소가 모듈 호출 전체가 아니라 모듈 호출의 특정 인스턴스를 참조하는 것으로 이해해요. 즉 moved를 사용해 키 사이를 전환하고, count와 for_each 또는 둘 다 없음 사이를 전환하면서 키를 추가하고 제거할 수 있어요.
모듈 호출 이름 바꾸기 (Rename a module call)
리소스 이름을 바꾸는 것과 비슷한 방식으로 모듈에 대한 호출 이름을 바꿀 수 있어요. 다음 원래 모듈 버전을 생각해 보세요:
module "a" {
source = "../modules/example"
# (module arguments)
}
이 구성을 적용할 때 Terraform은 이 모듈에 선언된 모든 리소스의 주소에 모듈 경로 module.a 접두사를 붙여요. 예를 들어 aws_instance.example 리소스는 전체 주소가 module.a.aws_instance.example이 돼요.
나중에 이 모듈 호출에 더 나은 이름을 선택한다면, module 블록의 이름 라벨을 바꾸고 이전 이름을 moved 블록 안에 기록할 수 있어요:
module "b" {
source = "../modules/example"
# (module arguments)
}
moved {
from = module.a
to = module.b
}
이 모듈을 사용하는 각 구성에 대한 다음 플랜을 만들 때, Terraform은 module.a로 시작하는 기존 객체 주소를 마치 module.b에서 생성된 것처럼 취급해요. module.a.aws_instance.example은 module.b.aws_instance.example으로 취급돼요.
이 예시의 두 주소는 모듈 호출을 전체로 참조했으므로, Terraform은 호출의 모든 인스턴스에 대해 이동을 인식해요. 이 모듈 호출이 count나 for_each를 사용한다면 각각을 개별적으로 지정할 필요 없이 모든 인스턴스에 적용돼요.
모듈 분할하기 (Split a module)
모듈이 새 요구 사항을 지원하면서 성장하다 보면, 결국 두 개의 별도 모듈로 분할할 가치가 있을 만큼 커질 수 있어요.
다음 예시 모듈을 생각해 보세요:
resource "aws_instance" "a" {
# (other resource-type-specific configuration)
}
resource "aws_instance" "b" {
# (other resource-type-specific configuration)
}
resource "aws_instance" "c" {
# (other resource-type-specific configuration)
}
이 모듈을 다음과 같이 두 개의 모듈로 분할할 수 있어요:
aws_instance.a는 이제 모듈 "x"에 속해요.aws_instance.b도 모듈 "x"에 속해요.aws_instance.c는 모듈 "y"에 속해요.
이전 리소스 주소에 바인딩된 기존 객체를 교체하지 않고 이 리팩터링을 달성하려면 다음을 해야 해요:
- 모듈 "x"를 작성하고, 포함해야 할 리소스 두 개를 복사해 넣어요.
- 모듈 "y"를 작성하고, 포함해야 할 리소스 하나를 복사해 넣어요.
- 원래 모듈을 편집해서 더 이상 이 리소스들을 포함하지 않게 하고, 대신 기존 사용자를 마이그레이션하기 위한 shim 구성만 담게 해요.
새 모듈 "x"와 "y"는 resource 블록만 담아야 해요:
# module "x"
resource "aws_instance" "a" {
# (other resource-type-specific configuration)
}
resource "aws_instance" "b" {
# (other resource-type-specific configuration)
}
# module "y"
resource "aws_instance" "c" {
# (other resource-type-specific configuration)
}
이제 이전 버전과의 호환성을 위한 shim일 뿐인 원래 모듈은 두 새 모듈을 호출하면서 리소스가 그 안으로 이동했음을 나타내요:
module "x" {
source = "../modules/x"
# ...
}
module "y" {
source = "../modules/y"
# ...
}
moved {
from = aws_instance.a
to = module.x.aws_instance.a
}
moved {
from = aws_instance.b
to = module.x.aws_instance.b
}
moved {
from = aws_instance.c
to = module.y.aws_instance.c
}
원래 모듈의 기존 사용자가 새 "shim" 버전으로 업그레이드하면, Terraform은 세 개의 moved 블록을 알아차리고 세 이전 리소스 주소와 연결된 객체가 마치 원래 두 새 모듈 안에서 생성된 것처럼 동작해요.
이 모듈군의 새 사용자는 결합된 shim 모듈을 사용하거나 두 새 모듈을 별도로 사용할 수 있어요. 기존 사용자에게 이전 모듈이 이제 더 이상 사용되지 않으므로(deprecated) 새 요구 사항에는 두 개의 분리된 모듈을 사용해야 한다고 알리는 것이 좋을 수 있어요.
다중 모듈 리팩터링 상황은 상위 모듈이 하위 모듈을 "닫힌 상자(closed box)"로 보고 그 안에 정확히 어떤 리소스가 선언됐는지 알지 못한다는 일반적인 규칙을 위반한다는 점에서 특별해요. 이 절충은 세 모듈 모두 같은 사람들이 유지 관리하고 단일 모듈 패키지로 함께 배포된다고 가정해요.
Terraform은 moved 블록의 모듈 참조를 정의된 모듈 인스턴스에 상대적으로 해석해요. 예를 들어 위 원래 모듈이 이미 module.original이라는 하위 모듈이라면, module.x.aws_instance.a에 대한 참조는 module.original.module.x.aws_instance.a로 해석돼요. 모듈은 자신의 객체와 하위 모듈의 객체에 대해서만 moved 문을 만들 수 있어요.
count나 for_each 메타-인자로 호출된 모듈 안의 리소스를 참조해야 한다면, 리소스 구성의 새 위치와 일치하도록 사용할 특정 인스턴스 키를 지정해야 해요:
moved {
from = aws_instance.example
to = module.new[2].aws_instance.example
}
moved 블록 제거하기 (Remove a move block)
시간이 지나면서 오래 지속된 모듈은 많은 moved 블록을 축적할 수 있어요. moved 블록을 제거하는 것은 파괴적인 변경인데, 이전 주소를 참조하는 모든 구성이 객체를 이동하는 대신 기존 객체를 삭제하도록 플랜할 것이기 때문이에요. 이전 버전의 사용자를 위한 업그레이드 경로를 보존하기 위해 모듈의 이전 버전에서 가져온 모든 역사적 moved 블록을 유지하는 것을 강력히 권장해요.
moved 블록을 제거하기로 결정한다면 주의해서 진행하세요. 조직 내에서 프라이빗 모듈을 유지 관리하고 모든 사용자가 새 모듈 버전으로 terraform apply를 성공적으로 실행했다고 확신할 때 moved 블록을 제거하는 것은 안전할 수 있어요.
같은 객체를 두 번 이름을 바꾸거나 이동해야 한다면, 전체 변경 이력을 문서화하기 위해 moved 블록을 연결(chaining)하는 것을 권장해요:
moved {
from = aws_instance.a
to = aws_instance.b
}
moved {
from = aws_instance.b
to = aws_instance.c
}
이런 방식으로 이동 시퀀스를 기록하면 aws_instance.a에 객체가 있는 구성과 aws_instance.b에 객체가 있는 구성 둘 다 성공적으로 업그레이드할 수 있어요. 두 경우 모두 Terraform은 기존 객체를 마치 원래 aws_instance.c로 생성된 것처럼 취급해요.