Terraform 사용 권장사항

이 문서에서는 여러 팀 멤버와 작업 스트림 간의 Terraform을 사용한 효율적인 개발을 위해 가이드라인과 권장사항을 제공합니다.

이 가이드는 Terraform 소개 내용이 아닙니다. Google Cloud에서 Terraform 사용에 대한 소개는 정보는 Terraform 시작하기를 참조하세요.

일반 스타일 및 구조 가이드라인

다음 권장사항에서는 Terraform 구성의 기본 스타일과 구조를 살펴봅니다. 이러한 권장사항은 재사용 가능한 Terraform 모듈 및 루트 구성에 적용됩니다.

표준 모듈 구조 따르기

  • Terraform 모듈은 표준 모듈 구조를 따라야 합니다.
  • 기본적으로 리소스가 있는 위치인 main.tf 파일로부터 모든 모듈을 시작합니다.
  • 모든 모듈에서 마크다운 형식에 README.md 파일을 포함합니다. README.md 파일에서 모듈에 대한 기본 문서를 포함합니다.
  • 각 예시에 대해 개별 하위 디렉터리를 사용해서 examples/ 폴더에 예시를 둡니다. 각 예시에 대해 자세한 README.md 파일을 포함합니다.
  • 자체 파일 및 network.tf, instances.tf, loadbalancer.tf와 같은 자세한 이름을 사용해서 리소스를 논리적 그룹으로 만듭니다.
    • 모든 리소스에 자체 파일을 제공하지 않습니다. 공유 목적에 따라 리소스를 그룹으로 묶습니다. 예를 들어 google_dns_managed_zonegoogle_dns_record_setdns.tf에 조합합니다.
  • 모듈의 루트 디렉터리에서 Terraform(*.tf) 및 저장소 메타데이터 파일(예: README.mdCHANGELOG.md)만 포함합니다.
  • 모든 추가 문서를 docs/ 하위 디렉터리에 둡니다.

이름 지정 규칙 채택

  • 단어 구분을 위한 밑줄을 사용해서 모든 구성 객체의 이름을 지정합니다. 이렇게 하면 리소스 유형, 데이터 소스 유형, 기타 사전 정의된 값에 대해 이름 지정 규칙을 일관적으로 사용할 수 있습니다. 인수 이름 지정에는 이 규칙이 적용되지 않습니다.

    다음을 권장합니다.

    resource "google_compute_instance" "web_server" {
      name = "web-server"
    }
    

    다음은 권장하지 않습니다.

    resource "google_compute_instance" "web-server" {
      name = "web-server"
    }
    
  • 단독 유형의 리소스 참조를 단순화하기 위해서는(예: 전체 모듈의 단일 부하 분산기) 리소스 이름을 main으로 지정합니다.

    • some_google_resource.my_special_resource.idsome_google_resource.main.id를 구분해서 기억하기 위해서는 정신적으로 추가적인 주의가 필요합니다.
  • 동일 유형의 리소스를 서로 구분하기 위해(예: primarysecondary) 의미 있는 리소스 이름을 제공합니다.

  • 리소스 이름을 단수로 만듭니다.

  • 리소스 이름에 리소스 유형을 반복하지 않습니다. 예를 들면 다음과 같습니다.

    다음을 권장합니다.

    resource "google_compute_global_address" "main" { ... }
    

    다음은 권장하지 않습니다.

    resource "google_compute_global_address" "main_global_address" { … }
    

변수를 신중하게 사용

  • variables.tf의 모든 변수를 선언합니다.
  • 변수 용도 또는 목적과 관련해서 자세한 이름을 변수에 지정합니다.
    • 디스크 크기 또는 RAM 크기와 같은 숫자 값을 나타내는 입력, 로컬 변수, 출력은 ram_size_gb와 같이 단위를 함께 사용해서 이름을 지정해야 합니다. Google Cloud API에는 표준 단위가 없으므로, 단위를 사용해서 변수 이름을 지정하여 구성 유지보수를 위한 예상 입력 단위를 명확하게 표현할 수 있습니다.
    • 스토리지 단위의 경우 바이너리(1,024의 거듭제곱) 단위 프리픽스(kibi, mebi, gibi)를 사용합니다. 다른 모든 측정 단위에 대해서는 십진수(1,000의 거듭제곱) 단위 프리픽스(kilo, mega, giga)를 사용합니다. 이러한 사용법은 Google Cloud 내에서의 사용법과 일치합니다.
    • 조건 논리를 단순화하기 위해 부울 변수에 명확한 이름을 지정합니다(예: enable_external_access).
  • 변수에는 설명이 있어야 합니다. 설명은 게시된 모듈의 자동 생성되는 문서에 자동으로 포함됩니다. 설명은 새로 투입되는 개발자를 위해 자세한 이름으로도 제공할 수 없는 추가적인 컨텍스트를 더해줍니다.
  • 변수에 정의된 유형을 지정합니다.
  • 필요한 경우 기본값을 제공합니다.
    • 디스크 크기와 같이 환경에 독립적인 값을 포함하는 변수의 경우 기본값을 제공합니다.
    • project_id와 같이 환경에 따라 달라지는 값을 포함하는 변수의 경우에는 기본값을 제공하지 않습니다. 이렇게 하면 호출 모듈이 의미 있는 값을 제공해야 합니다.
  • 변수를 비워 두어도 기본 API에서 거부되지 않는 경우에만 변수에 빈 기본값을 사용합니다(예: 빈 문자열 또는 목록).
  • 변수를 신중하게 사용합니다. 각 인스턴스 또는 환경에 따라 달라져야 하는 값만 매개변수화합니다. 변수를 노출할지 여부를 결정할 때는 해당 변수를 변경해야 하는 구체적인 사용 사례가 있어야 합니다. 변수가 필요할 가능성이 작을 때는 이를 노출하지 않습니다.
    • 기본값을 사용한 변수 추가는 하위 호환성을 갖습니다.
    • 변수 삭제는 하위 호환성을 갖지 않습니다.
    • 여러 장소에서 리터럴이 재사용되는 경우에는 이를 변수로 노출하지 않고 로컬 값을 사용할 수 있습니다.

