본문 바로가기
School/TFAS

[TFAS] 챕터 11 YAML 파일로 관리하는 VPC 모듈 만들기

by 세똔구리 SEDDONGURI 2025. 12. 4.
TFAS (TerraForm Addicted School)의 내용을 정리한 글입니다.
심각한 테라폼 중독입니다 - 책으로 스터디를 진행합니다.


인프라 관리 엔지니어의 취향 혹은 내부 컨벤션에 따라 동일 결과물을 내는 테라폼 구현체는 크게 달라질 수 있다. 이는 테라폼의 높은 자유도 덕분이며 이렇게 높은 자유도를 가지게 되는 기술들은 대체로 모범사례라는 암묵적으로 합의하는 효율적인 규칙들이 존재한다. 

여러 모범 사례를 고려하면서도 각 요구사항에 맞게 그리고 변경에 유연하게 대처할 수 있는 VPC 모듈을 만들어 보자. 

앞선 장에서 테라폼 모듈을 만드는 순서를 아래와 같이 정의했다.

  1. 요구사항 정리 및 입력값 정하기
  2. 입력값을 모듈에게 전달할 방법 정하기
  3. 모듈 만들기
  4. 유효성 검사
  5. 모듈 출력 설정하기

이 순서를 통해 확장 가능한 VPC 모듈을 만들어 보면서 모듈 만드는 연습을 해보자.

 

입력값 정하기

어떤 입력값을 받아서 어떤 동작을 한 후 출력값을 내보내는 것이 모듈이라고 했을 때 인풋과 아웃풋을 명확하게 하기 위해서 모듈의 요구사항을 정의해야 한다.

요구사항 정의

  • 모든 서브넷은 동일한 크기를 가진다.
  • 서브넷은 사용 용도에 맞게 가용 영역 개수만큼 구성할 수 있다.
  • 용도별 서브넷의 이름은 인터넷 연결 여부에 따른 지정된 접두사를 사용하고, 이름에 따라 라우트 테이블이 자동으로 연결된다.
    • 서브넷 접두사 이름은 프라이빗 "pri-", 퍼블릭 "pub-"으로 사용한다.
  • 용도별 서브넷의 가용 영역의 수는 임의로 지정 가능하다.
  • 생성하는 라우트 테이블 수는 다음과 같다.
    • 퍼블릭의 경우 한 개
    • 프라이빗의 경우 가용 영역 수만큼
  • 인터넷 게이트웨이 또는 nat 게이트웨이로 향하는 트래픽 라우팅은 각 라우트 테이블에 자동으로 구성된다.
  • 퍼블릭 서브넷이 하나라도 존재하면 인터넷 게이트웨이를 자동으로 생성한다.
  • nat 게이트웨이는 지정한 플래그에 따라 만들 수도, 만들지 않을 수도 있다.
  • nat 게이트웨이를 생성할 때, 어떤 서브넷에 생성할 지 지정할 수 있고, 한 개만 만들 것인지 가용 영역 별로 만들 것인지 선택할 수 있다.

이런 요구사항을 가지는 AWS VPC 모듈을 만든다고 했을 때, VPC 모듈에 입력해야 하는 입력값을 YAML 파일로 관리할 수 있게 YAML로 입력값을 정의해 보자.

cidr: 10.0.0.0/16

env: production
team: devops

subnet_newbits: 8
subnet_azs: [a, c, b, d]
subnets:
  pub-nat: [0, 1]
  pri-app: [2, 3]
  pri-db: [4, 5]
  pri-network: [6, 7]
  pri-msk: [8, 9, 10]

pub-nat:
  create: true
  subnet: pub-nat
  per_az: true

yaml 입력값은 인터넷 게이트웨이와 라우트 테이블은 구성되어 있지 않는데, 이는 자동으로 생성되도록 모듈이 작성될 것이다. 다음은 입력값을 모듈에 전달할 방법을 생각해 보자.

