✅ 이 블로깅은 테라폼으로 시작하는 IaC 책을 기반으로 작성한다.


테라폼을 사용하다 보면 점점 관리하는 리소스가 많아지고 구성이 복잡해 진다.

이 때 작업 효율성과 가시성, 종속성 등을 파악하기 위해 모듈로 분리하게 된다.

모듈은 Root Module과 Child Module로 구분된다.

모듈은 테라폼 구성의 집합으로 관리성, 캡슐화, 재사용성, 일관성표준화라는 키워드를 가지고 있다.

모듈 작성 기본 원칙

모듈 디렉터리 형식을 terraform-<프로바이더 이름>-<모듈 이름> 형식을 제안한다

terraform은 디렉터리 또는 레지스트리 이름이 테라폼을 위한 것이고 <프로바이더 이름>은 어떤 프로바이더의 리소스를 포함하고 있으며 <모듈 이름>은 부여된 이름이 무엇인지 판별할 수 있도록 한다.

모듈의 디렉토리 구조는 루트 모듈 하위에 자식 모듈을 구성하는 경우는 단순히 복잡한 코드를 분리하는 용도이다.

종속성이 발생할 수 있으므로 루트 모듈 사이에 아래처럼 모듈 디렉토리를 지정한다

└── t101-4week
    ├── modules                               # child-modules-home
    │   ├── terraform-random-pwgen
    │   │    ├── main.tf
    │   │    ├── output.tf
    │   │    └── variable.tf
    │   └── terraform-aws-ec2
    │        ├── main.tf
    │        ├── output.tf
    │        └── variable.tf
    └── t101-4week-basic                     # root-module
        └── main.tf

모듈화 해보기

자식 모듈 작성

random 프로바이더를 사용해서 random_pet는 이름을 자동으로 생성하고, random_password는 사용자의 패스워드를 설정한다

참고 ) Terraform Registry - random_pet

참고 ) Terraform Registry - random_password

### main.tf ###
resource "random_pet" "name" {
  keepers = {
    ami_id = timestamp()
  }
}

# DB일 경우 Password 생성 규칙을 다르게 반영 
resource "random_password" "password" {
  length           = var.isDB ? 16 : 10
  special          = var.isDB ? true : false
  override_special = "!#$%*?"
}
### variable ###
variable "isDB" {
  type        = bool
  default     = false
  description = "패스워드 대상의 DB 여부"
}
### output ###
output "id" {
  value = random_pet.name.id
}

output "pw" {
  value = nonsensitive(random_password.password.result) 
}

06-module-traning/modules/terraform-random-pwgen 하위에 main.tf variable.tf output.tf 파일을 생성한다.

$ terraform apply -auto-approve -var=isDB=true
	Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
	
	Outputs:
	
	id = "living-bobcat"
	pw = "0!#1SiLf4#sPBEnt"

-var=isDB=true 변수 값을 지정하고 apply를 실행하였다.

$ terraform state list
	random_password.password
	random_pet.name
$ terraform state show random_pet.name
	# random_pet.name:
	resource "random_pet" "name" {
	    id        = "living-bobcat"
	    keepers   = {
	        "ami_id" = "2023-09-23T19:05:45Z"
	    }
	    length    = 2
	    separator = "-"
	}
$ terraform state show random_password.password
	# random_password.password:
	resource "random_password" "password" {
	    bcrypt_hash      = (sensitive value)
	    id               = "none"
	    length           = 16
	    lower            = true
	    min_lower        = 0
	    min_numeric      = 0
	    min_special      = 0
	    min_upper        = 0
	    number           = true
	    numeric          = true
	    override_special = "!#$%*?"
	    result           = (sensitive value)
	    special          = true
	    upper            = true
	}

state에 이름과 패스워드가 저장되었다

$ cat terraform.tfstate | grep module

state 파일에는 module 정보가 아직 없다!

Untitled

그리고 루트모듈로 되어 있다

자식 모듈 호출 실습

자식 모듈을 호출하기 위해 modules와 같은 위치에 root 모듈 디렉토리와 main.tf를 생성한다.

module "mypw1" {
  source = "../modules/terraform-random-pwgen"
}

