테라폼으로 AWS 환경 구축하기 4장. 모듈을 이용하여 코드 다시 쓰기

2022.04.29

2장에서 코드를 직접 써보고 3장에서 모듈 등 다른 기능에 대해서 알아보았습니다.
그런데 작성한 코드를 보면 중복된 내용이 들어가는 부분이 상당히 많습니다.

## public Seucurity Group
resource "aws_security_group" "publicSG01" {
  name = "public-SG-01"
  description = "Allow all HTTP"
  vpc_id = aws_vpc.test.id
  ingress {
    cidr_blocks = [ "0.0.0.0/0" ]
    from_port = 80
    protocol = "tcp"
    to_port = 80
  }

  egress {
    cidr_blocks = [ "0.0.0.0/0" ]
    from_port = 0
    protocol = "-1"
    to_port = 0
  }
}

## private Security Group
resource "aws_security_group" "privateEC2SG01" {
---
}

resource "aws_security_group" "privateRDSSG01" {
---
}

(변수 값만 조금씩 다른 리소스의 코드 블록이 많다..)

이렇게 코드를 작성하면 문제가 발견해도 찾지 못한다거나 코드가 길어져서 바로 이해하기 힘들어지는 등 관리하기가 힘들어집니다.
이전 글에서 알게 된 모듈이나 워크스페이스 등을 사용하여 이러한 문제를 해결해보도록 하겠습니다.

환경

전체적인 구성 자체는 저번 구성에서 Nat Gateway만을 추가하였습니다.

단 환경에 따라 변수를 다르게 조정하여 각 리소스의 스펙은 다르게 설정하겠습니다.

코드 작성

흐름

다음과 같은 흐름으로 작업을 진행합니다.

  1. 환경 나누기(dev/prd)
  2. 모듈, 환경 코드 작동 확인
  3. 모듈 및 환경 코드 작성
  4. 리소스 생성 확인 후 삭제
  5. prd 환경 코드 작성

환경 나누기

환경을 나누기 위해서는 크게 2가지 방법이 있습니다.
첫 번째는 환경 별로 다른 디렉터리를 만들고 파일을 관리하는 방법입니다.

- env
  - dev
    - main.tf
    - outputs.tf
    - variables.tf
    - terraform.tfvars
  - prd
    - main.tf
    - outputs.tf
    - variables.tf
    - terraform.tfvars
- modules
  - iam
    - main.tf
    - variables.tf
    - outputs.tf
  - ec2
    - main.tf
    - variables.tf
    - outputs.tf
  - ...

이렇게 관리하면 두 환경이 섞일 일이 없이 확실히 구분이 가능합니다. 또한 모듈만 완성 되면 다른 환경에서는 변수만 바꾸면 됩니다.
하지만 파일 수가 너무 많이 늘어나는 등 단점도 존재합니다.
이번 글에서는 prd 와 dev 라는 2개의 직접 환경 폴더를 나누어 관리하도록 하겠습니다.

다른 방법으로는 테라폼에서 제공하고 있는 워크스페이스기능을 이용하는 것 입니다. 워크스페이스를 이용하면 같은 코드라도 환경을 나누어 사용할 수 있습니다.

resource "foo" "bar" {
  name = "${terraform.workspace == "prd" ? "prd-resource" : "dev-resource"}"
}

이와 같이 workspace 라는 파라미터로 적용할 값을 설정할 수 있습니다.

모듈, 환경 코드 작동 확인

서두에서 언급한대로 이전에 작성했던 코드를 보면 하나의 tf 파일에 모든 내용이 작성되어 있습니다.
이렇게 하나의 파일에 서비스에 관한 모든 리소스를 작성하면 코드가 너무 길어져 관리가 어렵게 됩니다. 또한 재사용도 할 수 없게 됩니다.
모듈화를 하면 이러한 문제점을 해결할 수 있습니다.

이전의 코드는 깃허브를 참고해주세요.

우선 디렉터리를 다음과 같이 환경(env)과 모듈(modules)로 나눕니다.

- env
  - dev
  - prd
- modules
  - network
  - ec2
  - ...

modules 아래에는 모듈화 할 서비스(EC2, RDS 등)나 기능(network 등) 단위로 디렉터리를 나눕니다. env 아래에는 사용할 환경 별로 디렉터리를 생성합니다.
그리고 아래와 같이 각 디렉터리 아래에 main.tf, variables.tf, outputs.tf, terraform.tfvars(환경 디렉터리만)을 생성합니다.

- env
  - dev
    - main.tf
    - outputs.tf
    - variables.tf
    - terraform.tfvars
  - prd
    - ...
- modules
  - network
    - main.tf
    - variables.tf
    - outputs.tf
  - ec2
    - ...
  - ...

우선 잘 작동하는지 테스트하기 위해 간단하게 VPC를 작성하고 테스트 해보겠습니다.
모듈의 main.tf 에는 해당 모듈로 작성할 리소스(전체적인 틀)을 작성합니다.

# modules/network의 main.tf
# VPC
resource "aws_vpc" "vpc" {
  cidr_block = var.vpc_cidr
  tags = {
    "name" = "${var.env}-vpc"
  }
}

# Subnets
resource "aws_subnet" "subnet" {
  vpc_id            = aws_vpc.vpc.id
  cidr_block        = var.subnet_cidr
  availability_zone = data.aws_availability_zones.available.names[0]
  tags = {
    "name" = "${var.env}-vpc"
  }
}

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

그리고 variables.tf에는 main.tf에서 이용하는 변수를 입력합니다.
이 때 모듈의 main.tf에서 이용하는 변수는 해당 모듈 디렉터리의 variables.tf와 환경 디렉터리의 variables.tf 양쪽에 정의되어 있어야합니다.
모듈 디렉터리의 variables.tf는 빈 값으로 설정해도 괜찮습니다.