출력 노출

  • 모든 출력을 outputs.tf 파일로 구성합니다.
  • 모든 출력에 의미 있는 설명을 제공합니다.
  • README.md 파일에 출력 설명을 기술합니다. terraform-docs와 같은 도구를 사용해서 커밋 시 설명을 자동 생성합니다.
  • 루트 모듈이 참조하거나 공유해야 할 수 있는 모든 유용한 값을 출력합니다. 특히 오픈소스 또는 매우 자주 사용되는 모듈의 경우 사용 가능성이 있는 모든 출력을 노출합니다.
  • 입력 변수를 통해 직접 출력을 전달하지 않습니다. 이렇게 하면 종속 항목 그래프에 올바르게 추가되지 않습니다. 암시적 종속 항목이 생성되도록 하려면 출력이 리소스의 속성을 참조하는지 확인합니다. 인스턴스에 대해 입력 변수를 직접 참조하는 대신 아래 표시된 대로 속성을 전달합니다.

    다음을 권장합니다.

    output "name" {
      description = "Name of instance"
      value       = google_compute_instance.main.name
    }
    

    다음은 권장하지 않습니다.

    output "name" {
      description = "Name of instance"
      value       = var.name
    }
    

데이터 소스 사용

  • 데이터 소스를 참조되는 리소스 옆에 둡니다. 예를 들어 인스턴스를 실행하는 데 사용할 이미지를 가져오는 경우에는 자체 파일에서 데이터 리소스를 수집하는 대신 해당 인스턴스와 함께 둡니다.
  • 데이터 소스 수가 많아지면 이를 전용 data.tf 파일로 이동할 수 있습니다.
  • 현재 환경을 기준으로 데이터를 가져오려면 변수 또는 리소스 보간을 사용합니다.

커스텀 스크립트 사용 제한

  • 스크립트는 필요한 경우에만 사용합니다. 스크립트를 통해 생성된 리소스 상태는 Terraform에서 고려되거나 관리되지 않습니다.
    • 가능하면 커스텀 스크립트 사용을 피합니다. Terraform 리소스에서 원하는 동작이 지원되지 않을 때만 스크립트를 사용합니다.
    • 사용된 커스텀 스크립트는 기존 항목에 대한 그리고 이상적으로는 지원 중단 계획까지 이유가 명확하게 기술되어 있어야 합니다.
  • Terraform은 로컬 실행 프로비저닝 도구를 포함하여 프로비저닝 도구를 통해 커스텀 스크립트를 호출할 수 있습니다.
  • Terraform에서 호출된 커스텀 스크립트는 scripts/ 디렉터리에 둡니다.

도우미 스크립트를 별도의 디렉터리에 포함

  • Terraform에서 호출되지 않는 도우미 스크립트는 helpers/ 디렉터리로 정리합니다.
  • 설명 및 예시 호출을 포함해서 도우미 스크립트 관련 내용을 README.md 파일에 기록합니다.
  • 도우미 스크립트에 인수가 사용될 경우 인수 검사 및 --help 출력을 제공합니다.

정적 파일을 별도의 디렉터리에 넣기

  • Terraform에서 참조되지만 실행되지는 않는 정적 파일(예: Compute Engine 인스턴스에 로드된 시작 스크립트)은 files/ 디렉터리에 정리해야 합니다.
  • HCL과 별개로 긴 HereDocs를 외부 파일에 둡니다. file() 함수를 사용하여 참조합니다.
  • Terraform templatefile 함수를 사용하여 읽혀지는 파일의 경우에는 .tftpl 파일 확장자를 사용합니다.
    • 템플릿은 templates/ 디렉터리에 두어야 합니다.

스테이트풀(Stateful) 리소스 보호

데이터베이스와 같은 스테이트풀(Stateful) 리소스에 대해 삭제 보호가 사용 설정되었는지 확인합니다. 예를 들면 다음과 같습니다.

resource "google_sql_database_instance" "main" {
  name = "primary-instance"
  settings {
    tier = "D0"
  }

  lifecycle {
    prevent_destroy = true
  }
}

기본 제공되는 형식 사용

모든 Terraform 파일은 terraform fmt 표준을 따라야 합니다.

표현식의 복잡성 제한

  • 개별 보간된 표현식의 복잡성을 제한합니다. 단일 표현식에 많은 함수가 필요하면 로컬 값을 사용해서 이를 여러 표현식으로 분할하는 것이 좋습니다.
  • 한 줄에 3항 연산을 두 개 이상 사용하지 마세요. 대신 로컬 변수를 여러 개 사용해서 논리를 쌓습니다.

조건부 값에 count 사용

리소스를 조건에 따라 인스턴스화하려면 count 메타 인수를 사용합니다. 예를 들면 다음과 같습니다.

variable "readers" {
  description = "..."
  type        = list
  default     = []
}

resource "resource_type" "reference_name" {
  // Do not create this resource if the list of readers is empty.
  count = length(var.readers) == 0 ? 0 : 1
  ...
}

