[Terraform@CloudNet] Terraform Module

2024. 7. 13. 19:59EKS@Terraform

- CloudNet에서 주관하는 Terraform 스터디 내용입니다

- 내용은 위 책 테라폼으로 시작하는 IaC 를 기준으로 정리하였습니다.

- 실습은 M1 macbook air 에서 진행했습니다.

- 매번 좋은 스터디를 진행해 주시는 CloudNet 팀 감사드립니다

- 잘못된 점, 업데이트된 지식, 다른 코멘트 언제나 환영입니다!

 

 

 

모듈

모듈은 테라폼 구성의 집합으로, 관리대상이 거대해짐에 따라 관리를 수월하게 하기 위해 생겨났다.

관리성/캡슐화/재사용성/일관성/표준화 를 지향한다. 다른 언어에서 라이브러리/패키지와 유사하다.

 

모듈은 루트/차일드 모듈로 나뉜다.

- 루트 모듈: 테라폼 실행 및 프로비저닝을 수행하는 최상위 모듈

- 차일드 모듈: 루트모듈이 호출하는 외부 집합

 

모듈 작성시, 디렉토리는 terraform-{프로바이더명}-{모듈명} 으로 구성할 것이 추천된다.

1. terraform: 이 디렉토리가 테라폼을 위한 것임

2. {프로바이더명}: 리소스를 포함한 프로바이더의 종류

3. {모듈명}: 에 부여된 이름

 

 

 

모듈화 실습

하나의 프로비저닝에서 사용자와 패스워드를 여러 번 구성해야 하는 경우를 모듈로 구성해 보자

 

우선 차일드 모듈 작성을 위해 폴더 구조를 아래와 같이 구성한다

 

 

 

main.tf 에는 랜덤으로 패스워드를 생헝하는데, variable의 isDB가 true/false 인지에 따라서 길이와 특수문자 여부가 결정된다. 만들어진 패스워드에는 timestamp를 찍어서 id로 사용한다 

resource "random_pet" "name" {
  keepers = {
    ami_id = timestamp()
  }
}

resource "random_password" "password" {
  length           = var.isDB ? 16 : 10
  special          = var.isDB ? true : false
  override_special = "!#$%*?"
}

 

 

variable.tf 에서는 위에서 쓰인 isDB를 받는다

variable "isDB" {
  type        = bool
  default     = false
  description = "패스워드 대상의 DB 여부"
}

 

 

output.tf 에는 만든 pw 를 출력한다

output "id" {
  value = random_pet.name.id
}

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

 

 

동작을 테스트하기 위해 apply 할 때, variables.tf 에서 만든 isDB 변수를 사용한다

terraform init && terraform plan
terraform apply -auto-approve -var=isDB=true

terraform state show random_pet.name
terraform state show random_password.password

 

isDB=true 였기 때문에 16글자에 특수문자가 혼용된 pw가 mature-kitten 과 같이 저장되었다 

 

 

 

이번에는 만들어진 자식모듈을 반복해서 호출해보자

모듈 디렉토리 옆에 연습 디렉토리를 하나 만들고 거기서 main.tf 를 선언한다

 

mypw1, mypw2 에서 반복적으로 id-pw 세트가 만들어지는 코드이다

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
}

 

 

해당 프로젝트를 init, plan, apply 한다

terraform init && terraform plan && terraform apply

 

 

실행결과가 output 으로 출력되는데, 이번에는 apply시 isDB 옵션을 쓰지 않았기 때문에 10자 길이로 특수문자 없이 생성되었다

 

graph 를 찍어보면 아래와 같이 잘 생성된 것을 볼 수 있다

 

 

 

모듈 사용

- 모듈로 만들어지는 리소스는 프로바이더 정의가 별도로 필요하다. 이때 프로바이더 정의를 자식/루트 모듈에서 에서 할 수 있다

자식 모듈 정의 루트 모듈 정의
프로바이더 버전에 민감할 경우 추천
루트 모듈에 프로바이더가 없을 경우

그러나 자식-루트 간 프로바이더 버전 충돌시 사용불가
자식 모듈이 루트 모듈의 프로바이더에 종속됨

주로 사용되는 방식

 

 

아까 비밀번호 발생기와 같은 계층에 terraform-aws-ec2 폴더를 만들어 ec2를 생성하는 차일드 모듈을 만든다.

 

이 모듈도 main.tf, variable.tf, output.tf 로 구성된다.

 

variable.tf 는 instance_type 과 instance_name 을 따로 빼서 변수로 받게 한다

variable "instance_type" {
  description = "vm 인스턴스 타입 정의"
  default     = "t2.micro"
}

variable "instance_name" {
  description = "vm 인스턴스 이름 정의"
  default     = "my_ec2"
}

 

 

main.tf 는 variable.tf 를 참고해서 사용할 인스턴스의 ami image 를 받아서 실제 인스턴스를 생성한다

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
  }
}

 

 

output.tf 는 생성된 ec2의 private_ip 를 출력해준다

output "private_ip" {
  value = aws_instance.default.private_ip
}

 

 

 

 

