Automation/Terraform

[Terraform] 2장 Getting Started with Terraform (terraform up&running 책 정리)

[앙금빵] 2024. 12. 21.

본 글은 Terraform Up& Running 책에 대하여 2장에 대한 내용 정리 글이다. 내용 정리에 있어 Udemy 강의에서 들은 내용과 겹치는 내용들은 통합하여 정리를 진행하였다.

 


1. Intro to Terraform Syntax

Terraform은 HashiCorp 구성 언어(HCL)로 작성된 .tf 확장자를 가진 구성 파일을 사용한다. 이러한 파일은 Notepad, Vim 등 텍스트 편집기 또는 IDE를 사용하여 생성할 수 있다.

  • Terraform은 선언형(declarative) 언어로, 사용자가 최종 상태에 대하여 인프라를 정의하면 Terraform이 이를 생성 및 상태변화를 알아서 파악하여 처리한다.
  • AWS, Azure, Google Cloud, DigitalOcean 등 다양한 플랫폼(Provider라고도 함)에서 Terraform을 통해 인프라를 생성할 수 있도록 API를 제공해준다.

 

Terraform에서는 다양한 provider들 대상으로 resoruce_type, Arguments 들이 존재한다. 이들을 모두 외울 필요는 없다.

 

HCL 구문 이해하기

  • HCL 파일은 블록(block)인수(argument)로 구성되어진다. 블록은 중괄호로 감싸여 있으며, 블록 내부에는 key-value 형식의 인수가 들어가 있어 설정 데이터를 나타내어진다.
  • Terraform에서 블록은 인프라 플랫폼에 대한 정보와 해당 플랫폼 내에서 생성할 리소스를 포함한다.
#local.tf
<block> <parameters> {
	key1 = value1
	key2 = value2
}

 

Terraform HCL 코드 예제

provider "aws" {
  region = "us-east-2"
}

resource "aws_instance" "example" {
  ami           = "ami-0fb653ca2d3203ac1"
  instance_type = "t2.micro"
}

 

provider 블록

  • AWS와 같이 Terraform 서비스 제공자에 대한 정보를 정의한다.
  • 내부 인수(argument) 값으로 region 정보가 담겨져 있다.

 

resource 블록

  • AWS provider 아래 배포할 리소스를 정의한다.
  • 내부 인수 값으로 ami 종류와 인스턴스 유형이 정의되어져 있다.
  • aws_instance 라는 리소스 유형으로 example이라는 이름의 EC2 인스턴스를 생성한다.

 

테라폼은 main.tf 기반으로 리소스에 대한 형상을 정의한다. 그러나 단일파일에서 국한되지 않으며, 추가로 구성 파일을 생성할 수 있다. 구성파일에서 대표적인 것은 variables.tf, outputs.tf, providers.tf 등이 있다.

출처: Udemy 강의

 

 

입력 변수 (Input Variables)

Bash 스크립팅이나 PowerShell과 같은 일반적인 프로그래밍 언어처럼 Terraform에서도 입력 변수를 사용할 수 있다.

 

입력 변수는 코드 내 특정 값(예: 포트 번호)을 한 곳에 정의하고 여러 곳에서 참조하게 함으로써 DRY 원칙을 지킨다. 이를 통해 내용변경 시 한 번의 수정으로 모든 참조 지점을 갱신할 수 있어 유지보수성이 향상되는 장점을 제공한다.

 

변수를 할당하려면 새 구성 파일을 생성하여 variables.tf라는 이름을 지정하고 값을 정의할 수 있으며 main.tf 파일처럼 블록과 인수로 구성되어진다.

자료: Udemy 강의

 

기존 인수를 변경하여 리소스를 업데이트하려면 variables.tf 파일만 업데이트하면 된다. main.tf 파일을 수정할 필요는 없다.

 

테라폼 변수는 다른 일반 프로그래밍 언어와 같이 list, map, tuple, object,  set 와 같이 다양한 구조를 제공한다. 자세한 내용과 예제는 아래 링크에 잘 설명되어져 있다.

 

How to Use Terraform Variables: Examples & Best Practices

Terraform variables types and how to use them. Learn how to use local, input, output, and environment variables. See how to mark Terraform variables as sensitive.

spacelift.io


2. Terraform Workflow

인프라를 프로비저닝할때, terraform init → plan → apply 순으로 진행이 되어진다.

 

init으로 환경을 준비하고, plan 으로 변경 사항을 검토한 후, apply로 실제 변경을 수행되어지는 구조이다. 이제, 각 명령어에 대한 역할과 가지는 의의에 대해서 정리해보자.

 

2-1. Terraform init