module "mypw2" {
  source = "../modules/terraform-random-pwgen"
  isDB   = true
}

output "mypw1" {
  value  = module.mypw1
}

output "mypw2" {
  value  = module.mypw2
}
/06-module-traning$ tree
	.
	├── 06-01-basic                     # root-module
	│   ├── main.tf
	│   └── terraform.tfstate
	└── modules                            # child-modules-home
	    └── terraform-random-pwgen
	        ├── main.tf
	        ├── output.tf
	        ├── terraform.tfstate
	        ├── terraform.tfstate.backup
	        └── variable.tf
	
	3 directories, 9 files

디렉토리 구조는 위와 같이 가져간다

Apply complete! Resources: 4 added, 0 changed, 0 destroyed.

Outputs:

mypw1 = {
  "id" = "allowing-zebra"
  "pw" = "wFzU1zGJWQ"
}
mypw2 = {
  "id" = "enormous-cat"
  "pw" = "U8Lb9uwP9fSx6ARE"
}

apply 실행 시에 output 처럼 리소스가 생성되며

$ terraform state list
	module.mypw1.random_password.password
	module.mypw1.random_pet.name
	module.mypw2.random_password.password
	module.mypw2.random_pet.name
$ cat terraform.tfstate | grep module
      "module": "module.mypw1",
      "module": "module.mypw1",
      "module": "module.mypw2",
      "module": "module.mypw2",
$ cat .terraform/modules/modules.json | jq
	{
	  "Modules": [
	    {
	      "Key": "",
	      "Source": "",
	      "Dir": "."
	    },
	    {
	      "Key": "mypw1",
	      "Source": "../modules/terraform-random-pwgen",
	      "Dir": "../modules/terraform-random-pwgen"
	    },
	    {
	      "Key": "mypw2",
	      "Source": "../modules/terraform-random-pwgen",
	      "Dir": "../modules/terraform-random-pwgen"
	    }
	  ]
	}

생성된 리소스를 state 파일에서 확인할 수 있다. 그리고 modules.json에서 실제로 이 모듈이 참조하고 있는 부분이 어디에 있는지까지 알려준다.

Untitled 1

모듈화하면 위와 같은 그래프로 루트 모듈과 modules로 분리된다.

나는 모듈화가 그저 디렉토리 분리와 용도에 따른 tf파일의 분리라고 생각했는데

테라폼에서 module 설정을 이렇게 따로 호출해서 사용하는지 몰랐다! 정말 IaC 라는 이름에 맞게 프로그래밍 패키지화와 비슷한 것 같이 머리아프다,,ㅎㅎ,,

모듈 사용 방식

모듈에서 사용되는 모든 리소스는 관련 프로바이더의 정의가 필요하다.

이는 자식 모듈 또는 루트 모듈에서 정의할 수 있다.

  1. 유형 1. 자식 모듈에서 프로바이더 정의
    • 프로바이더 버전과 구성에 민감하거나, 루트 모듈에서 프로바이더 정의 없이 자식 모듈이 독립적인 구조일 때 고려할 방법이다
    • 하지만 루트나 자식 모듈 간의 프로바이더 버전 충돌이 있을 수 있어 지양하는 편이라고 한다
  2. 유형 2. 루트 모듈에서 프로바이더 정의(실습)
    • 프로바이더를 모듈 내 리소스와 데이터 소스에 일괄 적용하고, 자식 모듈에 대한 반복문 사용에 자유로운 것이 장점이다