사용자 지정 변수를 사용하여 리소스에 count 변수를 설정하는 방식은 가능한 한 아끼는 것이 좋습니다. 이러한 변수(project_id 등)에 리소스 속성이 제공되었고 해당 리소스가 아직 존재하지 않으면 Terraform이 계획을 생성할 수 없습니다. 대신 Terraform은 value of count cannot be computed 오류를 보고합니다. 이러한 경우에는 별도의 enable_x 변수를 사용해서 조건부 논리를 계산합니다.

반복되는 리소스에 for_each 사용

입력 리소스를 기준으로 여러 리소스 복사본을 만들려면 for_each 메타 인수를 사용합니다.

레지스트리에 모듈 게시

재사용 가능한 모듈

다시 사용할 모듈에 대해서는 이전 가이드라인 외에도 다음 가이드라인을 따릅니다.

모듈에서 필요한 API 활성화

Terraform 모듈은 google_project_service 리소스 또는 project_services 모듈을 사용하여 필요한 서비스를 활성화할 수 있습니다. API 활성화를 포함하면 데모가 더 쉬워집니다.

  • API 활성화가 모듈에 포함된 경우 기본적으로 true로 설정되는 enable_apis 변수를 노출하여 API 활성화를 사용 중지할 수 있어야 합니다.
  • API 활성화가 모듈에 포함된 경우에는 모듈의 여러 인스턴스를 사용할 때 이 속성으로 인해 문제가 발생할 수 있기 때문에 API 활성화가 disable_services_on_destroyfalse설정해야 합니다.

    예를 들면 다음과 같습니다.

    module "project-services" {
      source  = "terraform-google-modules/project-factory/google//modules/project_services"
      version = "~> 12.0"
    
      project_id  = var.project_id
      enable_apis = var.enable_apis
    
      activate_apis = [
        "compute.googleapis.com",
        "pubsub.googleapis.com",
      ]
      disable_services_on_destroy = false
    }
    

소유자 파일 포함

모든 공유 모듈의 경우 OWNERS 파일(또는 GitHub의 CODEOWNERS)을 포함하고 모듈 책임자를 기술합니다. pull 요청을 병합하려면 먼저 소유자가 이를 승인해야 합니다.

태그 지정된 버전 출시

일부 경우에는 모듈에 브레이킹 체인지가 필요하고 구성을 특정 버전으로 고정할 수 있도록 해당 효과를 사용자에게 알려야 합니다.

새 버전이 태그 지정되었거나 출시되었을 때 공유 모듈이 SemVer v2.0.0을 따르는지 확인합니다.

모듈을 참조할 때는 버전 제약조건을 사용해서 해당 버전으로 고정합니다. 예를 들면 다음과 같습니다.

module "gke" {
  source  = "terraform-google-modules/kubernetes-engine/google"
  version = "~> 20.0"
}

제공자 또는 백엔드 선언 안 함

공유 모듈은 제공자 또는 백엔드를 구성하지 않아야 합니다. 대신 루트 모듈에서 제공자 및 백엔드를 구성합니다.

공유 모듈의 경우 required_providers 블록에서 다음과 같이 필요한 최소 제공자 버전을 정의합니다.

terraform {
  required_providers {
    google = {
      source  = "hashicorp/google"
      version = ">= 4.0.0"
    }
  }
}

달리 입증되지 않은 한 새 제공자 버전이 작동한다고 가정합니다.

라벨을 변수로 노출

모듈 인터페이스를 통한 리소스 라벨 지정을 유연하게 수행합니다. 다음과 같이 빈 맵의 기본값을 labels 변수에 제공합니다.

variable "labels" {
  description = "A map of labels to apply to contained resources."
  default     = {}
  type        = "map"
}

모든 리소스에 대해 출력 노출

변수 및 출력을 사용해서 모듈과 리소스 사이의 종속 항목을 유추할 수 있습니다. 출력이 없으면 해당 Terraform 구성과 관련해서 사용자가 모듈을 올바르게 주문할 수 없습니다.

공유 모듈에 정의된 모든 리소스에 대해 리소스를 참조하는 출력을 하나 이상 포함합니다.

복잡한 논리에 인라인 하위 모듈 사용

  • 인라인 모듈을 사용하여 복잡한 Terraform 모듈을 더 작고 중복성이 제거된 공통 리소스로 관리할 수 있습니다.
  • modules/$modulename에 인라인 모듈을 둡니다.
  • 공유 모델 문서에 특별히 기술되어 있지 않은 한 인라인 모듈을 외부 모듈에 사용되지 않는 비공개 모듈로 취급합니다.
  • Terraform은 리팩터링된 리소스를 추적하지 않습니다. 최상위 모듈에서 일부 리소스로 시작하고 이를 하위 모듈에 푸시하면 Terraform이 모든 리팩터링된 리소스를 다시 만들려고 시도합니다. 이러한 동작을 해결하기 위해서는 리팩터링할 때 moved 블록을 사용합니다.
  • 내부 모듈에 정의된 출력은 자동으로 노출되지 않습니다. 내부 모듈의 출력을 공유하려면 이를 다시 내보냅니다.

Terraform 루트 모듈

루트 구성(루트 모듈)은 Terraform CLI를 실행하는 작업 디렉터리입니다. 루트 구성이 다음 표준(및 해당하는 경우 이전 Terraform 가이드라인)을 준수하는지 확인합니다. 루트 모듈에 대해 명시적인 권장사항이 있으면 일반 가이드라인보다 우선 적용됩니다.

각 루트 모듈의 리소스 수 최소화