terraform init은 Terraform 작업의 첫 번째 단계로서, 이후의 모든 Terraform 명령어가 정상적으로 동작하기 위한 기반을 마련한다.

 

(1) 구성에 필요한 플러그인과 모듈을 가져온다.

(2) 상태 파일의 저장소를 확인하고 백엔드 설정을 구성한다.

 

  • Case 1. 상태 저장이 되어지는 경우
    • 작업 디렉토리에 기존 상태파일이 없고 새로 상태파일을 만들어야 하는 경우
    • 기존 상태 파일이 로컬에 존재하나, 새로운 백엔드로 상태파일을 이동해야하는 경우

 

  • Case 2. 상태를 가져오는 경우
    • 백엔드에 이미 상태파일이 있는 경우
    • 다른 작업 디렉토리에서 동일한 백엔드를 공유하는 경우

 

이 과정은 Terraform이 구성 파일에서 정의한 리소스를 인식할 수 있게 해주는 필수적인 과정이다.

$ terraform init 
Initializing the backend... 
Initializing provider plugins... - Reusing previous version of hashicorp/aws from the dependency 
lock file - Using hashicorp/aws v4.19.0 from the shared cache directory 
Terraform has been successfully initialized!

 

2-2. Terraform plan

terraform plan은 인프라 추가 및 변경사항에 대한 검증을 위한 단계이다. 이 명령어를 통해 사용자는 구성 파일의 변경이 실제 인프라에 어떤 영향을 미칠지 사전에 확인할 수 있게 된다.

 $ terraform plan 
(...) 
Terraform will perform the following actions: 
  # aws_instance.example will be created
  + resource "aws_instance" "example" { 
      + ami                          = "ami-0fb653ca2d3203ac1" 
      + arn                          = (known after apply) 
      + associate_public_ip_address  = (known after apply) 
      + availability_zone            = (known after apply) 
      + cpu_core_count               = (known after apply) 
      + cpu_threads_per_core         = (known after apply) 
      + get_password_data            = false 
      + host_id                      = (known after apply) 
      + id                           = (known after apply) 
      + instance_state               = (known after apply) 
      + instance_type                = "t2.micro" 
      + ipv6_address_count           = (known after apply) 
      + ipv6_addresses               = (known after apply) 
      + key_name                     = (known after apply) 
      (...) 
  } 
Plan: 1 to add, 0 to change, 0 to destroy.

 

2-3. Terraform apply

terraform apply는 인프라 구성 및 업데이트의 실제 실행 단계이다. 이 명령어를 통해 Terraform은 구성 파일에서 정의한 상태로 인프라를 구성 및 업데이트를 수행한다.

$ terraform apply 
(...) 
Terraform will perform the following actions: 
  # aws_instance.example will be created 
  + resource "aws_instance" "example" { 
      + ami                          = "ami-0fb653ca2d3203ac1" 
      + arn                          = (known after apply) 
      + associate_public_ip_address  = (known after apply) 
      + availability_zone            = (known after apply)
      + cpu_core_count               = (known after apply) 
      + cpu_threads_per_core         = (known after apply) 
      + get_password_data            = false 
      + host_id                      = (known after apply) 
      + id                           = (known after apply) 
      + instance_state               = (known after apply) 
      + instance_type                = "t2.micro" 
      + ipv6_address_count           = (known after apply) 
      + ipv6_addresses               = (known after apply) 
      + key_name                     = (known after apply) 
      (...) 
  } 
Plan: 1 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:

 


3. Deploying a Configurable WEB Server Clusters

본 단원에서는 WEB 서버 배포에 대한 간단한 예제 실습을 진행한다. 실습 코드는 저자 깃허브(링크)에서 내용을 확인할 수 있다. 여기서, 웹 서버 클러스터 배포 단원에서 모든 단원을 포괄하기에 해당 단원 내용에 대하여 각 코드파일이 시사하는 점을 정리하였다.

 

  • AWS 계정내 권한을 수행할 수 있는 EC2 Linux 서버로부터 테스트
  • IAM Profile 기능을 활용하여 Role 기반 권한 할당
  • Architecture Workflow: User → ELB → ASG(WEB EC2 Servers)

Architecture Workflow

 

3-1. 해당 단원 Insight 정리

File Structure

02-intro-to-terraform-syntax
├── main.tf
├── outputs.tf
├── README.md
└── variables.tf

 

variables.tf (입력 변수)

  • variables.tf 파일에서 인프라 배포 시 외부에서 받을 수 있는 값(예: VPC 이름, 서버 포트, 인스턴스 타입, 접속할 자격 증명 등)을 정의가 되어져 있다.
  • 포트 번호, ALB 정보, 보안그룹과 같이 환경 의존적이거나 자주 바뀔 수 있는 가능성이 있는 값을 변수화하여 코드 수정 범위를 최소화하였다. (DRY 원칙)
  • 이를 통해 코드 내 값이 하드코딩되지 않고, 배포 시점이나 환경에 따라 쉽게 변경 가능하며, 유지보수성과 재활용성 향상할 수 있는 핵심 역할을 수행한다.

 