입력값을 모듈에게 전달할 방법 정하기

AWS 계정 안에서 VPC 가 입력값 YAML의 파일수만큼 생성된다고 해보자. 이때 vpc에 종속된 다양한 서비스를 한눈에 파악하고 관리하기 위해서는 vpc 별로 디렉터리 구조를 분리하는 것이 좋다. 따라서 다음과 같은 구조를 생각할 수 있다.

.
├── env (실행 환경)
│   ├── ...
│   └── main.tf
└── info_files(VPC별 명세 파일 모음 디렉터리)
    ├── vpc-1(VPC 이름)
    │   ├── 서버 구성 파일
    │   ├── 보안 그룹 구성 파일
    │   ├── 데이터베이스 구성 파일
    │   └── vpc.yaml
    └── vpc-2(VPC 이름)
        ├── 서버 구성 파일
        ├── 보안 그룹 구성 파일
        ├── 데이터베이스 구성 파일
        └── vpc.yaml

 

vpc 별로 관리되는 구성 파일을 하나의 폴더로 모아두었다. 이렇게 명세 파일을 관리한다면 실행 환경 로직에서 하나의 vpc를 만들기 위해서 info_files/ 디렉터리 하위의 vpc.yaml 파일이 존재하는 디렉터리를 모두 찾아야 한다. 이는 다음과 같이 구현할 수 있다.

locals {
  info_files = "${path.root}/../info_files"

  vpc_set = toset([
    for vpcfile in fileset(local.info_files, "*/vpc.yaml") : dirname(vpcfile)
  ])
}

 

해당 vpc_set 변수를 모듈의 입력값으로 사용하는 테라폼은 다음과 같이 구성 가능하다.

locals {
  env_tags = {
    tf_env = "ch11/env"
  }
}

module "vpc" {
  for_each = local.vpc_set
  source   = "../modules/vpc"

  name      = each.key
  attribute = yamldecode(file("${local.info_files}/${each.key}/vpc.yaml"))

  tags = local.env_tags
}

 

attribute라는 하나의 변수를 사용하여 변경에 대한 최소한의 변경점을 허용하면서 모듈 설계를 할 수 있다. 하나의 모듈에 필요한 값을 수정하는 방식을 사용하면 미래에 변경이 필요한 시점에 변경이 복잡해질 수 있다.

모듈 만들기

vpc 생성

먼저 name과 attribute라는 변수를 선언하자. 모듈을 작성하기 시작하는 단계에서는 변수 이름만 선언하고 모듈 구성이 완료되면 변수 타입 지정 및 유효성 검사를 추가한다.

modules/vpc/variables.tf

variable "name" {
  type = string
}

variable "attribute" {
  type = map(any)
}

variable "tags" {
  type = map(any)
}

 

모듈에 적용할 공통 태그를 만들고 모든 리소스가 공통 태그를 사용하도록 설정한다.

modules/vpc/main.tf

locals {
  vpc_name = var.name

  module_tag = merge(
    var.tags,
    {
      tf_module = "vpc"
      Env       = var.attribute.env
      Team      = var.attribute.team
      VPC       = "${local.vpc_name}-vpc"
    }
  )
}

 

vpc를 생성하는 테라폼 구성은 다음과 같다. 앞서 yaml로 정의한 값 중 cidr을 사용한다.

modules/vpc/main.tf

locals {
  vpc_cidr = var.attribute.cidr
}

resource "aws_vpc" "this" {
  cidr_block           = local.vpc_cidr
  enable_dns_support   = true
  enable_dns_hostnames = true
  tags = merge(
    local.module_tag,
    {
      Name = "${local.vpc_name}-vpc"
    }
  )
}

 

퍼블릭 서브넷

pub-이라는 접두사가 붙은 서브넷이 존재하는 경우에만 true 가 되는 플래그 변수 퍼블릭 서브넷을 생성한다.