동일한 디렉터리 및 상태로 너무 많은 리소스가 저장되어 단일 루트 구성이 너무 커지지 않도록 하는 것이 좋습니다. 특정 루트 구성의 모든 리소스는 Terraform이 실행될 때마다 새로고침됩니다. 이렇게 하면 단일 상태에 포함된 리소스가 너무 많은 경우 실행 속도가 느려질 수 있습니다. 일반 규칙: 단일 상태에 포함되는 리소스 수를 100개 미만(이상적으로는 몇십 개까지만)으로 제한하는 것이 좋습니다.

각 애플리케이션에 개별 디렉터리 사용

애플리케이션 및 프로젝트를 서로 독립적으로 관리하기 위해 각 애플리케이션 및 프로젝트의 리소스를 자체 Terraform 디렉터리에 둡니다. 서비스는 특정 애플리케이션 또는 공유 네트워킹과 같은 공통 서비스를 제공할 수 있습니다. 특정 서비스의 모든 Terraform 코드를 하나의 디렉터리(하위 디렉터리 포함)에 끼워 넣습니다.

애플리케이션을 환경별 하위 디렉터리로 분할

Google Cloud에서 서비스를 배포할 때 해당 서비스의 Terraform 구성을 2개의 최상위 디렉터리로 분할합니다. 하나는 서비스의 실제 구성이 포함된 modules 디렉터리이고 다른 하나는 각 환경의 루트 구성이 포함된 environments 디렉터리입니다.

-- SERVICE-DIRECTORY/
   -- OWNERS
   -- modules/
      -- <service-name>/
         -- main.tf
         -- variables.tf
         -- outputs.tf
         -- provider.tf
         -- README
      -- ...other…
   -- environments/
      -- dev/
         -- backend.tf
         -- main.tf

      -- qa/
         -- backend.tf
         -- main.tf

      -- prod/
         -- backend.tf
         -- main.tf

환경 디렉터리 사용

환경 간 코드 공유를 위해 모듈을 참조합니다. 일반적으로 서비스의 기본 공유 Terraform 구성이 포함된 서비스 모듈일 수 있습니다. 서비스 모듈에서 공통 입력을 하드 코딩하고 환경 특정 입력만 변수로 요구합니다.

각 환경 디렉터리에는 다음 파일이 포함되어야 합니다.

  • Terraform 백엔드 상태 위치(일반적으로 Cloud Storage)를 선언하는 backend.tf 파일
  • 서비스 모듈을 인스턴스화하는 main.tf 파일

각 환경 디렉터리(dev, qa, prod)는 기본 Terraform 작업공간에 해당하며 서비스 버전을 이 환경에 배포합니다. 이러한 작업공간은 환경별 리소스를 자체 컨텍스트로 격리합니다. 기본 작업공간만 사용합니다.

다음과 같은 이유로 환경 내에 CLI 작업공간이 여러 개 있는 것은 권장되지 않습니다.

  • 각 작업공간에서 구성을 검사하기 어려울 수 있습니다.
  • 여러 작업 공간에 대해 단일 공유 백엔드를 사용하는 것은 권장되지 않습니다. 공유 백엔드가 환경 분리에 사용되는 경우 단일 장애점이 되기 때문입니다.
  • 코드 재사용이 가능하지만 현재 작업공간 변수(예: terraform.workspace == "foo" ? this : that)에 따라 코드를 전환해야 하는 코드는 읽기가 어려워집니다.

자세한 내용은 다음을 참조하세요.

원격 상태를 통한 출력 노출

루트 모듈에서 다른 루트 모듈에 사용될 수 있는 출력 정보로 내보냅니다. 특히 원격 상태로 유용한 중첩된 모듈 출력을 다시 내보내야 합니다.

다른 Terraform 환경 및 애플리케이션은 루트 모듈 수준 출력만 참조할 수 있습니다.

원격 상태를 사용하여 루트 모듈 출력을 참조할 수 있습니다. 구성을 위해 다른 종속된 앱에서 사용하도록 허용하려면 서비스 엔드포인트와 관련된 원격 상태 정보로 내보냅니다.

환경 디렉터리에서 공유된 서비스 모듈을 호출할 때와 같은 일부 경우에는 다음과 같이 전체 하위 모듈을 다시 내보내는 것이 적합합니다.

output "service" {
  value       = module.service
  description = "The service module outputs"
}

부 제공자 버전으로 고정

루트 모듈에서 각 제공자를 선언하고 버전으로 고정합니다. 이렇게 하면 확실한 대상을 유지하면서도 새 패치 출시 버전으로 자동 업그레이드할 수 있습니다. 일관성을 위해 버전 파일의 이름을 versions.tf로 지정합니다.

terraform {
  required_providers {
    google = {
      source  = "hashicorp/google"
      version = "~> 4.0.0"
    }
  }
}

tfvars 파일에 변수 저장

루트 모듈의 경우 .tfvars 변수 파일을 사용하여 변수를 제공합니다. 일관성을 위해 변수 파일 이름을 terraform.tfvars로 지정합니다.

대체 var-files 또는 var='key=val' 명령줄 옵션을 사용하여 변수를 지정하지 마세요. 명령줄 옵션은 임시적이고 잊기 쉽습니다. 기본 변수 파일을 사용하는 것이 보다 예측 가능합니다.

.terraform.lock.hcl 파일 체크인

루트 모듈의 경우 .terraform.lock.hcl 종속 항목 잠금 파일을 소스 제어에 체크인해야 합니다. 이렇게 하면 지정된 구성의 제공업체 선택 항목의 변경사항을 추적하고 검토할 수 있습니다.

교차 구성 통신

