본 포스트는 2024년 Google Study Jam을 공부하면서 개인적으로 내용을 정리한 포스트 입니다.
Managing Terraform State - Purpose of Terraform state
State은 Terraform이 작동 하기 위한 필수 요구사항이다.
사람들은 가끔 Terraform이 상태 없이 작동할 수 있는지, 아니면 상태를 사용하지 않고 실행할 때마다 클라우드 리소스만 검사할 수 있는지 묻는다. Terraform이 상태 없이 벗어날 수 있는 시나리오에서, 그렇게 하려면 엄청난 양의 복잡성을 한 곳(상태)에서 다른 곳(대체 개념)으로 옮겨야 한다.
현실 세계로 매핑
Terraform 구성을 현실 세계로 매핑하려면 일종의 데이터베이스가 필요하다.
구성에 resource resource "google_compute_instance" "foo"가 포함된 경우, Terraform은 이 맵을 사용하여 인스턴스 i-abcd1234가 해당 리소스로 표시되는지 확인한다.
Terraform은 각 원격 객체가 하나의 리소스 인스턴스에만 바인딩될 것으로 예상하며, 이는 Terraform이 객체를 생성하고 상태의 아이덴티티를 기록할 책임이 있기 때문에 일반적으로 보장된다.
대신 Terraform 외부에서 생성된 객체를 가져오는 경우, 각각의 개별 객체를 하나의 리소스 인스턴스로만 가져오는지 확인해야 한다.
하나의 원격 객체가 둘 이상의 리소스 인스턴스에 바인딩되어 있는 경우, 구성에서 원격 객체 상태로의 매핑이 불분명해져서 해당 객체에 대해 예기치 않은 작업이 발생할 수 있다.
메타데이터
Terraform은 리소스와 원격 객체 간의 매핑 외에도 리소스 종속성과 같은 메타데이터도 추적해야 한다.
Terraform은 일반적으로 구성을 사용하여 종속 항목 순서를 결정한다.
그러나 Terraform 구성에서 리소스를 제거할 때 Terraform은 해당 리소스를 삭제하는 방법을 알고 있어야 한다. Terraform은 구성 파일에 없는 리소스에 대한 매핑이 존재하고 폐기할 계획이 있음을 확인할 수 있다.
그러나 리소스가 더 이상 존재하지 않기 때문에 구성만으로는 순서를 결정할 수 없다.
Terraform은 올바른 작업을 보장하기 위해 상태 내에서 가장 최근의 종속성 세트의 사본을 보관한다.
이제 Terraform은 구성에서 하나 이상의 항목을 삭제할 때 상태로부터 올바른 소멸 순서를 결정할 수 있다.
Terraform이 리소스 유형 간의 필수 순서를 알고 있다면 이 문제를 피할 수 있다.
예를 들어, Terraform은 서버가 속한 서브넷이 삭제되기 전에 서버를 삭제해야 한다는 것을 알 수 있다.
그러나 이러한 접근 방식이 관리가 불가능할 정도로 복잡해 지는 이유는 Terraform이 모든 클라우드에 대한 모든 리소스의 순서 시맨틱스를 이해하는 것 외에도 제공업체 전반의 순서도 이해해야 하기 때문이다.
또한, Terraform은 여러 개의 별칭이 지정된 제공업체가 존재하는 상황에서 리소스로 가장 최근에 사용된 제공업체 구성에 대한 포인터와 같은 유사한 이유로 다른 메타데이터도 저장한다.
성능
Terraform은 기본 매핑 외에도 상태의 모든 리소스에 대해 속성 값의 캐시를 저장한다.
이 기능은 Terraform 상태의 선택사항이며 성능 개선 용도로만 사용된다.
terraform plan을 실행할 때 원하는 구성에 도달하는 데 필요한 변경 사항을 효과적으로 결정하려면 Terraform이 현재 리소스 상태를 알고 있어야 한다.
소규모 인프라의 경우, Terraform은 제공업체를 쿼리하고 모든 리소스에서 최신 속성을 동기화할 수 있다.
이것은 Terraform의 기본 동작으로 Terraform은 plan과 apply가 나올 때마다 상태의 모든 리소스를 동기화한다.
대규모 인프라의 경우 모든 리소스를 쿼리하는 것은 너무 느린다.
많은 클라우드 제공업체는 동시에 여러 리소스를 쿼리할 수 있는 API를 제공하지 않으며, 각 리소스에 대한 왕복 시간은 수백 밀리초이다.
또한 클라우드 제공업체는 거의 항상 API 비율 제한을 두기 때문에 Terraform은 일정 기간 동안 제한된 수의 리소스만 요청할 수 있다.
Terraform의 대규모 사용자들은 이 문제를 해결하기 위해 -refresh=false 플래그와 -target 플래그를 모두 사용하는 경우가 많다.
이러한 시나리오에서는 캐시된 상태가 정보 기록으로 취급된다.
동기화
기본 구성에서 Terraform은 Terraform이 실행된 현재 작업 디렉터리의 파일에 상태를 저장한다.
이 방법은 처음 시작할 때 유용하지만, 팀에서 Terraform을 사용할 때는 모든 사람이 동일한 상태로 작업하여 동일한 원격 객체에 작업이 적용되도록 하는 것이 중요하다.
이 문제를 해결하려면 원격 상태를 사용하는 것이 좋다.
모든 기능을 갖춘 상태 백엔드가 있는 Terraform은 여러 사용자가 실수로 동시에 Terraform을 실행하는 것을 방지하는 수단으로 원격 잠금을 사용할 수 있으며, 이를 통해 각 Terraform 실행이 가장 최근에 업데이트된 상태로 시작되도록 보장한다.
상태 잠금
Backend에서 지원하는 경우, Terraform은 상태를 쓸 수 있는 모든 작업에 대해 상태를 잠근다.
이렇게 하면 다른 사람이 잠금을 획득하여 잠재적으로 상태를 손상시키는 것을 방지할 수 있다.
상태 잠금은 상태를 쓸 수 있는 모든 작업에서 자동으로 발생한다. 상태 잠금이 발생하고 있다는 메시지는 표시되지 않다.
상태 잠금이 실패하면 Terraform은 계속 진행되지 않습니다.
-lock 플래그를 사용하여 대부분의 명령어에 대해 상태 잠금을 중지할 수 있지만 권장하지는 않다.
잠금을 획득하는 데 예상보다 시간이 오래 걸리는 경우, Terraform은 상태 메시지를 출력한다.
Terraform이 메시지를 출력하지 않으면 상태 잠금이 여전히 발생하고 있는 것이다.
모든 백엔드가 잠금을 지원하는 것은 아니다.
백엔드에서 잠금을 지원하는지 여부에 대한 자세한 내용은 Backend-type 목록을 참조.
작업공간
각 Terraform 구성에는 작업이 실행되는 방식과 Terraform 상태와 같은 영구 데이터가 저장되는 위치를 정의하는 연결된 Backend가 있다.
백엔드에 저장된 영구 데이터는 작업공간에 속한다.
처음에 백엔드에는 기본값이라고 하는 하나의 작업공간만 있으며, 따라서 해당 구성에는 하나의 Terraform 상태만 연결된다.
특정 백엔드는 이름이 지정된 여러 개의 작업공간을 지원하므로 여러 개의 상태를 단일 구성에 연결할 수 있다.
구성에는 여전히 하나의 백엔드만 있지만, 새 백엔드를 구성하거나 사용자 인증 정보를 변경하지 않고도 해당 구성의 여러 개별 인스턴스를 배포할 수 있다.
Managing Terraform State - Task 1. Working with configuration
Backend의 이점
- Working in a team: 백엔드는 상태를 원격으로 저장할 수 있으며, 이를 잠금으로 보호하여 손상을 방지한다. Terraform Cloud와 같은 일부 백엔드는 모든 상태 수정 이력을 자동으로 저장한다.
- Keeping sensitive information off disk: 상태는 필요에 따라 백엔드에서 검색되며, 메모리에만 저장된다.
- Remote operations: 더 큰 인프라나 특정 변경 사항의 경우, terraform apply는 오랜 시간이 걸릴 수 있다. 일부 백엔드는 원격 작업을 지원하여 작업을 원격으로 실행할 수 있게 해준다. 이 경우 컴퓨터를 꺼도 작업이 완료된다. 원격 상태 저장 및 잠금 기능과 결합하면(위에서 설명한 대로) 팀 환경에서도 유용하다.
Backend는 선택 사항이다: Terraform을 사용할 때 백엔드를 배우거나 사용하지 않고도 성공적으로 작업할 수 있다. 그러나 백엔드는 특정 규모의 팀이 겪는 문제를 해결하는 데 도움을 줍니다. 개인으로 작업하는 경우, 백엔드를 사용하지 않고도 성공할 수 있다.
"local" Backend만 사용할 계획이라도, 백엔드를 배우는 것이 유용할 수 있다. 로컬 백엔드의 동작을 변경할 수 있기 때문이다.
처음으로 백엔드를 구성할 때(정의되지 않은 백엔드에서 명시적으로 구성하는 경우), Terraform은 상태를 새 백엔드로 마이그레이션할 옵션을 제공한다. 이를 통해 기존 상태를 잃지 않고 백엔드를 도입할 수 있다.
추가적으로 안전을 기하기 위해, 상태를 수동으로 백업하는 것이 항상 권장된다. 이는 terraform.tfstate 파일을 다른 위치로 단순히 복사함으로써 수행할 수 있다. 초기화 과정에서 백업이 생성되지만, 안전을 위해 항상 확인하는 것이 좋다.
Cloud Shell을 열고 main.tf를 만들고 프로젝트 아이디를 검색한다.
touch main.tf
gcloud config list --format 'value(core.project)'
Open Editor를 새로운 윈도우에 열고 main.tf을 수정한다.
provider "google" {
project = "# REPLACE WITH YOUR PROJECT ID"
region = "REGION"
}
resource "google_storage_bucket" "test-bucket-for-state" {
name = "# REPLACE WITH YOUR PROJECT ID"
location = "US"
uniform_bucket_level_access = true
}
backend를 추가한다.
terraform {
backend "local" {
path = "terraform/state/terraform.tfstate"
}
}
이 내용은 terraform.tfstate 파일을 terraform/state 디렉토리에서 참조한다. 다른 파일 경로를 지정하려면 path 변수를 변경하면 된다.
로컬 백엔드는 상태를 로컬 파일 시스템에 저장하고, 시스템 API를 사용하여 해당 상태를 잠그며, 작업을 로컬에서 수행한다.
Terraform은 사용하기 전에 구성된 백엔드를 초기화해야 한다. 이를 위해 terraform init 명령을 실행한다. terraform init 명령은 팀의 모든 구성원에 의해 모든 Terraform 구성에서 첫 번째 단계로 실행되어야 한다. 이 명령은 여러 번 안전하게 실행할 수 있으며, 백엔드 초기화를 포함하여 Terraform 환경에 필요한 모든 설정 작업을 수행한다.
init 명령은 다음과 같은 경우에 호출해야 합니다:
- 백엔드를 구성하는 새로운 환경에서
- 백엔드 구성 변경 시 (백엔드 유형 포함)
- 백엔드 구성을 완전히 제거할 때
이러한 정확한 경우를 기억할 필요는 없다. Terraform은 초기화가 필요할 때 이를 감지하고 상황에 맞는 오류 메시지를 표시한다. Terraform이 자동으로 초기화하지 않는 이유는 사용자로부터 추가 정보를 요구하거나 상태 마이그레이션 등의 작업을 수행해야 할 수 있기 때문이다.
다음 명령으로 초기화 > 적용 > state 보기
terraform init
terraform apply
terraform show
Cloud Storage 백엔드 추가
Cloud Storage 백엔드는 상태를 지정된 버킷의 설정 가능한 접두사에 객체로 저장한다.
이 백엔드는 state locking도 지원합니다. 이는 모든 상태를 쓸 수 있는 작업에 대해 상태를 잠근다.
이로 인해 다른 사용자가 잠금을 획득하여 상태가 손상되는 것을 방지한다.
상태 잠금은 상태를 쓸 수 있는 모든 작업에서 자동으로 발생한다. 이 과정이 진행되고 있다는 메시지는 표시되지 않는다.
만약 상태 잠금이 실패하면, Terraform은 계속 진행하지 않는다. 대부분의 명령에 대해 -lock 플래그를 사용하여 상태 잠금을 비활성화할 수 있지만, 이는 권장되지 않는다.
main.tf에 backend를 local에서 gcs로 변경한다. (이전에 생성한 버킷 이름)
terraform {
backend "gcs" {
bucket = "# REPLACE WITH YOUR BUCKET NAME"
prefix = "terraform/state"
}
}
backend를 다시 초기화하고 자동으로 상태를 마이그레이션 한다.
terraform init -migrate-state
Navigation menu > Cloud Storage > Buckets을 눌러 terraform/state/default.tfstate 파일이 버킷에 있는 것을 확인할 수 있다.
Terraform이 상태 파일을 통해 알고 있는 상태를 실제 인프라와 조정하는데 사용하기 위해 Refresh를 한다.
실제 인프라를 변경하는 것이 아닌 상태만 변경한다. 이로 인해 다음 계획 중에 변경 사항이 발생하거나 적용될 수 있다.
Cloud Console Storage Bucket으로 돌아와서 check box 옆에 name을 선택한다.
Labels 탭을 누르고 Add Label을 클릭하고 Key 1= key, Value 1= value로 세팅하고 저장한다.
다음 명령어로 Refresh하고 확인한다.
terraform refresh
terraform show
Bucket Storage를 삭제하기 위해 다시 backend를 local로 바꾸고 상태를 마이그레이션 한다.
terraform {
backend "local" {
path = "terraform/state/terraform.tfstate"
}
}
terraform init -migrate-state
main.tf 파일에서 force_destory = true를 google_storage_bucket, resource에 추가한다.
resource "google_storage_bucket" "test-bucket-for-state" {
name = "qwiklabs-gcp-02-b7d7c92a956e"
location = "US"
uniform_bucket_level_access = true
force_destroy = true
}
terraform을 적용하고 리소스를 삭제한다.
terraform apply
terraform destroy
Managing Terraform State - Task 2. Import Terraform configuration
기본 Terraform 워크플로우는 인프라를 완전히 Terraform으로 생성하고 관리하는 것이다.
- Terraform 구성 작성: 생성하려는 인프라를 정의하는 Terraform 구성을 작성한다.
- Terraform 계획 검토: 구성의 결과가 예상되는 상태와 인프라로 이어지는지 확인하기 위해 Terraform 계획을 검토한다.
- 구성 적용: 구성을 적용하여 Terraform 상태와 인프라를 생성한다.
Terraform으로 인프라를 생성한 후, 구성을 업데이트하고 변경 사항을 계획하고 적용할 수 있다.
결국, 더 이상 필요하지 않은 인프라는 Terraform을 사용하여 파괴할 수 있다.
하지만 Terraform으로 생성되지 않은 인프라를 관리해야 할 수도 있다. 이 문제를 해결하는 것이 Terraform import이다.
이는 지원되는 리소스를 Terraform 작업 공간의 상태로 로드한다.
하지만 import 명령은 인프라를 관리하기 위한 구성을 자동으로 생성하지 않는다.
따라서 기존 인프라를 Terraform으로 가져오는 것은 다음 스탭으로 진행된다.
- 가져올 기존 인프라 식별
- 인프라를 Terraform 상태로 가져오기
- 인프라에 맞는 Terraform 구성 작성
- Terraform 계획 검토하여 구성이 예상되는 상태 및 인프라와 일치하는지 확인
- 구성 적용하여 테라폼 상태 업데이트
테스트를 위해 다음을 진행한다.
Docker Container 만들기
docker run --name hashicorp-learn --detach --publish 8080:80 nginx:latest
docker ps
8080 포트로 접속해서 NGINX가 배포되었는지 확인한다.
Git Clone으로 레포지토리를 가져온다.
git clone https://github.com/hashicorp/learn-terraform-import.git
cd learn-terraform-import
Terraform을 초기화
terraform init
learn-terraform-import/main.tf에서 provider를 docker로 변경하고 host args를 삭제하거나 주석 처리한다.
provider "docker" {
# host = "npipe:////.//pipe//docker_engine"
}
learn-terraform-import/docker.tf로 이동해서 resource를 넣는다.
resource "docker_container" "web" {}
docker ps로 container 이름을 찾고 명령어를 통해 docker_container 리소스에 기존 docker 컨테이너를 연결한다.
terraform import docker_container.web $(docker inspect -f {{.ID}} hashicorp-learn)
# docker inspect -f {{.ID}} hashicorp-learn은 SHA256으로 컨테이너 ID를 반환한다.
Terraform을 확인한다.
terraform show
Terraform으로 인프라 생성 예측 결과를 본다. (이름을 안넣었기 때문에 에러 발생)
terraform plan
Terraform state을 docker.tf로 저장한다.
terraform show -no-color > docker.tf
다시 예측 결과를 본다.
terraform plan
테라폼은 사용되지 않는 인수('links')와 여러 읽기 전용 인수(ip_address, network_data, gateway, ip_prefix_length, id)에 대한 경고 및 오류를 표시한다.
이러한 읽기 전용 인수는 Terraform이 Docker 컨테이너에 대해 해당 상태로 저장하지만 Docker에서 내부적으로 관리하기 때문에 구성을 통해 설정할 수 없는 값이다. Terraform은 링크 인수를 구성으로 설정할 수 있지만, 이 인수는 더 이상 사용되지 않으며 향후 버전의 Docker 제공업체에서 지원하지 않을 수 있으므로 여전히 경고를 표시한다.
여기에 표시된 접근 방식은 테라폼 상태로 표시된 모든 속성을 로드하기 때문에 기본값과 동일한 값을 가진 선택적 속성이 구성에 포함됩니다. 어떤 속성이 선택적이며 기본값은 제공업체마다 다르며 provider documentation에 나열됩니다.
이러한 옵션 속성을 선택적으로 제거할 수 있다. image, name, port, ... 필요한 속성만 유지한다. 제거한 후 모습은 다음과 유사해야 한다.
resource "docker_container" "web" {
image = "sha256:87a94228f133e2da99cb16d653cd1373c5b4e8689956386c1c12b60a20421a02"
name = "hashicorp-learn"
ports {
external = 8080
internal = 80
ip = "0.0.0.0"
protocol = "tcp"
}
}
실제 인프라를 가져올 때는 제공업체 설명서를 참조하여 각 인수의 기능을 알아봐야 한다. 이렇게 하면 계획 단계에서 오류나 경고를 처리하는 방법을 결정하는 데 도움이 된다. 예를 들어, link 인수에 대한 설명서는 도커 제공업체 설명서에 있다.
terraform으로 오류가 해결되었는지 확인하고 없으면 적용한다.
terraform plan
terraform apply
또 다른 경우에 terraform의 import를 사용하지 않고 리소스를 Terraform의 제어 아래 둘 수 있다.
도커 이미지의 ID를 가져오고 docker.tf 파일에 리소스로 직접 지정한다.
docker image inspect <IMAGE-ID> -f {{.RepoTags}}
# docker.tf
resource "docker_image" "nginx" {
name = "nginx:latest"
}
Terraform을 적용한다.
terraform apply
docker_container.web 값을 새로운 이미지 리소스를 참조하도록 변경한다.
resource "docker_container" "web" {
image = docker_image.nginx.image_id
name = "hashicorp-learn"
ports {
external = 8080
internal = 80
ip = "0.0.0.0"
protocol = "tcp"
}
}
Terraform을 적용한다.
terraform apply
Terraform을 사용해 Docker container를 관리할 수 있다.
docker.tf 파일을 수정해서 port를 변경한다.
resource "docker_container" "web" {
name = "hashicorp-learn"
image = docker_image.nginx.image_id
ports {
external = 8081
internal = 80
ip = "0.0.0.0"
protocol = "tcp"
}
}
Terraform을 적용한다.
terraform apply
docker의 배포 상태를 확인한다.
docker ps
Terraform으로 컨테이너 배포를 삭제하고 확인한다.
terraform destroy
docker ps --filter "name=hashicorp-learn"