terraform

Terraform을 이용해 Azure 인프라 구축하기(application gateway, virtual machine, Auto scaling)

kkuniyo 2025. 3. 27. 15:12
반응형

이번에 추가된 Terraform 구성요소

새롭게 추가된 Terraform 파일을 통해 다음과 같은 인프라 구성이 가능합니다.

  • 애플리케이션 게이트웨이(Application Gateway)
  • VM 및 이미지 관리(Shared Image Gallery)
  • 가상 머신 스케일 세트(VMSS)
  • DNS 영역 및 레코드 관리
  • DB 및 프로비저닝 스크립트(null_resource)

세부 파일 구성

다음과 같은 파일 구조로 확장되었습니다.

10_appgw.tf             # Application Gateway 생성
11_appgwasso.tf         # Application Gateway 연결 구성
12_vm.tf                # 가상 머신 생성
13_gallery.tf           # 이미지 관리(Shared Image Gallery)
14_as.tf                # 가용성 집합(Availability Set) 설정
15_vmss.tf              # 가상 머신 스케일 세트(VMSS) 관리
16_dns.tf               # DNS 영역 관리
17_dnsrecord.tf         # DNS 레코드 관리
19.null.tf              # 프로비저닝 및 자동화 스크립트 실행
20_db.tf                # 데이터베이스 리소스 설정

구현 효과

  • 확장성 및 가용성 증대: VM 스케일 세트와 가용성 집합을 통해 자동 확장과 고가용성을 실현할 수 있습니다.
  • 효율적인 애플리케이션 배포 및 관리: Application Gateway를 통해 부하 분산 및 트래픽 관리가 용이해집니다.
  • 빠른 복구 및 배포 효율성 향상: Shared Image Gallery를 이용한 이미지 관리로 VM 생성과 유지 보수가 간편해집니다.

10_appgw.tf

# Local Variables
# - Application Gateway 관련 공통 이름들을 미리 정의합니다.
locals {
  backend_address_pool_name      = "appGatewayBackendPool"         # 백엔드 주소 풀의 이름
  frontend_port_name             = "port_80"                       # 프론트엔드 포트 구성 이름
  frontend_ip_configuration_name = "appGatewayFrontendIP"          # 프론트엔드 IP 구성 이름
  http_setting_name              = "appGatewayBackendHttpSettings" # 백엔드 HTTP 설정 이름
  listener_name                  = "appGatewayHttpListener"        # HTTP 리스너 이름
  request_routing_rule_name      = "appGatewayRule"                # 요청 라우팅 규칙 이름
}


# Application Gateway 구성
# - 외부 트래픽을 받아 내부 백엔드 풀로 라우팅하는 역할을 수행합니다.
resource "azurerm_application_gateway" "main_appgw" {
  name                = "main-appgw"                                 
  resource_group_name = azurerm_resource_group.main_rg.name          
  location            = azurerm_resource_group.main_rg.location     

  # SKU 설정: Gateway의 성능 및 인스턴스 수 정의
  sku {
    name     = "Standard_v2"  
    tier     = "Standard_v2"  
    capacity = 2               
  }

  # 게이트웨이 IP 구성: Application Gateway가 연결될 서브넷 지정
  gateway_ip_configuration {
    name      = "appGatewayIpConfig"       
    subnet_id = azurerm_subnet.main_appgw.id  
  }

  # 프론트엔드 포트 구성: 클라이언트의 요청을 받을 포트 설정 (예: 80번)
  frontend_port {
    name = local.frontend_port_name  
    port = 80                       
  }

  # 프론트엔드 IP 구성: 외부와의 통신을 위한 Public IP와 연계
  frontend_ip_configuration {
    name                 = local.frontend_ip_configuration_name  
    public_ip_address_id = azurerm_public_ip.main_appgw_ip.id    
  }

  # 백엔드 주소 풀: 요청을 전달할 내부 서버 그룹을 정의
  backend_address_pool {
    name = local.backend_address_pool_name  
  }

  # 백엔드 HTTP 설정: 백엔드 서버와 통신할 때 사용할 프로토콜, 포트, 타임아웃 등을 정의
  backend_http_settings {
    name                  = local.http_setting_name   
    cookie_based_affinity = "Disabled"                 
    port                  = 80                       
    protocol              = "Http"                   
    request_timeout       = 20                      
  }

  # HTTP 리스너: 클라이언트 요청을 수신할 IP와 포트, 프로토콜을 정의
  http_listener {
    name                           = local.listener_name               
    frontend_ip_configuration_name = local.frontend_ip_configuration_name
    frontend_port_name             = local.frontend_port_name           
    protocol                       = "Http"                            
  }

  # 요청 라우팅 규칙: 들어오는 요청을 백엔드 풀 및 HTTP 설정으로 라우팅
  request_routing_rule {
    name                       = local.request_routing_rule_name    
    priority                   = 1                                    
    rule_type                  = "Basic"                             
    http_listener_name         = local.listener_name                   
    backend_http_settings_name = "appGatewayBackendHttpSettings"       # 백엔드 HTTP 설정 이름 (위에서 정의한 값과 일치해야 함)
    backend_address_pool_name  = "appGatewayBackendPool"               # 백엔드 주소 풀 이름 (위에서 정의한 값과 일치해야 함)
  }
}

