본 포스트는 2024년 Google Study Jam을 공부하면서 개인적으로 내용을 정리한 포스트 입니다.
Interact with Terraform Modules - What is a Terraform module
Terraform 모듈이란?
Terraform 모듈은 단일 디렉토리에 있는 Terraform 구성 파일의 집합이다. 하나 이상의 .tf 파일로 구성된 단순한 구성도 모듈로 간주된다. 이러한 디렉토리에서 직접 Terraform 명령을 실행하면 이를 루트 모듈로 간주한다. 따라서 모든 Terraform 구성은 모듈의 일부로 볼 수 있다.
예를 들어, 다음과 같은 간단한 Terraform 구성 파일 세트가 있을 수 있다.
├── LICENSE
├── README.md
├── main.tf
├── variables.tf
├── outputs.tf
- LICENSE: 이 파일은 모듈이 배포될 라이센스를 포함하고 있다. 모듈을 공유할 때, LICENSE 파일은 사용자가 해당 모듈의 이용 조건을 알 수 있도록 해준다. Terraform 자체는 이 파일을 사용하지 않는다.
- README.md: 이 파일은 모듈을 사용하는 방법에 대한 문서를 마크다운 형식으로 포함하고 있다. Terraform은 이 파일을 사용하지 않지만, Terraform Registry와 GitHub과 같은 서비스는 모듈의 Terraform Registry 또는 GitHub 페이지를 방문하는 사람들에게 이 파일의 내용을 표시한다.
- main.tf: 이 파일은 모듈의 주요 구성 세트를 포함하고 있다. 다른 구성 파일을 생성하고 프로젝트에 맞게 조직할 수도 있다.
- variables.tf: 이 파일은 모듈의 변수 정의를 포함한다. 다른 사용자가 모듈을 사용할 때, 변수는 모듈 블록의 인자로 설정됩니다. 모든 Terraform 값은 정의되어야 하므로 기본값이 없는 변수는 필수 인자가 된다. 기본값이 있는 변수는 모듈 인자로 제공될 수 있으며, 이 경우 기본값을 덮어쓰게 된다.
- outputs.tf: 이 파일은 모듈의 출력 정의를 포함한다. 모듈의 출력은 모듈을 사용하여 구성에서 사용할 수 있게 되며, 인프라의 특정 부분에 대한 정보를 구성의 다른 부분으로 전달하는 데 자주 사용된다.
이 경우, minimal-module 디렉토리 내에서 Terraform 명령을 실행하면 해당 디렉토리의 내용이 루트 모듈로 간주됩니다.
모듈 호출
- Terraform 명령은 일반적으로 현재 작업 디렉토리의 구성 파일만 직접 사용한다. 그러나 구성에서는 module 블록을 사용하여 다른 디렉토리의 모듈을 호출할 수 있다. Terraform이 module 블록을 만나면 해당 모듈의 구성 파일을 로드하고 처리한다.
- 다른 구성에 의해 호출되는 모듈은 때때로 해당 구성의 "자식 모듈"이라고 불린다.
로컬 및 원격 모듈
- 모듈은 로컬 파일 시스템이나 원격 소스에서 로드할 수 있다. Terraform은 Terraform Registry, 대부분의 버전 관리 시스템, HTTP URL, Terraform Cloud 또는 Terraform Enterprise의 개인 모듈 레지스트리와 같은 다양한 원격 소스를 지원한다.
모듈 모범 사례
- Terraform 모듈은 대부분의 프로그래밍 언어에서 발견되는 라이브러리, 패키지 또는 모듈의 개념과 유사하며, 동일한 많은 이점을 제공한다. 거의 모든 비트리비얼 컴퓨터 프로그램과 마찬가지로, 실제 Terraform 구성은 언급된 이점을 제공하기 위해 거의 항상 모듈을 사용하는 것이 좋다.
- 모든 Terraform 사용자에게 다음의 모범 사례를 따르며 모듈을 사용하는 것이 권장된다:
- 모듈에 대한 계획을 가지고 구성 작성을 시작하세요. 약간 복잡한 Terraform 구성을 단일 사용자가 관리하는 경우에도 모듈을 사용하는 이점이 이를 적절히 사용하는 데 소요되는 시간보다 크다.
- 로컬 모듈을 사용하여 코드를 조직하고 캡슐화하세요. 원격 모듈을 사용하거나 배포하지 않더라도, 처음부터 모듈 단위로 구성을 조직하면 인프라가 복잡해짐에 따라 구성의 유지 관리 및 업데이트 부담이 크게 줄어든다.
- 유용한 모듈을 찾기 위해 공개 Terraform Registry를 사용하길 권장. 이렇게 하면 다른 사람들의 작업을 신뢰하고 빠르게 구성할 수 있다.
- 팀과 모듈을 게시하고 공유해야 한다. 대부분의 인프라는 여러 사람이 관리하며, 모듈은 팀이 인프라를 생성하고 유지 관리하는 데 중요한 도구이다. 앞서 언급한 바와 같이, 모듈은 공개 또는 비공식적으로 게시할 수 있다.
Interact with Terraform Modules - Task 1. Use modues from the Registry
Terraform Network module에 대한 Terraform Registry page를 연다.
모듈을 호출할 때 source 인수가 필요하다. 이 예제에서는 Terraform이 주어진 문자열과 일치하는 모듈을 Terraform Registry에서 검색한다. 모듈의 소스는 URL이나 로컬 파일 경로를 사용할 수도 있다. 가능한 모듈 소스의 목록은 Terraform 문서를 참조.
여기에서 보여주는 다른 인수는 version이다. 지원되는 소스에 대해 버전은 어떤 버전 또는 버전들을 로드할지 정의할 수 있게 해준다. 이 실습에서는 사용하는 모듈의 정확한 버전 번호를 지정하게 된다. 모듈 문서에서 버전을 지정하는 다른 방법에 대해 읽어볼 수 있다.
모듈 블록에 대한 다른 인수는 모듈에 대한 입력 변수로 처리된다.
Cloud Shell에서 GitHub repository으로 예제를 클론하고 v6.0.1 branch로 스위치한다.
git clone https://github.com/terraform-google-modules/terraform-google-network
cd terraform-google-network
git checkout tags/v6.0.1 -b v6.0.1
Open Editor를 열고 terraform-google-network/examples/simple_project/main.tf 파일을 연다.
module "test-vpc-module"은 나머지 인프라에 네트워킹 서비스를 제공하는 VPC를 정의한다.
모듈 입력 변수 값 설정
- 일부 입력 변수는 필수입니다. 이는 모듈이 기본값을 제공하지 않음을 의미하며, Terraform이 올바르게 실행되기 위해서는 명시적인 값을 제공해야 합니다.
- "test-vpc-module" 블록 내에서 설정하는 입력 변수를 검토하세요. 이러한 입력 변수 각각은 Terraform Registry에 문서화되어 있습니다. 이 모듈의 필수 입력 항목은 다음과 같습니다:
- network_name: 생성할 네트워크의 이름
- project_id: 이 VPC가 생성될 프로젝트의 ID
- subnets: 생성할 서브넷의 목록
- "test-vpc-module" 블록 내에서 설정하는 입력 변수를 검토하세요. 이러한 입력 변수 각각은 Terraform Registry에 문서화되어 있습니다. 이 모듈의 필수 입력 항목은 다음과 같습니다:
- 대부분의 모듈을 사용하기 위해서는 모듈 구성에 입력 변수를 전달해야 합니다. 모듈을 호출하는 구성은 해당 입력 값을 설정할 책임이 있으며, 이 값들은 모듈 블록에 인자로 전달됩니다. source와 version 외에도, 대부분의 모듈 블록 인자는 변수 값을 설정합니다.
- Google Cloud 네트워크 모듈의 Terraform Registry 페이지에서 Inputs 탭은 해당 모듈이 지원하는 모든 input variables를 설명합니다.
루트 입력 변수 정의
프로젝트 ID 검색
gcloud config list --format 'value(core.project)'
Open Editor에서 variables.tf를 열고 파일 수정
variable "project_id" {
description = "The project ID to host the network in"
default = "FILL IN YOUR PROJECT ID HERE"
}
variable "network_name" {
description = "The name of the VPC network being created"
default = "example-vpc"
}
main.tf 수정
module "test-vpc-module" {
...
project_id = var.project_id
network_name = var.network_name
...
# 35 ~ 40 line
subnets = [
{
subnet_name = "subnet-01"
subnet_ip = "10.10.10.0/24"
subnet_region = "REGION"
},
{
subnet_name = "subnet-02"
subnet_ip = "10.10.20.0/24"
subnet_region = "REGION"
subnet_private_access = "true"
subnet_flow_logs = "true"
},
{
subnet_name = "subnet-03"
subnet_ip = "10.10.30.0/24"
subnet_region = "REGION"
...
..
}
루트 출력 값 정의
- 모듈은 또한 출력 값(output values)을 가지며, 이는 output 키워드를 사용하여 모듈 내에서 정의된다. 이러한 출력 값은 module.<MODULE NAME>.<OUTPUT NAME> 형식으로 접근할 수 있다. 입력 변수와 마찬가지로, 모듈 출력은 Terraform Registry의 outputs 탭에 나열되어 있다.
- 모듈 출력은 일반적으로 구성의 다른 부분에 전달되거나 루트 모듈에서 출력으로 정의된다. 이 실습에서는 두 가지 용도를 모두 확인할 수 있다.
- 구성 디렉토리 내의 outputs.tf 파일로 이동, 해당 파일이 다음 내용을 포함하고 있는지 확인한다.
Provision infrastructure
Cloud shell에 simple_project 디렉토리로 전환하고 Terraform 초기화하고 적용.
cd ~/terraform-google-network/examples/simple_project
terraform init
terraform apply
모듈 작동 방식 이해
새로운 모듈을 처음 사용할 때는 terraform init 또는 terraform get 명령을 실행하여 모듈을 설치해야 한다. 이 명령 중 하나가 실행되면, Terraform은 구성의 작업 디렉토리 내의 .terraform/modules 디렉토리에 새로운 모듈을 설치한다. 로컬 모듈의 경우, Terraform은 모듈 디렉토리에 대한 심볼릭 링크를 생성한다. 이로 인해 로컬 모듈에 대한 변경 사항은 즉시 반영되며, terraform get을 다시 실행할 필요가 없다.
Clean up your infrastructure
만들었던 인프라 파괴
terraform destroy
리소스를 삭제한 후 terraform-google-network 폴더를 삭제
cd ~
rm -rd terraform-google-network -f
Interact with Terraform Modules - Task 2. Build a module
모든 Terraform 구성은 유연하고 재사용 가능하며 구성을 구성할 수 있도록 설계하는 데 도움이 되므로 모듈로 사용할 수 있다는 가정 하에 작성하는 것이 좋다.
Terraform 모든 구성을 모듈로 처리한다. Terraform Commands, Terraform Cloud, Terraform Enterprise를 사용하여 Terraform을 원격으로 실행하는 경우, Terraform 구성이 포함된 대상 디렉토리를 루트 모듈로 처리된다.
- terraform.tfstate 및 terraform.tfstate.backup 파일: 이 파일들은 Terraform 상태를 포함하고 있으며, Terraform이 구성과 프로비저닝된 인프라 간의 관계를 추적하는 데 사용된다. 이 파일들은 특정 Terraform 인스턴스에 관련된 것이므로, 모듈과 함께 배포할 필요가 없다.
- .terraform 디렉토리: 이 디렉토리는 인프라를 프로비저닝하는 데 사용되는 모듈과 플러그인을 포함한다. 이 파일들은 특정 Terraform 인스턴스에만 해당하며, .tf 파일로 정의된 인프라 구성과는 관련이 없다.
- .tfvars 파일: 이 파일들은 모듈과 함께 배포할 필요가 없다. 모듈 입력 변수는 구성 내의 모듈 블록에 대한 인자를 통해 설정되기 때문이다. 만약 모듈을 독립적인 Terraform 구성으로 사용하고 있다면, 그 경우에만 배포할 수 있다.
Create a module
새 모듈을 위해 디렉토리를 만든다.
cd ~
touch main.tf
mkdir -p modules/gcs-static-website-bucket
모듈 디렉토리로 이동하고 다음 3개의 빈 파일을 만든다.
cd modules/gcs-static-website-bucket
touch website.tf variables.tf outputs.tf
README를 작성한다.
tee -a README.md <<EOF
# GCS static website bucket
This module provisions Cloud Storage buckets configured for static website hosting.
EOF
LICENSE를 작성한다.
tee -a LICENSE <<EOF
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
EOF
모듈 디렉토리 구조
main.tf
modules/
└── gcs-static-website-bucket
├── LICENSE
├── README.md
├── website.tf
├── outputs.tf
└── variables.tf
이 클라우드 스토리지 버킷 리소스를 modules/gcs-static-website-bucket 디렉토리 내의 website.tf 파일에 추가한다.
resource "google_storage_bucket" "bucket" {
name = var.name
project = var.project_id
location = var.location
storage_class = var.storage_class
labels = var.labels
force_destroy = var.force_destroy
uniform_bucket_level_access = true
versioning {
enabled = var.versioning
}
dynamic "retention_policy" {
for_each = var.retention_policy == null ? [] : [var.retention_policy]
content {
is_locked = var.retention_policy.is_locked
retention_period = var.retention_policy.retention_period
}
}
dynamic "encryption" {
for_each = var.encryption == null ? [] : [var.encryption]
content {
default_kms_key_name = var.encryption.default_kms_key_name
}
}
dynamic "lifecycle_rule" {
for_each = var.lifecycle_rules
content {
action {
type = lifecycle_rule.value.action.type
storage_class = lookup(lifecycle_rule.value.action, "storage_class", null)
}
condition {
age = lookup(lifecycle_rule.value.condition, "age", null)
created_before = lookup(lifecycle_rule.value.condition, "created_before", null)
with_state = lookup(lifecycle_rule.value.condition, "with_state", null)
matches_storage_class = lookup(lifecycle_rule.value.condition, "matches_storage_class", null)
num_newer_versions = lookup(lifecycle_rule.value.condition, "num_newer_versions", null)
}
}
}
}
variabes.tf 파일로 이동해 코드를 추가한다.
variable "name" {
description = "The name of the bucket."
type = string
}
variable "project_id" {
description = "The ID of the project to create the bucket in."
type = string
}
variable "location" {
description = "The location of the bucket."
type = string
}
variable "storage_class" {
description = "The Storage Class of the new bucket."
type = string
default = null
}
variable "labels" {
description = "A set of key/value label pairs to assign to the bucket."
type = map(string)
default = null
}
variable "bucket_policy_only" {
description = "Enables Bucket Policy Only access to a bucket."
type = bool
default = true
}
variable "versioning" {
description = "While set to true, versioning is fully enabled for this bucket."
type = bool
default = true
}
variable "force_destroy" {
description = "When deleting a bucket, this boolean option will delete all contained objects. If false, Terraform will fail to delete buckets which contain objects."
type = bool
default = true
}
variable "iam_members" {
description = "The list of IAM members to grant permissions on the bucket."
type = list(object({
role = string
member = string
}))
default = []
}
variable "retention_policy" {
description = "Configuration of the bucket's data retention policy for how long objects in the bucket should be retained."
type = object({
is_locked = bool
retention_period = number
})
default = null
}
variable "encryption" {
description = "A Cloud KMS key that will be used to encrypt objects inserted into this bucket"
type = object({
default_kms_key_name = string
})
default = null
}
variable "lifecycle_rules" {
description = "The bucket's Lifecycle Rules configuration."
type = list(object({
# Object with keys:
# - type - The type of the action of this Lifecycle Rule. Supported values: Delete and SetStorageClass.
# - storage_class - (Required if action type is SetStorageClass) The target Storage Class of objects affected by this Lifecycle Rule.
action = any
# Object with keys:
# - age - (Optional) Minimum age of an object in days to satisfy this condition.
# - created_before - (Optional) Creation date of an object in RFC 3339 (e.g. 2017-06-13) to satisfy this condition.
# - with_state - (Optional) Match to live and/or archived objects. Supported values include: "LIVE", "ARCHIVED", "ANY".
# - matches_storage_class - (Optional) Storage Class of objects to satisfy this condition. Supported values include: MULTI_REGIONAL, REGIONAL, NEARLINE, COLDLINE, STANDARD, DURABLE_REDUCED_AVAILABILITY.
# - num_newer_versions - (Optional) Relevant only for versioned objects. The number of newer versions of an object to satisfy this condition.
condition = any
}))
default = []
}
outputs.tf에 있는 모듈에 다음 출력 추가
output "bucket" {
description = "The created storage bucket"
value = google_storage_bucket.bucket
}
Root dir로 돌아와 main.tf에 다음을 추가한다.
module "gcs-static-website-bucket" {
source = "./modules/gcs-static-website-bucket"
name = var.name
project_id = var.project_id
location = "REGION"
lifecycle_rules = [{
action = {
type = "Delete"
}
condition = {
age = 365
with_state = "ANY"
}
}]
}
다음 명령어로 output.tf 생성 및 정의
cd ~
touch outputs.tf
# outputs.tf
output "bucket-name" {
description = "Bucket names."
value = "module.gcs-static-website-bucket.bucket"
}
variables.tf 생성 및 정의
touch variables.tf
# variables.tf
variable "project_id" {
description = "The ID of the project in which to provision resources."
type = string
default = "FILL IN YOUR PROJECT ID HERE"
}
variable "name" {
description = "Name of the buckets to create."
type = string
default = "FILL IN A (UNIQUE) BUCKET NAME HERE"
}
모듈 초기화 및 적용
terraform init
terraform apply
샘플 컨텐츠를 다운로드하고 버킷에 업로드 한다.
cd ~
curl https://raw.githubusercontent.com/hashicorp/learn-terraform-modules/master/modules/aws-s3-static-website-bucket/www/index.html > index.html
curl https://raw.githubusercontent.com/hashicorp/learn-terraform-modules/blob/master/modules/aws-s3-static-website-bucket/www/error.html > error.html
gsutil cp *.html gs://YOUR-BUCKET-NAME
접속시 화면