modules/vpc/main.tf

# public subnets ===========================================
locals {
  subnets = var.attribute.subnets
  enable_igw = anytrue(
    [for k, v in local.subnets : split("-", k)[0] == "pub"]
  )
}

resource "aws_internet_gateway" "this" {
  vpc_id = local.vpc_id
  count  = local.enable_igw ? 1 : 0

  tags = merge(
    local.module_tag,
    {
      Name = "${local.vpc_name}-igw"
    }
  )
}


resource "aws_route_table" "public" {
  count  = local.enable_igw ? 1 : 0
  vpc_id = local.vpc_id

  tags = merge(
    local.module_tag,
    {
      Name = "${local.vpc_name}-rt-pub"
    }
  )
}

resource "aws_route" "public_igw" {
  count                  = local.enable_igw ? 1 : 0
  destination_cidr_block = "0.0.0.0/0"
  route_table_id         = aws_route_table.public[count.index].id
  gateway_id             = aws_internet_gateway.this[count.index].id
}

라우트 테이블과 라우트에는 별도의 public, public_igw라는 이름을 사용했다. 이는 모듈 내에서 동일한 리소스 블록을 선언할 수 있는지에 따라 모듈 내에서 최대 한 번만 선언될 것인지에 따라서 this를 사용하거나 별도의 이름을 사용할 수 있다.

퍼블릭 라우트의 리소스는 count에 기반한 생명주기를 가지기 때문에 aws.route_table.public [0]. id 가 아닌 count.index의 메타변수를 사용해서 count와 동일한 생명주기를 가질 수 있도록 하드코딩하지 않고 사용한다.

 

프라이빗 라우트 테이블

퍼블릭 서브넷 유무에 관계없는 프라이빗 라우트 테이블을 만들어보자. 프라이빗 라우트 테이블을 가용 영역별로 나누는 이유는 nat 게이트웨이를 가용 영역별로 만들 수 있기 때문이다.

modules/vpc/main.tf

locals {
  subnet_azs = var.attribute.subnet_azs
}

resource "aws_route_table" "private" {
  for_each = toset(local.subnet_azs)

  vpc_id = local.vpc_id

  tags = merge(
    local.module_tag,
    {
      Name = "${local.vpc_name}-rt-pri-${each.value}"
    }
  )
}

nat 게이트웨이를 하나만 만드는 경우 라우트 테이블을 하나만 만들면 되지 않을까 생각할 수 있지만 nat의 개수가 늘어나야 하는 경우 인프라 변경의 난이도가 증가한다.

모든 서브넷이 하나의 라우트 테이블을 바라보다가 시간이 지난 후 nat 게이트웨이가 가용성을 위해 늘어나야 한다면, 서브넷의 라우트 테이블의 연결이 모두 변경되어야 하고, 가용 영역별 nat 게이트웨이로 향하는 라우트도 새롭게 추가되어야 한다. 따라서 처음부터 라우트 테이블을 가용 영역별로 만들어두고 모든 가용 영역이 하나의 nat 게이트웨이로 향하게 한다면 라우트 테이블 안의 라우트 하나만 변경해 주면 되므로 변경점이 줄어들게 된다. 따라서 가용 영역마다 라우트 테이블을 만들어두고 서브넷도 이에 따라 연결해 두면 추후 변경에 대한 부담이 줄어들 게 된다.

 

서브넷 생성

subnet locals 블록은 VPC 내에서 서브넷을 동적으로 생성하기 위한 데이터를 준비하는 부분이다. 이중 for 루프를 사용해서 subnets 맵을 순회 한다. 외부 루프는 각 서브넷 그룹(예: pub-sub, pri-sub)을 돌고, 내부 루프는 그 그룹에 속한 각 인덱스를 돈다. 각 반복에서 서브넷의 이름, 가용영역, CIDR 블록, 그리고 퍼블릭 여부를 담은 객체를 생성한다.