Terraform을 사용할 때 발생하는 일반적인 문제는 여러 팀에서 관리될 수 있는 여러 Terraform 구성 간에 정보를 공유하는 방법에 관한 것입니다. 일반적으로 단일 구성 디렉터리(또는 심지어 단일 저장소)에 저장하지 않더라도 여러 구성 간에 정보가 공유될 수 있습니다.

여러 Terraform 구성 간에 권장되는 정보 공유 방법은 다른 루트 모듈을 참조하기 위해 원격 상태를 사용하는 것입니다. Cloud Storage 또는 Terraform Enterprise는 선호되는 상태 백엔드입니다.

Terraform에서 관리되지 않는 리소스를 쿼리하기 위해서는 Google 제공업체의 데이터 소스를 사용합니다. 예를 들어 데이터 소스를 사용해서 기본 Compute Engine 서비스 계정을 검색할 수 있습니다. 다른 Terraform 구성으로 관리되는 리소스를 쿼리할 때는 데이터 소스를 사용하지 않습니다. 이렇게 하면 리소스 이름 및 구조에 대한 암시적 종속 항목이 생성되고, 일반적인 Terraform 작업에 따라 의도치 않게 손상될 수 있습니다.

Google Cloud 리소스 작업

Terraform으로 Google Cloud 리소스를 프로비저닝하기 위한 권장사항은 Google에서 유지보수되는 Cloud Foundation Toolkit 모듈에 통합되어 있습니다. 이 섹션에서는 이러한 권장사항 중 일부를 반복해서 설명합니다.

가상 머신 이미지 만들기

일반적으로 Packer와 같은 도구를 사용하여 가상 머신을 생성하는 것이 좋습니다. 그러면 Terraform이 미리 만든 이미지를 사용하여 머신을 실행하기만 하면 됩니다.

미리 만든 이미지를 사용할 수 없으면 Terraform이 provisioner 블록을 사용해서 새 가상 머신을 구성 관리 도구로 전달할 수 있습니다. 이 방법은 가급적 사용을 피하고 최후의 수단으로만 사용하는 것이 좋습니다. 인스턴스와 연관된 오래된 상태를 삭제하기 위해서는 해체 논리가 필요한 프로비저닝 도구에서 when = destroy가 포함된 provisioner 블록을 사용해야 합니다.

Terraform은 인스턴스 메타데이터와 함께 구성 관리에 VM 구성 정보를 제공해야 합니다.

Identity and Access Management 관리

Terraform과 IAM 연관을 프로비저닝할 때는 여러 리소스를 사용할 수 있습니다.

  • google_*_iam_policy(예: google_project_iam_policy)
  • google_*_iam_binding(예: google_project_iam_binding)
  • google_*_iam_member(예: google_project_iam_member)

google_*_iam_policygoogle_*_iam_binding은 Terraform 리소스가 관련 리소스에 할당할 수 있는 권한에 대해 유일한 신뢰 소스로 사용되는 권한 있는 IAM 연관을 만듭니다.

Terraform 외부에서 권한이 변경되면 다음 실행 시 Terraform이 모든 권한을 덮어써서 구성에 정의된 대로 정책을 제공합니다. 특정 Terraform 구성으로 전적으로 관리되는 리소스의 경우에는 이 방식이 적합할 수 있습니다. 하지만 Google Cloud에서 자동으로 관리되는 역할이 삭제되어 일부 서비스 기능이 중단될 수 있습니다.

이를 방지하기 위해서는 google_*_iam_member 리소스를 직접 사용하거나 Google의 IAM 모듈을 사용하는 것이 좋습니다.

버전 제어

다른 코드 형식에서와 같이 이전 항목을 보존하고 쉽게 롤백할 수 있도록 버전 제어에 인프라 코드를 저장합니다.

기본 분기 전략 사용

Terraform 코드가 포함된 모든 저장소의 경우 기본적으로 다음 전략을 사용합니다.

  • main 분기는 기본 개발 분기이고 최신 승인 코드를 제공합니다. main 분기는 보호됩니다.
  • 개발은 main 분기 외부의 기능 및 버그 수정 분기에서 수행됩니다.
    • 특성 분기 이름을 feature/$feature_name으로 지정합니다.
    • 버그 수정 분기 이름을 fix/$bugfix_name으로 지정합니다.
  • 특성 또는 버그 수정이 완료되면 pull 요청을 사용해서 이를 다시 main 분기로 병합합니다.
  • 병합 충돌을 방지하기 위해 병합 전 분기 기준을 다시 설정합니다.

루트 구성에 환경 분기 사용

Google Cloud에 직접 배포되는 루트 구성이 포함된 저장소의 경우 안전한 출시 전략이 필요합니다. 각 환경에 대해 개별 분기를 사용하는 것이 좋습니다. 따라서 여러 분기 간 변경사항을 병합하여 Terraform 구성에 대한 변경사항을 승격할 수 있습니다.

각 환경의 개별 분기

포괄적인 가시성 허용

Terraform 소스 코드 및 저장소가 엔지니어링 조직 간에, 인프라 소유자(예: SRE)에 대해, 인프라 이해관계자(예: 개발자)에 대해 포괄적으로 표시되고 액세스될 수 있게 합니다. 그러면 인프라 이해관계자가 사용되는 인프라를 더 잘 이해할 수 있습니다.

인프라 이해관계자가 변경 요청 프로세스 중 병합 요청을 제출할 수 있도록 권장합니다.

보안 비밀 커밋 안 함

Terraform 구성을 포함하여 소스 제어에 보안 비밀을 커밋하지 않습니다. 대신 Secret Manager와 같은 시스템에 업로드하고 데이터 소스를 사용해서 참조합니다.

