Terraform state 리팩터링

Terraform state 리팩터링 (Refactor Terraform state)

테라폼 구성이 점점 복잡해지면 유지보수성과 성능을 개선하기 위해 리소스를 재구성하고 싶을 수 있어요. 큰 state 파일이 있는 구성을 분리하면 테라폼 작업이 더 빨리 완료돼요.

출처: 문서

본문

이 문서는 테라폼 state를 여러 구성으로 분리하기 전에 거쳐야 할 단계를 설명해요. 먼저 리소스를 어떻게 그룹화할지 계획하고 리소스 간 의존성을 파악해야 해요. 그런 다음 리소스를 한 state 파일에서 다른 state 파일로 옮기는 가장 좋은 방법을 선택해야 해요. 작은 리팩터링이라도 이 원칙들을 잘 지켜야 해요. 구성 리팩터링은 구성과 해당 state 파일을 모두 갱신하는 것을 요구한다는 점을 명심하세요.

리팩터링 기회 파악하기 (Identify opportunities to refactor)

테라폼 구성을 리팩터링할 때는 별도의 생명주기 관리 주기(cadence)를 갖는 것이 좋은 리소스 그룹이나, 모듈로 묶을 수 있는 논리적으로 연관된 리소스 컬렉션을 찾아보세요. 테라폼 구성을 리팩터링하면 유지보수가 더 쉬워져야 해요.

다음은 리팩터링의 흔한 기회들이에요.

  • 긴 테라폼 apply. 구성이 시간이 지나면서 커져 관리하기 번거로워졌어요. 크고 단일체적인 구성은 테라폼 plan과 apply 작업이 완료되는 데 오래 걸리고 의도하지 않은 변경을 일으킬 수 있어요.
  • 관리 생명주기의 변화. 자주 갱신되는 리소스와 드물게 갱신되는 리소스를 분리해서 관리하면 작업이 단순해지고 의도하지 않은 변경의 폭발 반경(blast radius)을 줄일 수 있어요.
  • 리소스 소유권의 변화. 일부 조직에서는 팀이 아키텍처의 서로 다른 부분을 유지하는 책임을 나눠 맡아요. 이런 경우 팀이 자신의 소유 영역과 범위에 맞게 구성과 state를 리팩터링하는 것이 흔해요.
  • 재사용 가능한 모듈 기회. 구성에서 좋은 모듈이 될 만한 하위 섹션을 발견했어요. 여러 구성에서 동일한 리소스 집합을 만들 때는 그 리소스들을 모듈로 묶어 조직 전체에서 재사용하는 것을 권장해요.

리팩터링된 state 계획하기 (Plan your refactored state)

구성 리팩터링을 시작하기 전에 리소스를 어떻게 그룹화할지 계획해야 해요. 리소스를 함께 그룹화할 때 다음 리소스 속성들을 고려하세요.

  • 변동성과 변경 빈도(Volatility and rate of change): 수명이 긴 인프라를 불필요한 변동에 노출시키면 우발적인 변경의 기회가 늘어나요. 예를 들어 구성이 배포하는 컴퓨팅 리소스의 수를 하루에 여러 번 조정하지만, 네트워킹 구성은 몇 달 동안 정적으로 유지될 수 있어요. 네트워크 리소스를 별도로 관리하면 의도하지 않은 변경의 위험을 줄일 수 있어요.
  • Stateful vs stateless: 상태가 있는(stateful) 리소스를 상태가 없는(stateless) 리소스와 독립적으로 관리하면(예: 데이터베이스를 컴퓨팅 인스턴스와 분리), 리소스를 다시 프로비저닝하는 작업의 폭발 반경을 제한하고 우발적인 데이터 손실로부터 보호해요.
  • 접근과 팀 책임(Access and team responsibility): 워크스페이스를 팀별로 나누면 워크스페이스당 책임을 제한하고 팀이 뚜렷한 소유 영역을 유지하게 해줘요. 이를 통해 인프라의 특정 부분에 익숙한 사용자만 관련 구성을 변경하도록 보장할 수 있어요.

리소스 그룹화 전략에 대해 알아보려면 워크스페이스 모범 사례 문서를 참고하세요.

의존성 파악하기 (Identify dependencies)

리소스를 한 state 파일에서 다른 state 파일로 옮길 때는 리소스 간 의존성을 파악해야 해요. 예를 들어 네트워크 리소스에 의존하는 컴퓨트 리소스가 있을 수 있어요. 그 네트워크 리소스를 새 state 파일로 옮기면 컴퓨트 리소스가 더 이상 네트워크를 참조할 수 없게 돼요.

다른 구성의 리소스에 대한 정보를 하드 코딩하는 것보다 동적 참조(dynamic references)를 사용하는 것을 권장해요. 정보를 하드 코딩하면 데이터가 바뀔 때마다 구성을 수동으로 갱신해야 해서, 구성이나 배포된 리소스에 오류가 생길 수 있어요.