11_appgwasso.tf


# Application Gateway 백엔드 풀과 NIC 연관
# - WEB1 서버의 네트워크 인터페이스를 Application Gateway의 백엔드 풀에 연결
resource "azurerm_network_interface_application_gateway_backend_address_pool_association" "main_web1_nic_appgw" {
  network_interface_id    = azurerm_network_interface.main_web1_nic.id  
  backend_address_pool_id = tolist(azurerm_application_gateway.main_appgw.backend_address_pool)[0].id 
  # Application Gateway 백엔드 풀 ID (리스트 변환 후 첫번째 요소 선택)
  ip_configuration_name   = "main-web1-ipconfig" 
}


# Application Gateway 백엔드 풀과 NIC 연관
# - WEB2 서버의 네트워크 인터페이스를 Application Gateway의 백엔드 풀에 연결
resource "azurerm_network_interface_application_gateway_backend_address_pool_association" "main_web2_nic_appgw" {
  network_interface_id    = azurerm_network_interface.main_web2_nic.id 
  backend_address_pool_id = tolist(azurerm_application_gateway.main_appgw.backend_address_pool)[0].id
  # Application Gateway 백엔드 풀 ID
  ip_configuration_name   = "main-web2-ipconfig" 
}

12_vm.tf

# Linux Virtual Machine: BAT 서버
# - 배치 작업이나 백엔드 서비스용으로 사용되는 Linux VM
resource "azurerm_linux_virtual_machine" "main_bat" {
  name                  = "main-bat"                                 
  location              = azurerm_resource_group.main_rg.location     
  resource_group_name   = azurerm_resource_group.main_rg.name         
  size                  = "Standard_F1s"                               
  admin_username        = "main"                                     
  network_interface_ids = [azurerm_network_interface.main_bat_nic.id]   # 연결된 NIC 목록
  
  # SSH 접속을 위한 공개키 인증 설정
  admin_ssh_key {
    username   = "main"               # SSH 접속 사용자 이름
    public_key = file("main.pub")     # 공개키 파일 경로
  }

  # VM 부팅 시 실행할 사용자 데이터 스크립트 (Base64 인코딩)
  user_data = base64encode(file("key.sh"))

  # OS 디스크 구성
  os_disk {
    caching              = "ReadWrite"            # 캐싱 모드 설정
    storage_account_type = "StandardSSD_LRS"      # 스토리지 계정 종류
  }

  # VM에 사용될 소스 이미지 정보 (Rocky Linux 이미지)
  source_image_reference {
    publisher = "resf"                        # 이미지 퍼블리셔
    offer     = "rockylinux-x86_64"            # 이미지 오퍼
    sku       = "9-lvm"                        # 이미지 SKU
    version   = "9.3.20231113"                 # 이미지 버전
  }

  # 이미지 사용을 위한 플랜 정보 (Marketplace 이미지인 경우 필요)
  plan {
    publisher = "resf"
    product   = "rockylinux-x86_64"
    name      = "9-lvm"
  }

  # 부팅 진단 (여기서는 사용하지 않음)
  boot_diagnostics {
    storage_account_uri = null
  }
}