이러한 민감한 값은 결국 상태 파일에 포함될 수 있고 출력으로 노출될 수도 있다는 사실에 주의하세요.

팀 경계를 기반으로 저장소 구성

개별 디렉터리를 사용해서 리소스 간 논리적 경계를 관리할 수 있지만 조직 경계 및 물류에 따라 저장소 구조가 결정됩니다. 일반적으로 승인 및 관리 요구사항이 서로 다른 구성을 서로 다른 소스 제어 저장소로 분리한다는 설계 원칙을 따르세요. 이 원칙을 설명하기 위해 사용 가능한 일부 저장소 구성은 다음과 같습니다.

  • 하나의 중앙 저장소: 이 모델에서는 모든 Terraform 코드가 단일 플랫폼팀에 의해 중앙에서 관리됩니다. 이 모델은 모든 클라우드 관리를 책임지고 다른 팀에서 요청된 변경사항을 승인하는 전용 인프라팀이 있는 경우에 가장 적합합니다.

  • 팀 저장소: 이 모듈에서는 각 팀이 자신의 Terraform 저장소를 책임지고 자신이 소유한 인프라와 관련된 모든 것을 관리합니다. 예를 들어 보안팀은 모든 보안 제어가 관리되는 저장소를 갖고 있고 애플리케이션팀은 애플리케이션 배포 및 관리를 위해 자체 Terraform 저장소를 갖고 있습니다.

    팀 경계에 따라 저장소를 관리하는 것이 대부분의 엔터프라이즈 시나리오에서 가장 적합한 구조입니다.

  • 분리된 저장소: 이 모델에서는 각 논리적 Terraform 구성요소가 자체 저장소로 분할됩니다. 예를 들어 네트워킹에 전용 저장소가 있고 프로젝트 만들기 및 관리를 위해 별도의 프로젝트 팩토리 저장소가 있을 수 있습니다. 이 방식은 업무 책임이 다른 팀으로 전환되는 경우가 많은 높은 분산 환경에서 가장 적합합니다.

샘플 저장소 구조

이러한 원칙을 조합해서 Terraform 구성을 서로 다른 저장소 유형으로 분할할 수 있습니다.

  • 기본
  • 애플리케이션 및 팀별
기본 저장소

폴더 또는 조직 IAM과 같은 중요한 중앙 구성요소가 포함된 기본 저장소입니다. 이 저장소는 중앙 클라우드팀에서 관리될 수 있습니다.

  • 이 저장소에서는 각 주요 구성요소(예: 폴더, 네트워크 등)에 대한 디렉터리를 포함합니다.
  • 구성요소 디렉터리에서는 앞에서 설명한 디렉터리 구조 가이드에 따라 각 환경에 대해 개별 폴더를 포함합니다.

기본 저장소 구조

애플리케이션 및 팀별 저장소

각 팀이 고유한 애플리케이션별 Terraform 구성을 관리할 수 있도록 애플리케이션 및 팀별 저장소를 개별적으로 배포합니다.

애플리케이션 및 팀별 저장소 구조

운영

인프라를 안전하게 유지하기 위해서는 Terraform 업데이트를 적용하기 위한 안정적이고 안전한 프로세스가 필요합니다.

항상 먼저 계획

Terraform 실행을 위해 항상 먼저 계획을 생성합니다. 출력 파일에 계획을 저장합니다. 인프라 소유자가 승인한 다음 계획을 실행합니다. 개발자가 변경사항에 대해 로컬 프로토타입을 만들 때도 계획을 생성하고 계획을 적용하기 전 추가, 수정, 삭제할 리소스를 검토해야 합니다.

자동화된 파이프라인 구현

일관적인 실행 컨텍스트를 보장하기 위해 자동화된 도구 사용을 통해 Terraform을 실행합니다. Jenkins와 같은 빌드 시스템을 이미 사용 중이고 넓게 채택된 경우에는 이를 사용해서 terraform planterraform apply 명령어를 자동으로 실행합니다. 기존 시스템을 사용할 수 없으면 Cloud Build 또는 Terraform Cloud를 채택합니다.

지속적 통합을 위한 서비스 계정 사용자 인증 정보 사용

CI/CD 파이프라인의 한 머신에서 Terraform을 실행할 때는 파이프라인을 실행하는 서비스로부터 서비스 계정 사용자 인증 정보가 상속됩니다. 가능한 모든 경우에 Google Cloud에서 CI 파이프라인을 실행합니다. Cloud Build, Google Kubernetes Engine, Compute Engine이 서비스 계정 키를 다운로드하지 않아도 사용자 인증 정보를 삽입하기 때문입니다.

Google Cloud 외부에서 실행되는 파이프라인의 경우 서비스 계정 키를 다운로드하지 않고 워크로드 아이덴티티 제휴를 사용하여 사용자 인증 정보를 가져오는 것이 좋습니다.

기존 리소스 가져오기 방지

가능한 경우 기존 리소스 가져오기를 방지합니다(terraform import 사용). 이렇게 하면 수동으로 생성된 리소스의 출처 및 구성을 완전히 이해하는 것이 어려울 수 있습니다. 대신 Terraform을 통해 새 리소스를 만들고 이전 리소스를 삭제하는 것이 좋습니다.

이전 리소스 삭제로 인해 반복 업무가 크게 증가할 경우에는 명시적 승인과 함께 terraform import 명령어를 사용합니다. 리소스를 Terraform으로 가져온 후에는 Terraform을 사용해서 명시적으로 관리합니다.

