count, map, collection, array같은 테라폼 표현식과 dynamic 블록을 사용하는 방법에 대한 내용으로 코드의 중복을 제거하고 여러 개의 인프라를 배포하는 방법에 대해 배운다.
count인수를 사용해 여러 리소스를 프로비저닝
dynamic 블록을 사용해 여러 개의 블록 생성하기
3.2 여러 환경에서 인프라를 프로비저닝하기
테라폼 구성
main.tf파일은 프로비저닝 되어야 할 리소스들의 구성을 담고 있다.
variables.tf파일은 변수 선언을 담고 있다.
terraform.tfvars파일은 변수의 값을 담고 있다.
기본 구성 작동 방법
빈 폴더에 환경별로 폴더를 만든다. dev, test production
기본 테라폼 구성을 각 폴더에 동일하게 복사한다.
resource_group_name = "RG-Appdemo"
service_plan_name = "Plan-App"
environment = "DEV"
해당 구성은 다른 환경에 영향을 주지 않고, 환경별로 리소스를 쉽게 추가하거나 제거할 수 있다.
하지만, 코드 중복이 발생하기 때문에 유지보수 비용이 든다.
수정안
이 구조를 사용하면 plan, apply 명령시 -var-file 옵션을 추가해야 하지만, 공통 부분이 하나만 있고환경별로 채워야 할 테라폼 변수 파일이 각각 존재하기 때문에 코드를 변경하거나 새로운 환경이 발새할 경우 적은 작업만 하면 된다.
3.3 count인수를 사용해서 여러 리소스를 프로비저닝 하기
실무에서 리소스의 수평 확장해야 할 경우가 있을 수 있는데
중복 코드를 사용하지 않고 동일한 리소스를 여러 개 프로비저닝 하는 테라폼 구성 작성하기
프로비저닝할 동일한 리소스의 수를 빠르게 증가시키거나 감소시키기
## main.tf
terraform {
required_version = "~> 1.1"
required_providers {
azurerm = {
version = "~> 3.23"
}
random = {
source = "hashicorp/random"
version = ">= 3.0.0" # arm 지원 random 버전
}
}
}
provider "azurerm" {
features {}
}
resource "random_string" "random" {
length = 4
special = false
upper = false
}
resource "azurerm_resource_group" "rg" {
name = "${var.resource_group_name}-${var.environment}-${random_string.random.result}"
location = var.location
tags = {
ENV = var.environment
}
}
resource "azurerm_service_plan" "plan" {
name = "${var.service_plan_name}-${var.environment}-${random_string.random.result}"
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
sku_name = "S1"
os_type = "Linux"
}
resource "azurerm_linux_web_app" "app" {
count = var.webapp_count
name = "${var.app_name}-${var.environment}-${random_string.random.result}-${count.index + 1}"
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
service_plan_id = azurerm_service_plan.plan.id
site_config {} # 웹 어플리케이션의 구성 설정을 정의하는 블록으로 빈칸이라면 기본값이 적용
# 웹 앱의 세부 구성 요소(애플리케이션 스택, HTTP/HTTPS 동작, 리소스제한, 사용자 지정 설정 등)
}
## variables.tf
resource_group_name = "RG-App"
service_plan_name = "Plan-App"
environment = "DEV1"
webapp_count = 2
webapp_count라는 애저 앱 서비스의 개수를 설정
azurerm_linux_web_app 리소스에 count인수를 추가해 값을 webapp_count 변수로 설정
azurerm_linux_web_app 리소스 name 속성에 count인수의 현재 인덱스 값을 접미사로 추가
하지만 이처럼 index를 사용하는 조건문을 사용하는 것 보다는 모든 속성을 동일하게 유지하는 것이 좋다. 따라서 이 장의 다음에서 for_each 표현식을 학습한다.
3.4 맵 사용하기
표준 변수 유형 말고도 다양한 유형의 변수들을 제공한다.
맵은 다음 용도로 사용될 수 있다.
테라폼 리소스에 있는 블록의 모든 속성을 단일 변수에 설정
동일한 리소스의 여러 인스턴스에 대한 데이터 선언 후 for_each를 통해 반복
이 예제에서는 맵의 사용 방법에 대한 두 가지 방법을 살펴본다.
# variables.tf
variable "tags" {
type = map(string)
description = "Tags"
default = {}
}
variable "app_settings" {
type = map(string)
description = "App settings of the web app"
default = {}
}
# main.tf
resource "azurerm_linux_web_app" "app" {
name = "${var.app_name}-${var.environment}-${random_string.random.result}"
location = azurerm_resource_group.rg-app.location
resource_group_name = azurerm_resource_group.rg-app.name
service_plan_id = azurerm_service_plan.plan-app.id
site_config {}
app_settings = var.app_settings
}
# terraform.tfvars
tags = {
ENV = "DEV1"
CODE_PROJECT = "DEMO"
}
app_settings = {
KEY1 = "VAL1"
}
variables.tf에서 맵 형식의 변수 두 개를 설정
리소스를 위한 tags와 서비스를 위한 app_settings에 값을 설정
테라폼 구성 내에서 변수들을 사용해 리소스 그룹과 앱 서비스를 프로비저닝
merge를 사용해 두 맵을 병합하는 것도 가능하다.
# variables.tf
variable "custom_app_settings" {
description = "Cusom app settings"
type = map(string)
default = {}
}
# terraform.tfvars
custom_app_settings = {
APP = "1"
}
# main.tf
locals {
default_app_settings = {
"DEFAULT_KEY1" = "DEFAULT_VAL1"
}
}
이렇게 추가해주면 다음과 같은 app_settings 값을 얻을 수 있다.
3.5 객체로 구성된 맵 순환하기
count가 아닌 객체를 이용해 리소스를 프로비저닝 하는 방법에 대해 배워본다.
# variables.tf
variable "web_apps" {
description = "List of web App to create"
type = map(object({
name = string
location = optional(string, "westeurope")
serverdatabase_name = string
}))
}
# terraform.tfvars
web_apps = {
webapp1 = {
"name" = "webappdemobook1"
"location" = "westeurope"
"serverdatabase_name" = "server1"
},
webapp2 = {
"name" = "webapptestbook2"
"serverdatabase_name" = "server2"
}
}
# main.tf
resource "azurerm_linux_web_app" "app" {
for_each = var.web_apps
name = each.value["name"]
location = lookup(each.value, "location", "westeurope")
resource_group_name = azurerm_resource_group.rg-app.name
service_plan_id = azurerm_service_plan.plan-app.id
site_config {}
connection_string {
name = "DataBase"
type = "SQLServer"
value = "Server=${each.value["serverdatabase_name"]};Integrated Security=SSPI"
}
}
새로운 객체 맵 변수를 선언하고 다음 속성들(name, location(옵션), serverdatabase_name)을 포함하도록 정의
객체 맵으로 구성된 변수의 값들을 각 앱 서비스의 속성에 맞게 설정
azurerm_linux_web_app 리소스에 for_each 표현식을 사용해 맵의 값을 순환(for_each의 each, value를 이용해 가져오거나 테라폼 내장함수 lookup함수를 이용)
3.6 dynamic 블록을 사용해서 여러 개의 블록 생성하기
테라폼 리소스는 다음의 요소들로 정리된다.
azurerm_linux_web_app 리소스 내부에 site_config블록 같이 여러 개의 속성을 묶은 블록 형태의 정의
테라폼의 주요 특징 중 하나인 dynamic 블록은 리소스 내부에 정의되는 블록들을 반복하는데 사용된다.
dynamic 블록을 이용해 azurerm_network_security_group에 여러 security_rule 블록을 정의한다.
dynamic "security_rule" {
for_each = var.nsg_rules
content {
name = security_rule.value["name"]
priority = security_rule.value["priority"]
direction = security_rule.value["direction"]
access = security_rule.value["access"]
protocol = security_rule.value["protocol"]
source_port_range = security_rule.value["source_port_range"]
destination_port_range = security_rule.value["destination_port_range"]
source_address_prefix = security_rule.value["source_address_prefix"]
destination_address_prefix = security_rule.value["destination_address_prefix"]
}
}
for_each를 사용해도 되지만, 조건부로 사용하거나 중첩된 for문을 이용해야 하는경우 dynamic이 유용하게 쓰일 수 있다.
dynamic "boot_diagnostics" {
for_each = local.use_boot_diagnostics == true ? [1] : []
content {
storage_accout_uri = "https://storageboot.blob.core.windows.net/"
}
}
}
3.7 맵 필터링하기
대부분 맵으로 정의된 리스트 값을 바탕으로 리소스를 만들지만 리스트 값 중 필터링이 필요한 경우를 알아본다.
# terraform.tfvars
web_apps = [
webapp1 = {
"name" = "webapptestbook1"
"os" = "Linux"
},
webapp2 = {
"name" = "webapptestbook2"
"os" = "Linux"
},
webapp3 = {
"name" = "webapptestbook3"
"os" = "Windows"
}
]
# main.tf
locals {
linux_web_app = toset([for each in var.web_apps : each.name if each.os == "Linux"])
Windows_web_app = toset([for each in var.web_apps : each.name if each.os == "Windows"])
}
# main.tf 리소스
resource "azurerm_linux_web_app" "app" {
for_each = local.linux_web_app
name = each.value
location = "westeurope"
site_config {}
}
해당 어플리케이션을 프로비저닝 하면 두 개의 로컬 변수를 만들어 os 키 값이 리눅스, 윈도우인지에 따라서 변수를 읽어온 후 어플리케이션을 프로비저닝 한다.