Azure 상식

TerraForm의 이해

ktzzang0601 2025. 8. 19. 21:48

1. 서론

  • 클라우드 리소스 배포는 단순한 서버 생성 이상을 요구합니다. 네트워크, 보안 그룹, 스토리지, 애플리케이션 구성까지 모두 정교하게 관리되어야 하죠. 이를 사람이 수동으로 Azure 포털에서 일일이 설정한다면 실수 가능성이 높고, 변경 이력 관리도 어렵습니다.
    이 문제를 해결하기 위해 등장한 접근 방식이 Infrastructure as Code(IaC)이며, 그 대표 도구가 Terraform입니다. Terraform은 HashiCorp에서 만든 오픈소스 IaC 도구로, 선언적 언어를 이용해 Azure와 같은 클라우드 인프라를 코드로 정의하고 자동으로 배포·관리할 수 있게 해줍니다.

2. 본론

A. 기본 구조 이해하기

  • Terraform은 .tf 확장자를 가진 코드 파일에 인프라 구성을 선언합니다.
    아주 단순한 예시는 아래와 같습니다.

provider “azurerm” {
features {}
}

resource “azurerm_resource_group” “example” {
name = “rg-example”
location = “koreacentral”
}

  • provider "azurerm": Azure 리소스를 다룰 때는 반드시 Provider를 정의해야 합니다. 여기서 features {} 블록은 AzureRM Provider의 필수 초기화 구문으로, 특정 기능을 켜거나 끄는 옵션을 포함할 수 있습니다.
  • resource "azurerm_resource_group": 실제 생성할 리소스입니다. "example"이라는 로컬 이름으로 관리되며, 코드 안에서 참조할 수 있습니다.
  • location: Azure 리소스는 지역성을 갖기 때문에 반드시 명시해야 합니다.

B. 스토리지 계정 예시

  • 리소스 그룹 다음으로 자주 쓰이는 것이 스토리지 계정입니다.

resource “azurerm_storage_account” “example” {
name = “stexample12345”
resource_group_name = azurerm_resource_group.example.name
location = azurerm_resource_group.example.location
account_tier = “Standard”
account_replication_type = “LRS”
}

  • resource_group_name, location: 위에서 만든 리소스 그룹을 그대로 참조합니다. 이렇게 코드를 연결하면 사람이 일일이 위치를 지정할 필요가 없어집니다.
  • account_tier, account_replication_type: Azure 스토리지 성능과 가용성을 결정하는 핵심 옵션입니다. IaC로 작성해두면, 나중에 성능 조정 시 변경사항이 코드에 남아 형상 관리가 가능합니다.

C. Variables와 Outputs 활용

  • 하드코딩을 피하고 재사용성을 높이려면 변수를 씁니다.

variable “prefix” {
default = “demo”
}

resource “azurerm_resource_group” “rg” {
name = “${var.prefix}-rg”
location = “koreacentral”
}

output “resource_group_name” {
value = azurerm_resource_group.rg.name
}

  • variable: 동일한 코드가 여러 환경(dev, test, prod)에서 재사용될 수 있게 합니다.
  • output: 배포 후 다른 모듈이나 사용자에게 필요한 값을 노출합니다. 예를 들어 리소스 그룹 이름을 CI/CD 파이프라인에서 참조할 수 있습니다.

D. 운영에서 중요한 개념 — State 관리

  • Terraform은 terraform.tfstate라는 파일에 현재 리소스 상태를 기록합니다. 이 파일을 바탕으로 “코드와 실제 Azure 환경의 차이”를 판단하고, terraform plan 명령으로 변경 내용을 미리 보여줍니다.
  • 장점: 운영자가 실수로 리소스를 삭제할 위험을 줄여줍니다.
  • 주의: 팀 협업 시 이 파일은 반드시 **원격 스토리지(예: Azure Storage Backend)**에 보관해야 충돌을 피할 수 있습니다.

E. 모듈화

  • 규모가 커지면 리소스를 모듈 단위로 쪼개야 관리가 편해집니다. 예를 들어 네트워크, VM, 데이터베이스를 각각 모듈로 정의해두고, 메인 코드에서 불러옵니다. 이렇게 하면 팀별로 분업이 가능하고, 재사용성도 높아집니다.

F. 예제 코드

provider “azurerm” {
features {}
}

# 1. 리소스 그룹 생성
resource “azurerm_resource_group” “rg” {
name = “demo-rg”
location = “koreacentral”
}

# 2. 스토리지 계정 생성
resource “azurerm_storage_account” “storage” {
name = “demostorage12345”
resource_group_name = azurerm_resource_group.rg.name
location = azurerm_resource_group.rg.location
account_tier = “Standard”
account_replication_type = “LRS”
}

# 3. 가상 네트워크 + 서브넷 (VM 배포를 위해 필요)
resource “azurerm_virtual_network” “vnet” {
name = “demo-vnet”
address_space = [“10.0.0.0/16”]
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
}

resource “azurerm_subnet” “subnet” {
name = “demo-subnet”
resource_group_name = azurerm_resource_group.rg.name
virtual_network_name = azurerm_virtual_network.vnet.name
address_prefixes = [“10.0.1.0/24”]
}

# 4. 네트워크 인터페이스
resource “azurerm_network_interface” “nic” {
name = “demo-nic”
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name

ip_configuration {
name = “internal”
subnet_id = azurerm_subnet.subnet.id
private_ip_address_allocation = “Dynamic”
}
}