# Linux Virtual Machine: WEB1 서버
# - 웹 서비스를 제공하기 위한 Linux VM (WEB1)
resource "azurerm_linux_virtual_machine" "main_web1" {
  name                  = "main-web1"                                 
  location              = azurerm_resource_group.main_rg.location     
  resource_group_name   = azurerm_resource_group.main_rg.name         
  size                  = "Standard_F1s"                                 
  admin_username        = "main"                                        
  network_interface_ids = [azurerm_network_interface.main_web1_nic.id]  
  
  # SSH 공개키 인증 설정
  admin_ssh_key {
    username   = "main"               
    public_key = file("main.pub")     
  }

  # 초기화 스크립트 (Base64 인코딩)
  user_data = base64encode(file("main-ins1.sh"))

  # OS 디스크 구성
  os_disk {
    caching              = "ReadWrite"            
    storage_account_type = "StandardSSD_LRS"     
  }

  # 사용될 소스 이미지 정보
  source_image_reference {
    publisher = "resf"                      
    offer     = "rockylinux-x86_64"           
    sku       = "9-lvm"                      
    version   = "9.3.20231113"              
  }

  # 이미지 플랜 정보
  plan {
    publisher = "resf"
    product   = "rockylinux-x86_64"
    name      = "9-lvm"
  }

  # 부팅 진단 설정 (비활성화)
  boot_diagnostics {
    storage_account_uri = null
  }
  
  # 의존성: WEB1 VM 생성 전에 NAT Gateway 연관이 완료되어야 함
  depends_on = [ azurerm_subnet_nat_gateway_association.main_sub_nat_assoc1 ]
}


# Linux Virtual Machine: WEB2 서버
# - 추가 웹 서비스를 제공하기 위한 Linux VM (WEB2)
resource "azurerm_linux_virtual_machine" "main_web2" {
  name                  = "main-web2"                                 
  location              = azurerm_resource_group.main_rg.location    
  resource_group_name   = azurerm_resource_group.main_rg.name        
  size                  = "Standard_F1s"                            
  admin_username        = "main"                                       
  network_interface_ids = [azurerm_network_interface.main_web2_nic.id]
  
  # SSH 공개키 인증 설정
  admin_ssh_key {
    username   = "main"              
    public_key = file("main.pub")    
  }

  # 초기화 스크립트 (Base64 인코딩)
  user_data = base64encode(file("main-ins2.sh"))

  # OS 디스크 구성
  os_disk {
    caching              = "ReadWrite"        
    storage_account_type = "StandardSSD_LRS"    
  }

  # 소스 이미지 정보
  source_image_reference {
    publisher = "resf"                      
    offer     = "rockylinux-x86_64"        
    sku       = "9-lvm"                      
    version   = "9.3.20231113"              
  }

  # 이미지 플랜 정보
  plan {
    publisher = "resf"
    product   = "rockylinux-x86_64"
    name      = "9-lvm"
  }
  
  # 부팅 진단 설정 (비활성화)
  boot_diagnostics {
    storage_account_uri = null
  }
}

13_gallery.tf