이번에는 작성된 차일드 모듈을 사용하는 루트모듈을 만든다. 폴더구조는 아래와 같이 차일드모듈보다 한 뎁스 위에 있다.

 

루트모듈의 main.tf 는 차일드모듈을 가져다가 사용하는데, 2개의 프로바이더를 사용했다(같은 ec2 다른 리전). 이때 사용하는 소스는 동일하게 차일드 모듈의 테라폼 파일을 가져다가 서울/싱가폴 리전에 각각 ec2와 vpc 를 생성한다. 

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 는 각 리전의 private ip 를 출력한다

output "module_output_singapore" {
  value = module.ec2_singapore.private_ip
}

output "module_output_seoul" {
  value = module.ec2_seoul.private_ip
}

 

 

 

그러면 이번에는 구성한 루트/차일드 모듈을 사용해서 리소스를 생성해보자. 루트모듈 폴더에서 테라폼을 배포한다

cd 06-module-traning/multi_provider_for_module/
terraform init
cat .terraform/modules/modules.json | jq #모듈 확인 경로
terraform apply -auto-approve

 

 

아래와 같이 사용된 모듈을 볼 수 있다

apply 후 ec2 & vpc 가 잘 생성되었다!

graph 명령어로 시각화하면 의존성을 보다 명확하게 파악할 수 있다

 

 

루트모듈의 terraform.tfatate 파일에서 사용한 모듈들을 파악할 수 있다

aws cli 를 이용해서도 배포된 리소스를 확인할 수 있다.

실습에 사용한 리소스를 삭제하는 것도 잊지 말자.

## aws cli로 확인
# 서울 리전
aws ec2 describe-instances \ 
--region ap-northeast-2 \ 
--query "Reservations[*].Instances[*].{PublicIPAdd:PublicIpAddress,InstanceName:Tags[?Key=='Name']|[0].Value,Status:State.Name}" \
--filters Name=instance-state-name,Values=running \
--output text

# 싱가폴 리전
aws ec2 describe-instances \
--region ap-southeast-1 \
--query "Reservations[*].Instances[*].{PublicIPAdd:PublicIpAddress,InstanceName:Tags[?Key=='Name']|[0].Value,Status:State.Name}" \
--filters Name=instance-state-name,Values=running \
--output text

## 리소스 삭제
terraform destroy -auto-approve

 

 

 

반복문으로 모듈 사용

모듈은 리소스 묶음을 원하는 수량만큼 프로비저닝할 수 있으므로 리소스 종속성 관리/유지보수가 용이하다. 테라폼 문법에서는 반복문으로 count 와 for_each 릴 지원하는데, 모듈 묶음에 일관된 구성과 구조가 사용된다면 count 사용이 간편하고 인수 등을 다양하게 사용한다면 for_each 가 용이하다.

 

 

 

count 사용부터 먼저 실습해 보자

 

우선 modules 와 같은 뎁스에 module_loop_count 라는 디렉토리를 만들어서 루트모듈로 사용한다.

module_loop_count 의 main.tf 는 count 를 사용해서 총 2개의 ec2 인스턴스를 배포한다. 이때 상대경로를 이용하여 위 실습에서 만들어놓은 modules 의 terraform-aws-ec2 모듈을 사용하여 vpc를 같이 구성해서 배포한다

# main.tf
provider "aws" {
  region = "ap-northeast-2"  
}

module "ec2_seoul" {
  count  = 2
  source = "../modules/terraform-aws-ec2"
  instance_type = "t3.small"
}

output "module_output" {
  value  = module.ec2_seoul[*].private_ip   
}

 

init & apply 로 각 2개의 ec2와 vpc가 성공적으로 배포된 모습을 볼 수 있다.

 

count를 사용해서 배포한 리소스를 for_each를 사용해서 배포해보자.

main.tf를 아래와 같이 수정한다. 마찬가지로 2개의 리소스를 배포하지만 dev/production 상황에 맞게 각각 리소스명을 다르게 주고, 사용 인스턴스 스펙도 다르다.

locals {
  env = {
    dev = {
      type = "t3.micro"
      name = "dev_ec2"
    }
    prod = {
      type = "t3.medium"
      name = "prod_ec2"
    }
  }
}

module "ec2_seoul" {
  for_each = local.env
  source = "../modules/terraform-aws-ec2"
  instance_type = each.value.type
  instance_name = each.value.name
}

output "module_output" {
  value  = [
    for k in module.ec2_seoul: k.private_ip
  ]
}

 

 

apply 하면 기존에 count 로 만들어졌던 모듈 인스턴스는 삭제되고 dev/prod 라벨을 단 인스턴스가 재배포되는 것을 확인할 수 있다

vpc는 기능상으로는 없어지지 않아도 되지만 모듈로 묶여있기 때문에 삭제 후 재생성되었다. count 가 사용하기에는 편리하지만 유지보수 측면에서는 for_each 를 최대한 사용해야겠다고 생각하게 되었다.

 

 

모듈과 반복문이 결합하니 더욱 쉽고 빠르게 리소스를 배포할 수 있다는 점이 실무에서 긍정적으로 작용할 것 같다