# modules/network/variables.tf
variable "vpc_cidr" {}
variable "env" {}
variable "subnet_cidr" {}

# env/dev/variables.tf
variable "env" {
  description = "env name"
  default     = ""
}

variable "vpc_cidr" {
  description = "vpc cidr"
}

variable "subnet_cidr" {
  description = "subnets cidr"
}

여기까지 작성이 완료되었다면 dev디렉터리 아래의 main.tf을 다음과 같이 작성합니다.
위에서 모듈화 한 내용들을 main.tf에서 불러와서 필요한 값만 지정해줍니다.

# env/dev/main.tf
terraform {
  required_version = "~> 1.0.9"

  required_providers {
    aws = "~> 4.0"
  }
}

provider "aws" {
  region = "ap-northeast-1"
}

module "network" {
  source   = "../../modules/network"
  vpc_cidr = var.vpc_cidr
  subnet_cidr = var.subnet_cidr
  env = var.env
}

변수의 실제 값은 terraform.tfvars에 정의합니다.

# env/dev/terraform.tfvars
env = "dev"
vpc_cidr = "202.2.0.0/16"
subnet_cidr = "202.2.0.0/24"

여기까지 작성이 완료되었으면 dev디렉터리에서 terraform init으로 참조된 모듈을 정의한 후 terraform plan으로 제대로 작동되는지 확인해봅시다.

모듈 및 환경 코드 작성

이후로 나머지 리소스(EC2, SecurityGroup, RDS ...)을 똑같이 모듈화 한 뒤 환경의 main.tf에서 해당 모듈을 불러오고 설정하는 작업의 반복입니다.

기존의 코드를 어떤식으로 나누면 되는지 network 모듈을 예로 들어 설명하겠습니다.

network모듈에는 VPC, Subnet, VPC Endpoint 등 네트워크와 관련된 리소스를 정의합니다. 이전의 코드를 확인해보면 서브넷이나 라우팅 테이블 연결 등 반복되는 부분이 많습니다.

# Subnets
## public subnet
resource "aws_subnet" "publicSubnet1" {
  ...
}
  ...

resource "aws_subnet" "privateSubnet2" {
  ...
}

### subnet associate
resource "aws_route_table_association" "publicRTbAssociation01" {
  ...
}
  ...

resource "aws_route_table_association" "privateRTbAssociation04" {
  ...
}

이렇게 반복되는 count를 이용하여 간단하게 정의할 수 있습니다.

# modules/network/main.tf
# 서브넷 생성의 반복
resource "aws_subnet" "public_subnet" {
  count             = var.sub_count
  vpc_id            = aws_vpc.vpc.id
  cidr_block        = cidrsubnet(var.vpc_cidr, 8, count.index)
  availability_zone = data.aws_availability_zones.available.names[count.index]
  tags = {
    "Name" = "${var.env}-public-subnet-${count.index + 1}"
  }
}

# 라우팅 테이블 연결의 반복
resource "aws_route_table_association" "public_rtb_association" {
  count          = var.sub_count
  subnet_id      = aws_subnet.public_subnet[count.index].id
  route_table_id = aws_route_table.public_rtb.id
}

코드에 사용되는 sub_count라는 변수는 위에서 설명한대로 variables.tf에 정의할 필요가 있습니다.
이렇게 생성되는 리소스의 실제 스펙을 모듈에 정의해두면 환경 디렉터리의 main.tf에서는 몇 개의 변수만 입력하면 몇 번이고 해당 모듈을 재사용 할 수 있습니다.

# env/dev/main.tf
module "network" {
  source    = "../../modules/network"
  vpc_cidr  = var.vpc_cidr
  sub_count = var.resource_count
  env       = var.env
}

모듈을 정의한 후 terraform init 명령어로 설정 파일에 모듈을 등록합니다.
이 후 terraform plan -> terraform apply로 리소스의 생성을 확인합니다.

이렇게 이전에 작성한 코드에서 반복의 정리 및 코드의 재사용 가능성을 고려하여 코드를 작성해나갑니다.
작성한 코드는 깃허브를 참고해주세요.

리소스 생성 확인 후 삭제

모든 코드를 작성한 후 terraform apply를 실행하여 리소스가 전부 잘 생성되는지 확인하고 문제없이 생성되었다면 필요없는 비용이 발생하지 않도록 terraform destory로 리소스를 삭제해줍니다.

prd 환경 코드 작성

dev 환경에서 모든 작업이 완료되었다면 env아래에 prd 디렉터리를 생성하여 dev 환경의 내용을 그대로 복사합니다. 그리고 terraform.tfvars의 변수 값만 수정하면 간단하게 prd 환경의 리소스를 생성할 수 있습니다.
이렇게 간단하게 새로운 환경의 리소스를 작성할 수 있는게 모듈화의 장점입니다.

마치며

이번 글에서는 IAM 등의 리소스 작성은 하지 않았습니다. 필요에 따라 모듈을 생성해주세요.
모듈화를 잘 사용하면 재사용성을 높여 효율적인 리소스 관리가 가능해집니다.
저도 아직 많이 실력이 부족하여 코드가 아주 효율적이지는 못합니다. 모두 같이 노력하여 테라폼 마스터가 되도록 합시다!

긴 글 읽어주셔서 감사합니다.
오탈자 및 내용 피드백은 언제나 환영합니다. must01940 지메일로 연락 주시면 감사합니다!


본 블로그 게시글을 보시고 문의 사항이 있으신 분들은
클래스메소드코리아 (info@classmethod.kr)로 연락 주시면 빠른 시일 내 담당자가 회신 드릴 수 있도록 하겠습니다 !