다른 state 파일의 리소스를 참조하는 데는 다음 동적 접근 방식을 사용할 수 있어요.

  • 프로바이더가 지원한다면, 리소스 특정 데이터 소스를 사용해 클라우드 프로바이더에 질의할 수 있어요. 예를 들어 aws_vpc 데이터 소스로 다른 구성에서 만든 VPC에 대한 정보를 조회할 수 있어요.
  • HCP Terraform이나 Terraform Enterprise를 사용한다면, tfe_outputs 데이터 소스로 다른 워크스페이스의 출력을 참조할 수 있어요.
  • 다른 원격 백엔드나 로컬 백엔드를 사용한다면, terraform_remote_state 데이터 소스를 사용할 수 있어요. HCP Terraform 워크스페이스의 state에 접근하려면 어떤 다른 워크스페이스가 접근 권한을 갖는지 명시적으로 지정해야 해요. 자세한 내용은 원격 state 공유를 참고하세요.

terraform graph 명령을 사용하면 구성 안 리소스 간의 관계를 시각화하고 의존성을 파악하는 데 도움이 돼요.

리소스 마이그레이션 (Migrate resources)

테라폼 구성을 리팩터링할 때, 가동 중단(downtime)이나 추가 비용 없이 가능하다면 상태가 없는 리소스를 새 테라폼 구성에서 다시 만드는 것을 권장해요.

데이터베이스나 객체 저장소 같은 상태가 있는 리소스는 마이그레이션이 더 복잡해요. 많은 경우 삭제하고 다시 만들 수 없거나, 데이터를 백업·복원하는 것이 복잡하고 비용이 클 수 있어요. 이런 경우 state 파일 간에 리소스를 옮겨서 마이그레이션할 수 있어요.

두 테라폼 state 파일 간에 리소스를 마이그레이션하는 데는 두 가지 흔한 접근 방식이 있어요.

요구사항 (Requirements)

리소스 제거·가져오기는 테라폼 버전 1.7 이상이 필요해요.

테라폼 CLI로 리소스를 새 state 파일로 직접 이동하려면 테라폼 버전 1.0 이상이 필요해요.

제거 후 가져오기 (Remove and import)

테라폼의 구성 기반 removedimport 블록을 사용해서 리소스를 파괴하지 않고 state 파일 간에 이동할 수 있어요.

다음 예제는 새 state 파일로 옮길 초기 리소스 구성이에요.

resource "aws_instance" "example" {
    instance_type = "t3.micro"
    ami = data.aws_ami.example.id
}

소스 구성에서 다음 단계를 따라 리소스를 제거해요.

  1. 최신 테라폼 state를 가져와서 출력을 파일로 백업으로 저장해요.
$ terraform state pull > terraform.tfstate.backup
  1. 프로바이더 구성을 검토해서, 리소스 타입을 제거하기 전에 import에 어떤 속성을 사용해야 하는지 확인해요. 예를 들어 aws_instance import 리소스는 id 속성을 사용해 EC2 인스턴스를 테라폼 state로 가져와요.
$ terraform state show aws_instance.example
##...
id = "i-07b510cff5f79af00"
##...
  1. 마이그레이션하려는 각 리소스에 대해 resource 블록을 removed 블록으로 바꿔서 리소스를 파괴하지 않고 state에서 제거해요. 이 removed 블록은 구성의 어디에나 존재할 수 있지만, 향후 유지보수를 위해 조직이 어디에 선언할지 표준화하는 것을 권장해요. 흔한 방법 중 하나는 이전에 해당 resource 블록이 있던 파일에 removed 블록을 정의하는 것이에요.
- resource "aws_instance" "example" {
-     instance_type = "t3.micro"
-     ami = data.aws_ami.example.id
- }

+ removed {
+   from = aws_instance.example
+   lifecycle {
+     destroy = false
+   }
+ }
  1. terraform plan을 실행해서 테라폼이 리소스를 파괴하지 않을 것임을 확인해요.