# Managed Image 생성: WEB1 이미지를 기반으로 생성
# - 이후 이미지 갤러리 등에 사용하기 위한 관리 이미지
resource "azurerm_image" "main_web1_image" {
  name                = "main-web1-image"                           
  location            = azurerm_resource_group.main_rg.location      
  resource_group_name = azurerm_resource_group.main_rg.name        
  hyper_v_generation  = "V2"                                           # Hyper-V 세대
  source_virtual_machine_id = azurerm_linux_virtual_machine.main_web1.id # 원본 VM (WEB1) ID
  
  # OS 디스크 설정 정보
  os_disk {
    size_gb      = 10                  
    os_type      = "Linux"               
    os_state     = "Specialized"         
    caching      = "ReadWrite"            
    storage_type = "StandardSSD_LRS"      
  }
  
  # 의존성: WEB1 VM의 일반화(generalize) 과정 완료 후 이미지 생성
  depends_on = [ null_resource.Generalize_web1_delay2 ]
}

# Shared Image Gallery 생성
# - 여러 이미지 버전을 저장하고 관리할 수 있는 이미지 갤러리 생성
resource "azurerm_shared_image_gallery" "main_gallery" {
  name                = "maingallery"                           
  location            = azurerm_resource_group.main_rg.location      
  resource_group_name = azurerm_resource_group.main_rg.name         
}


# Shared Image 생성
# - 갤러리 내에 사용할 공유 이미지 정의
resource "azurerm_shared_image" "main_rocky" {
  name                         = "main-rocky"                      
  gallery_name                 = azurerm_shared_image_gallery.main_gallery.name  
  resource_group_name          = azurerm_resource_group.main_rg.name  
  location                     = azurerm_resource_group.main_rg.location 
  os_type                      = "Linux"                          
  hyper_v_generation           = "V2"                             
  architecture                 = "x64"                             
  min_recommended_vcpu_count   = 1                                  
  max_recommended_vcpu_count   = 1                                   
  min_recommended_memory_in_gb = 1                                  
  max_recommended_memory_in_gb = 2                                 
  
  # 이미지 식별자: 원본 이미지의 퍼블리셔, 오퍼, SKU 정보를 지정
  identifier {
    publisher = "resf"                      
    offer     = "rockylinux-x86_64"          
    sku       = "9-lvm"                     
  }
}


# 데이터 소스: 관리 이미지 조회
# - 이전에 생성한 WEB1 이미지를 조회합니다.
data "azurerm_image" "cont" {
  name                = "main-web1-image"                           # 조회할 이미지 이름
  resource_group_name = azurerm_resource_group.main_rg.name           # 해당 이미지가 속한 리소스 그룹
  
  # 의존성: 이미지가 생성된 후에 데이터 조회
  depends_on = [ azurerm_image.main_web1_image ]
}


# Shared Image Version 생성
# - 공유 이미지에 버전 정보를 부여하여 관리
resource "azurerm_shared_image_version" "main-image-version" {
  name                = "1.0.0"                                    
  gallery_name        = azurerm_shared_image_gallery.main_gallery.name
  resource_group_name = azurerm_resource_group.main_rg.name             
  location            = azurerm_resource_group.main_rg.location        
  image_name          = azurerm_shared_image.main_rocky.name          
  managed_image_id    = data.azurerm_image.cont.id                       
  
  # 타깃 지역 설정: 이미지 버전을 복제할 대상 지역 지정
  target_region {
    name                   = "koreacentral"      
    regional_replica_count = 1                     
    storage_account_type   = "Standard_LRS"       
  }
  
  # 의존성: 관리 이미지 생성 후에 공유 이미지 버전 생성
  depends_on = [azurerm_image.main_web1_image]
}

14_as.tf