Google은 Google Cloud 리소스를 Terraform 상태로 가져오기 위해 사용할 수 있는 도구를 제공합니다. 자세한 내용은 Terraform 상태로 Google Cloud 리소스 가져오기를 참조하세요.

Terraform 상태를 수동으로 수정 안 함

Terraform 상태 파일은 Terraform 구성과 Google Cloud 리소스 사이의 매핑을 유지관리하는 데 중요합니다. 파일이 손상되면 중요한 인프라 문제로 이어질 수 있습니다. Terraform 상태 수정이 필요할 때는 terraform state 명령어를 사용합니다.

정기적으로 버전 고정 검토

버전 고정은 안정성을 보장하지만 버그 수정 및 기타 향상 기능을 구성에 도입하는 데 방해가 됩니다. 따라서 Terraform, Terraform 제공업체, 모듈에 대해 버전 고정을 정기적으로 검토합니다.

이 프로세스를 자동화하려면 Dependabot과 같은 도구를 사용합니다.

로컬 실행 시 애플리케이션 기본 사용자 인증 정보 사용

개발자는 Terraform 구성에 따라 로컬 반복을 수행할 때 애플리케이션 기본 사용자 인증 정보 생성을 위해 gcloud auth application-default login을 실행하여 인증해야 합니다. 다운로드한 키는 관리 및 보안이 더 어려우므로, 서비스 계정 키를 다운로드하지 마세요.

Terraform에 별칭 설정

로컬 개발을 쉽게 할 수 있도록 명령어 셸 프로필에 별칭을 추가할 수 있습니다.

  • alias tf="terraform"
  • alias terrafrom="terraform"

보안

Terraform은 작동을 위해 클라우드 인프라에 대해 민감한 액세스가 필요합니다. 여기에 표시된 보안 권장사항에 따라 관련된 위험을 최소화하고 전반적인 클라우드 보안 수준을 향상시킬 수 있습니다.

원격 상태 사용

Google Cloud 고객의 경우 Cloud Storage 상태 백엔드를 사용하는 것이 좋습니다. 이 접근 방식은 팀 협업을 위해 상태를 잠급니다. 또한 상태 및 모든 잠재적으로 민감한 정보를 버전 제어와 분리합니다.

빌드 시스템 및 권한이 높은 관리자만 원격 상태에 사용되는 버킷에 액세스할 수 있는지 확인합니다.

개발 상태를 소스 제어에 실수로 커밋하지 않도록 방지하기 위해 Terraform 상태 파일에 대해 gitignore를 사용합니다.

상태 암호화

Google Cloud 버킷이 저장 상태에서 암호화되지만 고객 제공 암호화 키를 사용해서 추가적인 보호 레이어를 제공할 수 있습니다. 이렇게 하려면 GOOGLE_ENCRYPTION_KEY 환경 변수를 사용합니다. 상태 파일에는 보안 비밀을 사용하지 않아야 하지만 추가적인 방어 수단으로서 항상 상태를 암호화합니다.

상태에 보안 비밀 저장 안 함

Terraform에는 상태 파일에서 보안 비밀 값을 일반 텍스트로 저장하는 많은 리소스 및 데이터 제공자가 있습니다. 가능한 경우 상태에 보안 비밀을 저장하지 않아야 합니다. 다음은 보안 비밀을 일반 텍스트로 저장하는 몇 가지 제공자 예시입니다.

민감한 출력 표시

민감한 값 암호화를 수동으로 시도하는 대신 민감한 상태 관리를 위해 Terraform에서 기본 제공되는 지원을 사용합니다. 민감한 값을 출력에 내보낼 때는 값이 민감한으로 표시되었는지 확인합니다.

책임 분리 보장

사용자가 액세스할 수 없는 자동화된 시스템에서 Terraform을 실행할 수 없으면 권한 및 디렉터리를 분리하여 책임 분리를 준수합니다. 예를 들어 네트워크 프로젝트는 해당 액세스 권한이 이 프로젝트로 제한되는 네트워크 Terraform 서비스 계정 또는 사용자에 해당합니다.

적용 전 검사 실행

자동화된 파이프라인에서 Terraform을 실행할 때는 적용하기 전 gcloud terraform vet와 같은 도구를 사용해서 정책에 따라 계획 출력을 확인합니다. 이렇게 하면 작업을 수행하기 전 보안 회귀를 감지할 수 있습니다.

지속적 감사 실행

terraform apply 명령어가 실행된 후 자동화된 보안 확인을 실행합니다. 이러한 확인 작업은 인프라가 안전하지 않은 상태로 전환되지 않도록 보장하는 데 도움이 될 수 있습니다. 다음 도구는 이 유형의 확인 작업을 위해 선택할 수 있는 옵션입니다.

테스트

Terraform 모듈 및 구성을 테스트하기 위해서는 경우에 따라 애플리케이션 코드를 테스트할 때와 다른 패턴 및 규칙을 따라야 합니다. 애플리케이션 코드를 테스트할 때는 주로 애플리케이션 자체의 비즈니스 논리를 테스트하지만 인프라 코드를 완전히 테스트하기 위해서는 프로덕션 오류 위험을 최소화하기 위해 실제 클라우드 리소스를 배포해야 합니다. Terraform 테스트를 실행할 때는 몇 가지 사항에 유의해야 합니다.

  • Terraform 테스트를 실행하면 실제 인프라가 생성, 수정, 삭제되므로, 테스트 수행에 시간과 비용이 발생할 수 있습니다.
  • 엔드 투 엔드 아키텍처에 대해 순전히 단위 테스트를 실행할 수 없습니다. 가장 좋은 방법은 아키텍처를 여러 모듈로 나누고 이를 개별적으로 테스트하는 것입니다. 이 방법은 빠른 테스트 런타임으로 인한 반복적인 개발 시간이 단축되고, 각 테스트에 대한 비용이 감소되고, 제어할 수 없는 요소들로 인한 테스트 실패 가능성이 줄어든다는 이점이 있습니다.
  • 가능한 경우 상태 재사용을 방지합니다. 다른 구성과 함께 데이터를 공유하지만 이상적으로는 각 테스트를 독립적으로 수행하고 테스트 간 상태를 재사용하지 않아야 할 경우가 있습니다.