유형 2. 루트 모듈에서 프로바이더 정의 실습

  • 자식 모듈 생성 - 06-module-traning/modules/terraform-aws-ec2 하위에 main.tf variable.tf output.tf 파일을 생성한다.

      ### main.tf ###
      terraform {
        required_providers {
          aws = {
            source = "hashicorp/aws"
          }
        }
      }
        
      resource "aws_default_vpc" "default" {}
        
      data "aws_ami" "default" {
        most_recent = true
        owners      = ["amazon"]
        
        filter {
          name   = "owner-alias"
          values = ["amazon"]
        }
        
        filter {
          name   = "name"
          values = ["amzn2-ami-hvm*"]
        }
      }
        
      resource "aws_instance" "default" {
        depends_on    = [aws_default_vpc.default]
        ami           = data.aws_ami.default.id
        instance_type = var.instance_type
        
        tags = {
          Name = var.instance_name
        }
      }
    
      ### variable.tf ###
      variable "instance_type" {
        description = "vm 인스턴스 타입 정의"
        default     = "t2.micro"
      }
        
      variable "instance_name" {
        description = "vm 인스턴스 이름 정의"
        default     = "my_ec2"
      }
    
      ### output.tf ###
      output "private_ip" {
        value = aws_instance.default.private_ip
      }
    
  • 루트 모듈 생성 - 06-module-traning/multi_provider_for_module 하위에 main.tf output.tf 파일을 생성한다

      ### main.tf ###
      provider "aws" {
        region = "ap-southeast-1"  
      }
        
      provider "aws" {
        alias  = "seoul"
        region = "ap-northeast-2"  
      }
        
      module "ec2_singapore" {
        source = "../modules/terraform-aws-ec2"
      }
        
      module "ec2_seoul" {
        source = "../modules/terraform-aws-ec2"
        providers = {
          aws = aws.seoul
        }
        instance_type = "t3.small"
      }
    
      ### output.tf ###
      output "module_output_singapore" {
        value = module.ec2_singapore.private_ip
      }
        
      output "module_output_seoul" {
        value = module.ec2_seoul.private_ip
      }
    
  • 현재 모듈 구성

      /06-module-traning$ tree
      	.
      	├── modules                            # child-modules-home
      	│   └── terraform-aws-ec2
      	│       ├── main.tf
      	│       ├── output.tf
      	│       └── variable.tf
      	└── multi_provider_for_module           # root-module
      	    ├── main.tf
      	    └── output.tf
    
      $ cat .terraform/modules/modules.json | jq
      {
        "Modules": [
          {
            "Key": "",
            "Source": "",
            "Dir": "."
          },
          {
            "Key": "ec2_seoul",
            "Source": "../modules/terraform-aws-ec2",
            "Dir": "../modules/terraform-aws-ec2"
          },
          {
            "Key": "ec2_singapore",
            "Source": "../modules/terraform-aws-ec2",
            "Dir": "../modules/terraform-aws-ec2"
          }
        ]
      }
    

    지금 modules.json를 통해 모듈화된 것을 확인하였다.

  • 프로바이더 구성 테스트

      Apply complete! Resources: 4 added, 0 changed, 0 destroyed.
        
      Outputs:
        
      module_output_seoul = "172.31.21.77"
      module_output_singapore = "172.31.45.233"
    

    Untitled 2

    Untitled 3

    서울과 싱가폴 리전에 인스턴스가 하나씩 생성되었다.

      $ terraform output
      	module_output_seoul = "172.31.21.77"
      	module_output_singapore = "172.31.45.233"
      $ terraform state list
      	module.ec2_seoul.data.aws_ami.default
      	module.ec2_seoul.aws_default_vpc.default
      	module.ec2_seoul.aws_instance.default
      	module.ec2_singapore.data.aws_ami.default
      	module.ec2_singapore.aws_default_vpc.default
      	module.ec2_singapore.aws_instance.default
      $ cat terraform.tfstate | grep module
          "module_output_seoul": {
          "module_output_singapore": {
            "module": "module.ec2_seoul",
            "module": "module.ec2_seoul",
            "module": "module.ec2_seoul",
                  "module.ec2_seoul.aws_default_vpc.default",
                  "module.ec2_seoul.data.aws_ami.default"
            "module": "module.ec2_singapore",
            "module": "module.ec2_singapore",
            "module": "module.ec2_singapore",
                  "module.ec2_singapore.aws_default_vpc.default",
                  "module.ec2_singapore.data.aws_ami.default"
    

    ec2_seoul 모듈에서 구성한 리소스와 데이터 소스는 aws 프로바이더 중 alias가 seoul로 지정된 프로바이더 구성에 의해 생성된다.

    Untitled 4

  • 마지막으로 자식 모듈에서 필요로 하는 프로바이더의 버전루트 모듈의 정의와 다른 경우, 테라폼 구성에서 정의한 내용이 서로 호환되지 않아 오류가 발생할 수 있다.

Categories:

Updated:

Leave a comment