# Monitor Autoscale Setting 설정
# - Virtual Machine Scale Set(VMSS)의 자동 확장 정책 설정
resource "azurerm_monitor_autoscale_setting" "main_as" {
  name                = "main-as"                                  
  resource_group_name = azurerm_resource_group.main_rg.name          
  location            = azurerm_resource_group.main_rg.location     
  target_resource_id  = azurerm_linux_virtual_machine_scale_set.main_vmss.id 
  enabled             = true                                         # 자동 확장 활성화

  profile {
    name = "main-as-profile"                            

    # 용량 설정: 기본, 최소, 최대 인스턴스 수 정의
    capacity {
      default = 1    # 기본 인스턴스 수
      minimum = 1    # 최소 인스턴스 수
      maximum = 6    # 최대 인스턴스 수
    }

    # 스케일 아웃(인스턴스 증가) 규칙: CPU 사용률이 특정 임계치를 초과할 경우
    rule {
      metric_trigger {
        metric_name        = "Percentage CPU"                         # 모니터링할 메트릭
        metric_resource_id = azurerm_linux_virtual_machine_scale_set.main_vmss.id  # 메트릭이 적용될 VMSS ID
        time_grain         = "PT1M"                                   # 데이터 측정 간격 (1분)
        statistic          = "Average"                                # 평균 값 사용
        time_window        = "PT5M"                                  
        time_aggregation   = "Average"                              
        operator           = "GreaterThan"                           
        threshold          = 70                                     
      }

      scale_action {
        direction = "Increase"   # 인스턴스 증가
        type      = "ChangeCount"  # 인스턴스 수 변경 방식
        value     = "1"          # 1개씩 증가
        cooldown  = "PT5M"       # 확장 후 대기 시간 (5분)
      }
    }

    # 스케일 인(인스턴스 감소) 규칙: CPU 사용률이 특정 임계치보다 낮을 경우
    rule {
      metric_trigger {
        metric_name        = "Percentage CPU"                       
        metric_resource_id = azurerm_linux_virtual_machine_scale_set.main_vmss.id  
        time_grain         = "PT1M"                                   
        statistic          = "Average"                              
        time_window        = "PT5M"                                 
        time_aggregation   = "Average"                                
        operator           = "LessThan"                             
        threshold          = 20                                      
      }

      scale_action {
        direction = "Decrease"
        type      = "ChangeCount"
        value     = "1"
        cooldown  = "PT5M"
      }
    }
  }
}

 

15_vmss.tf

# Linux Virtual Machine Scale Set 구성
# - 여러 인스턴스로 구성된 Linux VM Scale Set을 생성합니다.
resource "azurerm_linux_virtual_machine_scale_set" "main_vmss" {
  name                = "main-vmss"
  resource_group_name = azurerm_resource_group.main_rg.name
  location            = azurerm_resource_group.main_rg.location
  instances           = 1
  # 이미지 소스 ID: 공유 이미지 버전 리소스의 ID 사용
  source_image_id     = azurerm_shared_image_version.main-image-version.id
  admin_username      = "main"
  sku                 = "Standard_F1s"
  # 업그레이드 모드 (수동으로 업그레이드)
  upgrade_mode        = "Manual"
  # VM 인스턴스의 우선순위 (일반)
  priority            = "Regular"

  # Plan 설정 (Marketplace 이미지인 경우 필요)
  plan {
    publisher = "resf"
    product   = "rockylinux-x86_64"
    name      = "9-lvm"
  }

  # SSH 접속 설정: 관리자가 SSH로 접속할 수 있도록 공개키 인증 구성
  admin_ssh_key {
    username   = "main"     
    public_key = file("main.pub")  
  }

  # OS 디스크 설정: 운영체제 디스크의 캐싱 및 스토리지 종류 설정
  os_disk {
    caching              = "ReadWrite"
    storage_account_type = "StandardSSD_LRS"      
  }

  # 네트워크 인터페이스 설정: VMSS 인스턴스의 네트워크 연결 구성
  network_interface {
    name                      = "main-vmss-nic"
    primary                   = true
    network_security_group_id = azurerm_network_security_group.main_web_nsg.id
    ip_configuration {
      name  = "main-vmss-ipconfig"
      subnet_id = azurerm_subnet.main_web1.id
      # Application Gateway의 백엔드 풀과 연관할 NIC의 IP 구성을 설정
      application_gateway_backend_address_pool_ids = [
        for pool in azurerm_application_gateway.main_appgw.backend_address_pool : pool.id
      ]
    }
  }

  # 부팅 진단 설정: 부팅 진단 기능 설정
  boot_diagnostics {
    storage_account_uri = null
  }
}

 