outputs.tf (출력 변수)

  • outputs.tf 파일은 배포가 완료된 후 최종적으로 확인하거나 다른 프로세스에서 재활용할 값을 표시한다. (웹 서버의 퍼블릭 IP, 로드 밸런서 DNS 이름, DB 접속 정보 등) 이를 통해 다른 시스템이나 개발자가 쉽게 결과를 확인하고, 추가 작업에 활용하도록 지원한다.
  • 비밀번호나 키와 같은 민감한 변수에 sensitive = true 설정을 적용해 Terraform 출력 로그나 상태 파일에 노출되지 않도록 하였다.
  • 배포 후에 중요한 정보(예: 서버의 퍼블릭 IP 또는 로드 밸런서의 도메인 이름)를 쉽게 확인할 수 있도록 output "NAME" 블록을 사용한다

 

main.tf (실제 리소스를 정의하는 핵심 파일)

  • create_before_destroy 라이프사이클 설정을 사용하여 무중단 배포를 위한 리소스 교체 전략 수립 (변경 시 새 리소스를 먼저 생성한 뒤 기존 리소스를 제거함으로써 다운타임을 줄이는 패턴)
  • data Source를 활용하여 VPC나 Subnet 정보 등을 외부로부터 동적으로 조회
    • data 소스를 사용함으로써 VPC나 Subnet과 같은 정보를 동적으로 파싱
    • 코드를 작성할 때는 VPC나 Subnet을 특정하는 "조건"만 제시하고, 실제 리소스의 구체적인 ID나 속성은 Terraform이 실행될 때 프로바이더를 통해 자동으로 검색하여 가져오게 할 수 있다.
    • 예제에서는 기본 VPC(default VPC)를 찾아 그 ID를 가져오라는 의미 → 별도의 하드코딩 없이 "default = true"라는 조건을 통해 AWS 환경에서 해당 조건을 충족하는 VPC를 조회

 

3-2 Data Source 내용 추가 정리

data 블록 안에 들어가는 인수들은 대부분 "필터" 역할을 한다. 예를 들어,

data "aws_vpc" "default" {
  default = true
}

이 코드는 "기본 VPC(default VPC)"를 찾아 그 ID를 가져오라는 의미이다. 별도의 하드코딩 없이 "default = true"라는 조건을 통해 AWS 환경에서 해당 조건을 충족하는 VPC를 조회한다.

 

이후 해당 VPC의 ID를 다른 data 소스나 리소스에서 참조할 수 있다.

data "aws_subnets" "default" {
  filter {
    name   = "vpc-id"
    values = [data.aws_vpc.default.id]
  }
}

여기서는 앞서 조회한 default VPC의 ID를 활용하여 해당 VPC 내에 있는 Subnet들을 가져온다. filter 블록에서 name = "vpc-id", values = [data.aws_vpc.default.id]는 "앞서 조회한 기본 VPC ID와 매칭되는 모든 Subnet을 반환하라"는 조건을 의미한다.

 

data source를 더 응용하면 Tag 네이밍 규칙을 활용하여 vpc 및 subnet ID를 호출할 수 있다. 아래는 vpc_id, subnet_id를 동적으로 출력할 수 있는 예시이다.

# main.tf
terraform {
  required_version = ">= 1.0.0"
}

provider "aws" {
  region  = "ap-northeast-1"
  profile = "example-profile" # 일반적인 프로파일 이름으로 변경
}

# "Name" 태그가 "example-vpc"인 VPC를 조회
data "aws_vpc" "example_vpc" {
  filter {
    name   = "tag:Name"
    values = ["example-vpc"]
  }
}

# "Name" 태그가 "example-private-a"인 서브넷을 동적으로 조회
data "aws_subnet" "example_private_a" {
  filter {
    name   = "tag:Name"
    values = ["example-private-a"]
  }

  filter {
    name   = "vpc-id"
    values = [data.aws_vpc.example_vpc.id] # 위에서 조회한 VPC의 ID를 기준으로 검색
  }
}

# VPC ID 출력
output "example_vpc_id" {
  value       = data.aws_vpc.example_vpc.id
  description = "'example-dev-vpc' 이름의 vpc_id 추출"
}

# 서브넷 ID 출력
output "example_private_a_subnet_id" {
  value       = data.aws_subnet.example_private_a.id
  description = "'example-private-a' 이름의 subnet_id 추출"
}

 

댓글