# aws_instance.example will no longer be managed by Terraform, but will not be destroyed
# (destroy = false is set in the configuration)
. resource "aws_instance" "example" {
        id                                   = "i-07b510cff5f79af00"
##...
  1. terraform apply를 실행해서 리소스를 state에서 제거해요.

그런 다음 대상(destination) 구성에서 다음 단계를 따라 리소스를 가져와요.

  1. 리소스를 마이그레이션할 구성에 리소스를 추가해요.

  2. 추가한 각 리소스에 import 블록을 추가해서 리소스를 다시 만들지 않고 state에 추가해요. 이 import 블록은 구성의 어디에나 존재할 수 있지만, 조직에서 위치를 표준화해 향후 유지보수를 돕는 것을 권장해요. 흔한 방법 중 하나는 resource 블록을 추가하는 동일한 파일에 import 블록을 정의하는 것이에요.

resource "aws_instance" "example" {
    instance_type = "t3.micro"
    ami = data.aws_ami.example.id
}

import {
id = "i-07b510cff5f79af00"
to = aws_instance.example
}
  1. terraform plan을 실행해서 테라폼이 리소스를 제대로 가져올 것임을 확인해요.
# aws_instance.example will be imported
    resource "aws_instance" "example" {
  1. terraform apply를 실행해서 import를 완료해요.
$ terraform apply 

##...

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

Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.

Enter a value: yes

aws_instance.example: Importing... [id=i-12345678901234567]
aws_instance.example: Import complete [id=i-12345678901234567]

Apply complete! Resources: 1 imported, 0 added, 0 changed, 0 destroyed.

마이그레이션을 완료한 후에는 선택적으로 removedimport 블록을 제거하거나, 리소스의 생명주기 기록으로 남겨둘 수 있어요.

removedimport 블록에 대해 더 알아보려면 다음 문서를 참고하세요.

새 state 파일로 리소스를 직접 이동 (Move resources directly to a new state file)

terraform state mv 명령으로 리소스를 state 파일 간에 직접 이동할 수도 있어요.

경고

-state-state-out 플래그는 테라폼이 하위 호환성을 위해 유지하는 레거시 옵션이에요.

이 접근 방식은 terraform state pullterraform state push 명령도 사용해요. 테라폼이 원격 state를 수동으로 갱신할 때 안전 검사를 수행하지만, 이 방법은 원격 state를 손상시킬 위험이 어느 정도 있어요.

새 마이그레이션에는 위에 문서화된 removedimport 블록을 사용하는 것을 권장해요.

state 파일 준비하기 (Prepare the state files)

로컬 state 백엔드를 사용한다면 두 state 파일 간에 리소스를 직접 이동할 수 있어요. HCP Terraform이나 AWS S3 같은 원격 state 백엔드를 사용한다면 먼저 소스·대상 워크스페이스의 state 파일을 다운로드해야 해요.

state 파일을 다운로드하려면 다음 단계를 따라요.

  1. 소스 구성이 있는 디렉터리에서 terraform state pull 명령으로 state 파일을 로컬에 저장해요.
$ terraform state pull > source.tfstate
  1. 대상 구성이 있는 디렉터리에서 terraform state pull 명령으로 state 파일을 로컬에 저장해요.
$ terraform state pull > destination.tfstate
리소스 이동하기 (Move the resources)

다음으로 terraform state mv 명령으로 소스 state에서 대상 state로 리소스를 이동해요.

$ terraform state mv -state source/source.tfstate -state-out destination/destination.tfstate aws_instance.example aws_instance.example

Move "aws_instance.example" to "aws_instance.example"
Successfully moved 1 object(s).

terraform state mv 명령은 -state 플래그로 소스 state 파일을, -state-out 플래그로 대상 state 파일을 가리켜요. 마지막 두 인자는 소스 state에서 어떤 리소스를 이동할지, 대상 state에서 어떤 리소스로 이동할지를 테라폼에 알려줘요.

마이그레이션하려는 각 리소스에 대해 이 단계를 반복해요.

state 파일 push하기 (Push the state files)

원격 백엔드를 사용한다면 terraform state push 명령으로 state 파일을 백엔드에 push해야 해요.

  1. 소스 구성이 있는 디렉터리에서 terraform state push 명령으로 원격 state를 갱신해요.
$ terraform state push source.tfstate
  1. 대상 구성이 있는 디렉터리에서 terraform state push 명령으로 원격 state를 갱신해요.
$ terraform state push destination.tfstate
구성 갱신과 마이그레이션 검증 (Update your configuration and verify the migration)

다음으로 state에 맞게 구성을 갱신한 다음 변경 사항이 올바른지 검증해요.

  1. 소스 구성에서 마이그레이션한 리소스를 제거해요.
  2. 소스 구성에서 terraform plan을 실행하고 테라폼이 인프라에 어떤 변경도 하지 않을 것임을 확인해요.
  3. 마이그레이션한 리소스를 대상 구성에 추가해요.
  4. 대상 구성에서 terraform plan을 실행하고 테라폼이 인프라에 어떤 변경도 하지 않을 것임을 확인해요.
  5. 소스·대상 구성을 저장하는 리포지토리용 풀 리퀘스트를 만들어요. 코드 리뷰 프로세스가 테라폼 구성 변경을 위한 가상(speculative) plan을 만들면, 그 결과를 검토해서 테라폼이 인프라에 어떤 변경도 하지 않을 것임을 확인해요.
  6. 풀 리퀘스트를 병합해요.

더 알아보려면 terraform state mv 명령 레퍼런스 문서를 참고하세요.

더 알아보기 (Learn more)