flatten 함수는 이중 루프로 생성된 중첩 리스트를 단일 리스트로 평탄화한다. 결과적으로 모든 서브넷 정보가 하나의 리스트에 담기게 되어, 이후 aws_subnet 리소스를 for_each로 생성할 때 편리하게 사용할 수 있다.

modules/vpc/main.tf

# subnets ===========================================
locals {
  subnet_newbits = var.attribute.subnet_newbits
  subnet_azs     = var.attribute.subnet_azs
  subnets        = var.attribute.subnets

  subnets_data = flatten([
    for name, indices in local.subnets : [
      for idx in indices : {
        name      = name
        az        = local.subnet_azs[index(indices, idx)]
        cidr      = cidrsubnet(local.vpc_cidr, local.subnet_newbits, idx)
        is_public = split("-", name)[0] == "pub"
      }
    ]
  ])
}

 

이렇게 생성된 서브넷 결과물을 vpc 생성을 위해 서브넷 이름을 키로 하고 데이터의 항목을 값으로 하는 맵의 형태로 변환한다. 이를 사용하여 입력에 따른 서브넷을 일괄적으로 생성하는 테라폼 구성은 다음과 같다.

modules/vpc/main.tf

locals {
  subnets_map = {
    for s in local.subnets_data : "${replace(s.name, "-", "")}_${s.az}" => s
  }
}

module "current" {
  source = "../../../ch09/meta_module"
}

locals {
  region_name = module.current.region_name
}

resource "aws_subnet" "this" {
  for_each = local.subnets_map

  vpc_id                  = local.vpc_id
  cidr_block              = each.value.cidr
  availability_zone       = "${local.region_name}${each.value.az}"
  map_public_ip_on_launch = each.value.is_public

  tags = merge(
    local.module_tag,
    {
      Name = "${local.vpc_name}-subnet-${each.value.name}-${each.value.az}"
    }
  )
}

 

서브넷과 라우트 테이블 연결

모든 퍼블릭 서브넷은 하나의 퍼블릭 라우트 테이블에 연결되고 프라이빗 라우트 서브넷은 가용 영역이 같은 프라이빗 라우트 테이블과 연결되어야 한다. 이를 위해 라우트 테이블을 참조하기 쉬운 형태로 정리한 후 서브넷과 라우트 테이블 사이의 연결에 필요한 연관 리소스를 정의한다.

modules/vpc/main.tf

# route table association ===========================================
locals {
  public_rt = try(aws_route_table.public[0].id, "")
  private_rt = {
    for k, v in aws_route_table.private : k => v.id
  }
}

resource "aws_route_table_association" "this" {
  for_each = local.subnets_map

  subnet_id      = aws_subnet.this[each.key].id
  route_table_id = each.value.is_public ? local.public_rt : local.private_rt[each.value.az]
}

 

NAT 게이트웨이와 라우트

먼저 서브넷 이름으로 서브넷 아이디를 참조하는 경우 실제 서브넷 이름을 사용하기 때문에 replace를 통해 대시를 언더바로 변경하여 실제 서브넷의 아이디를 가지고 온다. 이를 쉽게 사용하기 위해 서브넷의 실제 이름과 가용영역을 두 개의 키로 사용하여 서브넷 아이디를 값으로 매핑하는 2차원 맵을 정의할 수 있다.

modules/vpc/main.tf

# mapping ==========================================
locals {
  subnet_ids_with_az = {
    for k, v in aws_subnet.this : k => {
      for az in slice(local.subnet_azs, 0, length(v)) : az => aws_subnet.this["${replace(k, "-", "_")}_${az}"].id
    }
  }
}

 

해당 변수를 이용하여 생성하는 nat gateway의 테라폼 코드는 아래와 같다.

modules/vpc/gateway.tf