19_null.tf

# Generalize_web1_vm: 초기 대기 작업
# - main-web1 VM의 생성 완료 후 일정 시간(약 120초) 대기하여,
#   이후 일반화 작업이 안정적으로 진행될 수 있도록 합니다.
resource "null_resource" "Generalize_web1_vm" {
  provisioner "local-exec" {
    command = "ping 127.0.0.1 -n 121 > nul"  # 120초 대기 (ping 121회)
  }
  depends_on = [azurerm_linux_virtual_machine.main_web1]
}


# Generalize_web1_1: VM 할당 해제
# - main-web1 VM을 deallocate 하여 일반화(generalize) 작업을 위한 준비를 합니다.
resource "null_resource" "Generalize_web1_1" {
  provisioner "local-exec" {
    command = "az vm deallocate --resource-group 02-main-rg --name main-web1"
  }
  depends_on = [null_resource.Generalize_web1_vm]
}

# Generalize_web1_delay: 첫 번째 대기 작업
# - deallocate 명령 실행 후 충분한 시간이 지나도록 약 30초 대기합니다.
resource "null_resource" "Generalize_web1_delay" {
  provisioner "local-exec" {
    command = "ping 127.0.0.1 -n 31 > nul"  # 약 30초 대기
  }
  depends_on = [null_resource.Generalize_web1_1]
}

# Generalize_web1_2: VM 일반화 작업 실행
# - main-web1 VM에 대해 일반화(generalize) 명령을 실행합니다.
resource "null_resource" "Generalize_web1_2" {
  provisioner "local-exec" {
    command = "az vm generalize --resource-group 02-main-rg --name main-web1"
  }
  depends_on = [null_resource.Generalize_web1_delay]
}

# Generalize_web1_delay2: 두 번째 대기 작업
# - 일반화 작업 후 추가 안정화를 위해 약 30초 대기합니다.
resource "null_resource" "Generalize_web1_delay2" {
  provisioner "local-exec" {
    command = "ping 127.0.0.1 -n 31 > nul"  # 약 30초 대기
  }
  depends_on = [null_resource.Generalize_web1_delay]
}

 

부록 ins.sh

#! /bin/bash

# SELinux의 즉시 적용 모드를 비활성화 (임시)
setenforce 0

# 모든 커널에 대해 SELinux를 영구적으로 비활성화 (부팅 시에도 적용)
grubby --update-kernel=ALL --args selinux=0

dnf install -y wget tar httpd php php-gd php-curl php-mysqlnd

wget https://ko.wordpress.org/wordpress-6.7.2-ko_KR.tar.gz

tar xvfz wordpress-6.7.2-ko_KR.tar.gz -C /root/

cp -ar /root/wordpress/* /var/www/html/

sed -i "s/DirectoryIndex index.html/DirectoryIndex index.php/g" /etc/httpd/conf/httpd.conf

cp /var/www/html/{wp-config-sample.php,wp-config.php}

sed -i "s/database_name_here/wordpress/g" /var/www/html/wp-config.php

sed -i "s/username_here/main/g" /var/www/html/wp-config.php

sed -i "s/localhost/10.0.5.4/g" /var/www/html/wp-config.php

sed -i "s/password_here/It12345\!/g" /var/www/html/wp-config.php

# base64 인코딩된 문자열을 디코딩하여 간단한 헬스 체크 페이지(health.html)를 생성
echo -n 'PGh0bWw+PGJvZHk+PGgxPmhlYWx0aC10ZXN0cGFnZS0xPC9oMT48L2JvZHk+PC9odG1sPg==' | base64 -d > /var/www/html/health.html

chown -R apache.apache /var/www/

systemctl enable --now httpd
반응형