비용이 낮은 테스트 방법 먼저 사용

Terraform 테스트를 위해 사용할 수 있는 방법은 여러 가지입니다. 비용, 실행 시간, 깊이 순으로 다음 방법들이 포함됩니다.

  • 정적 분석: 컴파일러, 린터, 테스트 실행과 같은 도구를 사용해서 리소스를 배포하지 않고 구성의 구문 및 구조를 테스트합니다. 이렇게 하려면 terraform validate를 사용합니다.
  • 모듈 통합 테스트: 모듈이 올바르게 작동하는지 확인하기 위해 격리된 상태로 개별 모듈을 테스트합니다. 모듈의 통합 테스트에는 테스트 환경에 모듈을 배포하고 예상 리소스가 생성되는지 확인하는 과정이 포함됩니다. 다음과 같이 테스트를 쉽게 만들 수 있게 해주는 몇 가지 테스트 프레임워크가 있습니다.
  • 엔드 투 엔드 테스트: 통합 테스트 방식을 전체 환경으로 확장하여 여러 모듈이 함께 작동하는지 확인할 수 있습니다. 이 방식에서는 아키텍처를 구성하는 모든 모듈을 새로운 테스트 환경에 배포합니다. 이상적으로 테스트 환경이 프로덕션 환경과 가능한 한 비슷해야 합니다. 비용이 많이 들지만 변경사항으로 인해 프로덕션 환경이 중단되지 않는다는 가장 높은 신뢰도를 얻을 수 있습니다.

소규모로 시작

테스트가 서로 반복적으로 기초로 사용되는지 확인합니다. 빠른 실패 방식에 따라 먼저 소규모로 테스트를 실행하고 점차 더 복잡한 테스트를 만듭니다.

프로젝트 ID 및 리소스 이름 무작위 구성

이름 충돌을 방지하기 위해 해당 구성에서 프로젝트 ID가 전역적으로 고유하고 각 프로젝트 내에서 리소스 이름이 겹치지 않는지 확인합니다. 이렇게 하려면 리소스에 네임스페이스를 사용합니다. Terraform에는 이를 위한 무작위 제공자가 기본 제공됩니다.

테스트에 개별 환경 사용

테스트 중에는 많은 리소스가 생성 및 삭제됩니다. 리소스 삭제 중 삭제 사고가 발생하지 않도록 테스트 환경이 개발 또는 프로덕션 프로젝트와 격리되어 있는지 확인해야 합니다. 가장 좋은 방법은 테스트마다 프로젝트 또는 폴더를 새로 만드는 것입니다. 잘못된 구성을 방지하기 위해 각 테스트를 실행할 때마다 서비스 계정을 만드는 것이 좋습니다.

모든 리소스 삭제

인프라 코드를 테스트하기 위해서는 실제 리소스를 배포해야 합니다. 비용이 청구되지 않도록 방지하려면 삭제 단계를 구현해야 합니다.

특정 구성에서 관리되는 모든 원격 객체를 삭제하려면 terraform destroy 명령어를 사용합니다. 일부 테스트 프레임워크에는 삭제 단계가 기본 제공됩니다. 예를 들어 Terratest를 사용하는 경우 테스트에 defer terraform.Destroy(t, terraformOptions)를 추가합니다. Kitchen-Terraform을 사용할 경우에는 terraform kitchen delete WORKSPACE_NAME을 사용해서 작업공간을 삭제합니다.

terraform destroy 명령어를 실행한 후 추가 삭제 절차도 실행해서 Terraform으로 삭제하지 못한 모든 리소스를 삭제합니다. 이렇게 하려면 테스트 실행에 사용된 모든 프로젝트를 삭제하거나 project_cleanup와 같은 도구를 사용합니다.

테스트 런타임 최적화

테스트 실행 시간을 최적화하려면 다음 방법을 따릅니다.

  • 테스트를 병렬로 실행합니다. 일부 테스트 프레임워크에서는 여러 Terraform 테스트의 동시 실행이 지원됩니다.
    • 예를 들어 Terratest의 경우 테스트 기능 정의 후 t.Parallel()을 추가할 수 있습니다.
  • 단계별로 테스트합니다. 개별적으로 테스트할 수 있는 독립적인 구성으로 테스트를 구분합니다. 이렇게 하면 테스트를 실행할 때 모든 단계를 진행할 필요가 없고 반복적인 개발 주기를 빠르게 진행할 수 있습니다.
    • 예를 들어 Kitchen-Terraform에서는 테스트를 개별 모음으로 분할합니다. 반복 시에는 각 모음을 독립적으로 실행합니다.
    • 마찬가지로 Terratest를 사용할 때는 stage(t, STAGE_NAME, CORRESPONDING_TESTFUNCTION)을 사용해서 테스트의 각 단계를 래핑합니다. 실행할 테스트를 나타내는 환경 변수를 설정합니다. 예를 들면 SKIPSTAGE_NAME="true"입니다.
    • 청사진 테스트 프레임워크에서는 단계별 실행이 지원됩니다.

다음 단계