# NAT gateway ===========================================
locals {
  nat     = var.attribute.naaat
  nat_azs = slice(local.subnet_azs, 0, local.nat.per_az ? try(length(local.subnets[local.nat.subnet]), 0) : 1)
  nat_set = local.nat.create ? toset(local.nat_azs) : toset([])
}

## nat eip
resource "aws_eip" "this" {
  for_each = local.nat_set
  tags = merge(
    local.module_tag,
    {
      Name = "${local.vpc_name}-nat-${each.key}"
    }
  )
}

## nat gateway
resource "aws_nat_gateway" "this" {
  for_each      = local.nat_set
  allocation_id = aws_eip.this[each.key].id
  subnet_id     = local.subnet_ids_with_az[local.nat.subnet][each.key]

  tags = merge(
    local.module_tag,
    {
      Name = "${local.vpc_name}-nat-${each.key}"
    }
  )
}

resource "aws_route" "private_nat" {
  for_each = local.nat.create ? local.private_rts : {}

  route_table_id         = each.value
  destination_cidr_block = "0.0.0.0/0"
  nat_gateway_id         = aws_nat_gateway.this[element(local.nat_azs, index(local.subnet_azs, each.key) % length(local.nat_azs))].id
}

nat gateway 의 경우 per_az의 변수에 따라 한 개만 생성하거나 az 마다 생성해야 한다. subnet의 가용 영역 전체에 nat를 생성하기 위해 subnets [nat.subnet]의 로컬 변수를 사용하여 알아낸다. aws_route를 생성할 때 나머지 연산 기반으로 nat gateway id를 저장한다.

 

변수 유효성 검사

처음 선언한 변수는 현재 어떻게 사용해야 하는지 가이드라인이 없는 상태이다. 적절한 유효성 검사를 추가하여 변수에 대해서 잘못 사용하는 경우 빠르게 파악할 수 있도록 타입 유효성과 입력 유효성을 체크해 보자.

먼저 value에 대해서 타입을 선언하여 타입에 대한 유효성검사를 수행한다.

variable "name" {
  type        = string
  description = "구분되는 VPC 이름"
}

variable "attribute" {
  description = "VPC 속성 정의"
  type = object({
    cidr           = string
    env            = optional(string, "develop")
    team           = optional(string, "devops")
    subnet_newbits = optional(number, 8)
    subnet_azs     = list(string)
    subnets        = optional(map(list(number)), {})
    nat = optional(object({
      create = optional(bool, true)
      per_az = optional(bool, false)
      subnet = optional(string, "")
    }), {})

  })

}

variable "tags" {
  description = "모든 리소스에 적용될 태그 (map)"
  type        = map(string)
  default     = {}
}

 

입력값에 대한 유효성 검사는 조건이 다양하다. 조건을 하나씩 확인해 보면서 어떤 블록을 사용하여 유효성 검사를 진행할지 확인해 보자.

env: 정해진 환경 이름 중에서 사용

env는 태깅을 위해 사용하는 값이다. 다만, 인프라 관리 입장에서 사내 환경이 정해져 있는 경우가 많기 때문에 사내에서 사용하는 환경 이름 중 하나의 입력을 보장하기 위해 입력값 유효성 검사를 수행해야 한다.

variable "attribute" {
  # ...

  validation { # env 값이 develop, staging, rc, production 중 하나인가?
    condition     = contains(["develop", "staging", "rc", "production"], var.attribute.env)
    error_message = "env 값이 develop, staging, rc, production 중 하나여야 합니다."
  }
}

 

subnet_azs: 알파벳 한 글자로만 이루어진 문자열 리스트

variable "attribute" {
  # ...

  validation {
    condition     = alltrue([for az in var.attribute.subnet_azs : can(regex("^[a-zA-Z]$", az))])
    error_message = "subnet_azs 값이 알파벳으로 구성되어야 합니다."
  }
}