# 5. 가상머신 생성
resource “azurerm_linux_virtual_machine” “vm” {
name = “demo-vm”
resource_group_name = azurerm_resource_group.rg.name
location = azurerm_resource_group.rg.location
size = “Standard_B1s”
admin_username = “azureuser”
network_interface_ids = [azurerm_network_interface.nic.id]

os_disk {
caching = “ReadWrite”
storage_account_type = “Standard_LRS”
}

source_image_reference {
publisher = “Canonical”
offer = “UbuntuServer”
sku = “18.04-LTS”
version = “latest”
}

admin_ssh_key {
username = “azureuser”
public_key = file(“~/.ssh/id_rsa.pub”)
}
}

3. Azure Terraform CLI 작성시 몇가지 유의사항

  •    pod_cidr 옵션 설정
     => Azure CNI 인 경우 각 Pod IP들은 서브넷에서 직접 할당받기 때문에 pod_cidr 옵션을 쓰지 않아야 한다.
          다음은 AKS 네트워크 추가 옵션 별 작성 요령을 정리한 표이다.
    항목 Kubenet Azure CNI Azure CNI + Overlay
    Pod IP 출처 Pod 전용 CIDR(노드 VNet과 분리)에서 할당 노드와 같은 VNet 서브넷에서 직접 할당 Pod 전용 CIDR(노드 VNet과 분리)에서 할당
    외부로 나갈 때 노드 IP로 SNAT돼 나감 같은 VNet 내 대상은 Pod IP가 그대로 보임 기본적으로 노드 IP로 NAT하여 외부 도달
    VNet IP 소모 적음 많음(대규모 시 서브넷 IP 고갈 위험) 적음(대규모 확장 유리)
    설정 키 포인트 network_plugin="kubenet" + pod_cidr 지정 network_plugin="azure" (일반) → pod_cidr 미사용 network_plugin="azure" + network_plugin_mode="overlay" + pod_cidr 지정
    공식 개념 문서 kubenet 개요(별도 Pod 대역) “Pod가 서브넷에서 IP를 받음”  “Pod는 VNet과 다른 private CIDR에서 IP” + NAT
  • Azure ARM Terraform 문법 기본 규칙
    필드 이름 형태 기대하는 값 예시 기타
    *_name, name 이름 문자열 name = "vnet-app" / resource_group_name = azurerm_resource_group.rg.name 이름은 짧은 리소스명(포털에 보이는 이름)
    id, *_id, resource_id, target_resource_id, parent_id, scope ARM 리소스 ID(풀경로) subnet_id = azurerm_subnet.app.id / scope = azurerm_resource_group.rg.id 포털의 “리소스 ID”에 해당. **.id**로 참조하는 게 정석
    location 리전 문자열 location = azurerm_resource_group.rg.location 혹은 "Korea Central" 보통 RG의 location을 그대로 상속
    숫자/불리언/맵 타입에 맞는 리터럴 port = 80, enabled = true, tags = { env = "prod" }  
    문자열 내 참조 문자열 보간 name = "vm-${var.env}-01" HCL 0.12+에서는 name = "${var.env}"도 가능하지만 위가 가독성↑
  • 추가 체크리스트
    - 이 필드는 이름 자리인가요? → "..." 또는 … .name
    - 이 필드는 ARM ID 자리인가요? → … .id (따옴표 없이)
    - location은 RG에서 그대로 상속했나요? → …rg.location
    - 포털 ID를 하드코딩하지 않았나요? → data 소스/리소스 참조로 대체
    - 모듈 변수는 “이름/ID 중 하나로 타입을 표준화”했나요?

4. 중요 예제 코드

# Create Public Load Balancer
resource "azurerm_lb" "example" {
  name                = var.load_balancer_name
  location            = azurerm_resource_group.example.location
  resource_group_name = azurerm_resource_group.example.name
  sku                 = "Standard"

  frontend_ip_configuration {
    name                 = var.public_ip_name
    public_ip_address_id = azurerm_public_ip.example.id
  }
}

# Create Backend Address Pool for the Load Balancer
resource "azurerm_lb_backend_address_pool" "example" {
  loadbalancer_id = azurerm_lb.example.id
  name            = "test-pool"
}

# Create Load Balancer Health Probe
resource "azurerm_lb_probe" "example" {
  loadbalancer_id = azurerm_lb.example.id
  name            = "test-probe"
  port            = 80
}

# Create Load Balancer Rule
# This rule will forward traffic from the frontend IP configuration to the backend address pool
# on port 80 using TCP protocol. It also disables outbound SNAT for the backend pool.
# The probe is used to check the health of the backend instances.
resource "azurerm_lb_rule" "example_rule" {
  loadbalancer_id                = azurerm_lb.example.id
  name                           = "test-rule"
  protocol                       = "Tcp"
  frontend_port                  = 80
  backend_port                   = 80
  disable_outbound_snat          = true
  frontend_ip_configuration_name = var.public_ip_name
  probe_id                       = azurerm_lb_probe.example.id
  backend_address_pool_ids       = [azurerm_lb_backend_address_pool.example.id]
}

resource "azurerm_lb_outbound_rule" "example" {
  name                    = "test-outbound"
  loadbalancer_id         = azurerm_lb.example.id
  protocol                = "Tcp"
  backend_address_pool_id = azurerm_lb_backend_address_pool.example.id

  frontend_ip_configuration {
    name = var.public_ip_name
  }

5. 참고 문헌