subnet_azs는 문자열 리스트를 입력으로 받으며 알파벳 글자로 이루어진 문자열이다. 이때 정규 표현식을 사용해서 az라는 문자열이 알파벳 한 글자로만 이루어졌는지 확인한다. 이때, 오류가 발생한 여부에 따라서 true, false를 반환하기 위해 can을 사용하여 표현한다. 모든 subnet_azs에 대해서 검사하고 alltrue를 통해 전체가 검사 통과하는지 확인한다.

 

가용 영역: 선택한 가용 영역을 현재 사용할 수 있다.

subnet_azs로 받은 가용 영역이 현재 시점에 실제로 사용 가능한 영역인지 검사해야 한다. 

현재 사용 가능한 가용 영역은 데이터 블록을 사용해 조회할 수 있다. 

data "aws_availability_zones" "available" {
	state = "available"
}

여기서 문제는 현재 사용할 수 있는 가용 영역인지 검증하기 위해 데이터 블록을 실행해야 한다는 점이다. 그러나 변수 블록 내에 존재하는 중첩블록이며, 데이터 블록 실행보다 변수 유효성 확인을 먼저 수행한다.  즉 데이터 블록은 변수 유효성 검사가 끝난 후 실행되므로 검사 블록 안에서 데이터 블록의 값을 사용할 수 없다. 그러므로 개별 리소스가 만들어지기 직전과 직후에 검증을 수행하는 precondition과 postcondition 블록을 사용해야 한다.

resource "aws_subnet" "this" {
  # ...

  lifecycle {
    precondition {
      condition     = contains(local.available_azs, "${local.region_name}${each.value.az}")
      error_message = "${upper(each.value.az)} zone 은 현재 리전 (${local.region_name}) 에서 유효하지 않다. 사용 가능 영역: [${join(", ", [for az in local.available_azs : trimprefix(az, local.region_name)])}]"
    }
  }
}

 

서브넷 이름: "pub-" 또는 "pri-" 로만 시작 가능

var.attribute.subnet 은 맵 형식의 데이터다. 맵 안의 키가 서브넷의 이름이 되는데 서브넷의 이름은 반드시 "pub-", "pri-" 로만 시작하도록 제한해야 한다. 이때 precondition 블록을 사용하여 실패 지점을 리소스 단위로 정확히 특정하여 오류 메시지로 출력할 수 있다.

resource "aws_subnet" "this" {
  # ...

  lifecycle {
    precondition {
      condition     = contains(["pub", "pri"], split("-", each.value.name)[0])
      error_message = "[${local.vpc_name}] ${each.value.name} 이라는 서브넷 이름은 유효하지 않습니다. subnets 이름들은 모두 [pub-, pri-]로 시작해야 합니다."
    }
  }
}

 

네트워크 넘버: 동일한 VPC 안에서 겹칠 수 없다

서브넷별 리스트 속 네트워크 넘버를 의미하는 숫자는 서브네팅을 위해 사용되는 요소로 동일한 VPC 안에서 겹칠 수 없다. 동일한 CIDR을 가지는 서브넷은 생성할 수 없기 때문이다. 네트워크 넘버의 중복을 플래닝 단계부터 검증하여 입력값의 오류를 확인할 수 있으면 시간을 절약할 수 있다. 테라폼에서 리스트 안에서 중복 값이 있는지 확인할 수 있는 함수는 없기 때문에 기존 리소스와 중복이 제거된 리스트의 길이를 비교하는 방식으로 검증할 수 있다.

variable "attribute" {
  # ...

  validation {
    condition     = length(flatten([for k, v in var.attibute.subnets : v])) == length(distinct(flatten([for k, v in var.attibute.subnets : v])))
    error_message = "한 VPC 내에서 서브네팅을 위한 netnum 은 겹칠 수 없습니다."
  }
}

 

네트워크 넘버 리스트: 각 netnum의 길이는 subnet_azs의 길이보다 작거나 같다

네트워크 넘버 리스트만큼 서브넷이 생성되며, subnet_azs 리스트에 적힌 순서대로 가용영역을 할당한다. 각 서브넷의 네트워크 넘버 리스트의 길이는 subnets_azs 리스트의 길이보다 길 수 없다. 그러므로 유효성 검사를 통해 검증해야 비정상적인 작동을 피할 수 있다.

variable "attribute" {
  # ...

  validation {
    condition     = alltrue([for k, v in var.attribute.subnets : length(v) <= length(var.attribute.subnet_azs)])
    error_message = "각 subnet 의 netnum list 의 길이는 subnet_azs 의 길이 (${length(var.attribute.subnet_azs)})보다 작거나 같아야 합니다."
  }
}

 

nat.subnet: NAT 게이트웨이가 생성될 서브넷은 퍼블릭 서브넷

nat 게이트웨이를 생성할 때 nat 게이트웨이를 생성할 서브넷을 지정해야 한다. 서브넷의 라우트 테이블에는 반드시 인터넷 게이트웨이로 향하는 라우트가 존재해야 한다. 이건 AWS의 제한사항이므로 반드시 지켜져야 한다. 이는 nat 게이트웨이 리소스에 종속되는 유효성으로 리소스에 종속적인 유효성 검증은 precondition 블록을 활용하여 검증할 수 있다.

## nat gateway
resource "aws_nat_gateway" "this" {
  # ...

  lifecycle {
    precondition {
      condition     = split("-", local.nat.subnet)[0] == "pub"
      error_message = "[${local.vpc_name}] nat.subnet 으로는 퍼블릭 서브넷만 지정 가능합니다."
    }
  }
}

 

nat.subnet: subnets에 명시된 이름 중 하나여야 한다

nat 게이트웨이를 생성할 때 지정할 서브넷은 vpc 모듈 안에서 생성된 서브넷에서 참조해서 지정할 수 있어야 한다. 서브넷 이름이 var.attribute.subnets 맵에 등록된 이름이어야 제대로 서브넷 아이디를 참조할 수 있기 때문에, nat 게이트웨이의 서브넷 이름이 반드시 입력 yaml 파일의 subnets 항목에 기재된 이름 중 하나여야 한다.

variable "attribute" {
  # ...
  
  validation {
    condition     = !(var.attribute.nat.create && !contains([for k, v in var.attribute.subnets : k], var.attribute.nat.subnet))
    error_message = "[${local.vpc_name} VPC] nat.subnet 이름은 subnets 에 기재된 항목 중 하나여야 한다."
  }
}

 

모듈 출력값 설정

모듈 출력 블록의 진짜 용도는 실행 결과를 다른 모듈이나 실행 환경에서 참조하기 위한 것이다. VPC 모듈은 aws의 뼈대인 만큼 vpc 모듈의 출력값으로 다양한 리소스에서 참조할 수 있다. 

modules/vpc/output.tf

output "vpc_name" {
  value       = local.vpc_name
  description = "VPC 이름"
}

output "vpc_id" {
  value       = local.vpc_id
  description = "VPC ID"
}

output "vpc_cidr" {
  value       = local.vpc_cidr
  description = "VPC CIDR"
}

output "subnet_ids" {
  value       = local.subnet_ids
  description = "서브넷 ID"
}

output "subnet_ids_with_az" {
  value       = local.subnet_ids_with_az
  description = "AZ 별 서브넷 ID"
}

output "public_rt_id" {
  value       = local.public_rt
  description = "퍼블릭 라우팅 테이블 ID"
}

output "private_rt_id" {
  value       = local.private_rt
  description = "프라이빗 라우팅 테이블 ID"
}

output "igw_id" {
  value       = try(aws_internet_gateway.this[0].id, null)
  description = "인터넷 게이트웨이 ID"
}

output "nat_ids" {
  value = {
    for k in local.nat_set : k => aws_nat_gateway.this[k].id
  }
  description = "NAT 게이트웨이 ID"
}