테라폼 자체 제작 모듈간 연동 구성 삽질기
본문요약
- 퍼블릭 서브넷: ALB / 프라이빗 서브넷: EC2 가 위치한 기본 구성
- 간단해 보여도, IaC tool(테라폼)으로 구성하려면 여러 리소스에 대한 설정이 필요
- VPC network
- VPC & Subnet CIDRs, igw & NAT, Routing tables, etc…
- ALB
- Subnet mapping, Listener rule & Target group, etc…
- EC2
- AMI, Instance type, Security groups, etc…
- VPC network
- 해당 구성을 자주 사용한다면, 어떻게 재사용성을 높일 수 있을까?
- 필요 변경값만을 정의하고 변수화하여 해당 값 변경으로 새로운 세트 생성
- 동일한 리소스 파일(=모듈)에 변수값만 다르게하여 빠른 복제 가능
- 단일 모듈 대신 다수의 작은 모듈간 연동으로 구성하고자 하였음
- 일부 모듈만 사용하여 구성할 수 있도록
- 모듈간 연동문제 발생 > depend_on으로 해결
- 모듈간 정보를 data source로 연동 시도
- 모듈 호출시에는 필요한 data가 생성되지않아 에러 발생
- depend_on으로 모듈간 의존성을 부여하여 해결 > 좋은 방법은 아님
!모듈 왜써요? / !모듈 왜 만들어써요?
공부하는데 이유가 어딨어? 그냥 하는거지
위의 본문요약에도 간단히 적어둔것처럼, 테라폼을 통해 생성 및 관리되는 리소스 중 반복되는 리소스가 많다면 모듈을 사용해 재사용성을 높일 수 있다.
일반적인 모듈의 장점은 Docs나 다른 블로그들에 잘 나와있고
작성해보면서 느낀 모듈을 써야하는 / 만들어 써야하는 이유는 아래와 같다.
- 변경할 부분만 바로 볼 수 있음
- 몇십~몇백줄의 resource block을 매번 보는것은 부담스러움
- module block으로 간단하게 관리가능
- 공개된 모듈을 사용자 환경에 맞게 개선
- aws module과 같이 provider가 공식적으로 제공하는 모듈도 존재
- 모듈로 제공되지 않는 리소스도 일부 존재 -> 모듈로 쓰려면 만들어서 써야함
- 범용성을 위해 입력값으로 설정된 부분들(vpc/subnet/sg등)을 모듈안에 입력해둘 수 있음
중점적으로 고민했던 부분들
- 어디까지 모듈로 만들고 각 모듈마다 어떤 리소스를 배정할것인가?
- 한두번만 쓰고 말거면 모듈로 구성할 필요가 있는가?
- Backend(S3 bucket, DynamoDB key)등은 별도 생성해서 복붙사용
- 몇개의 모듈을 만들어서 사용할 것인가?
- VPC, ALB, EC2 3개의 모듈 생성 및 연동
- VPC-ALB-EC2 모두 묶은 1개의 모듈로도 구성 가능. But,
- 기 VPC에 ALB-EC2 세트만 추가생성 할수도 있고
- ALB 없이 퍼블릭에 EC2만 생성하고 싶을 수도 있음
- VPC-ALB-EC2 모두 묶은 1개의 모듈로도 구성 가능. But,
- VPC, ALB, EC2 3개의 모듈 생성 및 연동
- 각 모듈마다 어떤 리소스가 배정되야하고 어느부분이 자주 변경되는가?
- All: project_name, env_name, etc…
- VPC: CIDRs(VPC,Subnet), etc…
- ALB: sec_grps(ingress),target_grps, etc…
- EC2: instance_type, amount, etc…
- 한두번만 쓰고 말거면 모듈로 구성할 필요가 있는가?
- 해당 고민을 하고난 뒤 기존의 짜여진 모듈들을 보고 컨닝함.
- 모듈간의 산출믈을 서로 어떻게 넘겨줄것인가
- Resource.local_file 사용
- local_file 리소스를 사용해 별도 산출물 파일을 생성해 다른 모듈의 경로에 던져주기
-
ex)
/modules/00network/nw-output.tf resource "local_file" "example { content = <<EOF variable "vpc_id" = "${aws_vpc.vpc.id}" 이하 생략 EOF filename = "../02ec2/nw_output.tf" }
- Module Output 사용
- Accessing Module Output Values 모듈간 참조할 값을 output으로 뺴놓고, output을 타 모듈의 input으로 넣어주기
-
ex)
/modules/00network/nw-output.tf output "vpc_id" {value = aws_vpc.vpc.id} 이하 생략 /dev/aaa/aaa-main.tf module "aaa-ec2" { vpc_id = module.aaa-network.vpc_id 이하 생략 }
- Tag 기반 Data source 사용
- Dependency Inversion 모듈간 참조할 값을 개별 module마다 data source로 받아와서 사용
-
ex)
/modules/02ec2/ec2-variable.tf data "aws_vpc" "this" {tags={Name="dev"} /modules/02ec2/ec2-template.tf resource "aws_instance" "this" { vpc_id = data.aws_vpc.this.id }
- Resource.local_file 사용
- So?
- 3번 채택
- 개별 모듈안에는 template & variable만 남겨두고 싶음
- 최상위 input에는 수동입력이 필요한것만 남기고 나머지는 다 눈에 안보이게 치워두고 싶음
- 추가로 연동할 부분이 생긴다면, output 기반은 해당 리소스id/arn을 추가로 설정해 줘야하지만 data 기반으로하면 별도로 뺴낼필요 없이 기존의 data를 활용해서 사용할 수 있을것이라고 생각함.
- 3번 채택
- 최선입니까? 확실해요?
- 의존성 문제 발생 > depends_on으로 의존성 부여하여 해결
- 모듈이 시작되는 시점에서 동시에 data를 긁어오다보니 모듈간 결과를 못받아오는 경우 발생
- apply를 여러번 적용
- 주석 처리해놓고, 하나씩 주석해제해가면서 terraform apply -auto-approve && terraform apply -auto-approve && terraform apply -auto-approve …
- 근본적인 해결책은 아닌듯함..
- 모듈 구조를 잘못 짠것에 대해서 반성하기
- 어쩐지 예제들에서 모듈간 연계하기보다는 한모듈안에 때려넣더라
- vpc-alb-ec2 순으로 생성되도록 순서부여
- 한 모듈 생성을 마치고 다음 모듈이 생성될 수 있도록
- 전체적인 실행시간은 느려지겠지만 apply 한번에 생성되는 목적은 달성
구축 Architecture
ALB 1개, EC2 2개로 구성된 간단한 구조
Project Structure
.
├── README.md
├── dev
│ ├── aaa
│ │ ├── aaa-main.tf
│ │ ├── aaa-variable.tf
│ │ └── aaa.auto.tfvars
│ └── bbb
│ └── WIP
├── prd
│ └── WIP
└── _modules
├── 00network
│ ├── nw-variable.tf
│ └── nw-template.tf
├── 01alb
│ ├── alb-template.tf
│ └── alb-variable.tf
└── 02ec2
├── ec2-template.tf
└── ec2-variable.tf
디렉토리별 상세 설명
/
최상위 디렉토리
각 환경들이 상호 영향을 주지않기 위해 디렉토리 단위로 구분되어 있음
/dev/aaa
aaa.auto.tfvars / aaa-variable.tf / aaa-main.tf 3개의 파일이 존재,
순서대로 리소스 생성에 필요한 값들을 전달하고/전달받으며 전체 모듈을 실행함
- aaa.auto.tfvars
- 이후 파일들이 참조할 수 있도록 사용자 입력이 필요한 값들을 입력
- 특정 파일명을 설정하면 Terraform은 자동으로 해당 파일 내부의 값을 변수로 입력
- ex)
# ------ 프로젝트 공통 설정 ------ name_prefix = "aaa" // 프로젝트 명 입력 env = "dev" // 생성환경 입력 # ------ Network 설정 ------ vpc_cidr = "10.0.0.0/16" // VPC CIDR subnet_cidrs = { // Subnets CIDR pub1 = "10.0.10.0/24" pub2 = "10.0.20.0/24" pub3 = "10.0.30.0/24" pri1 = "10.0.40.0/24" pri2 = "10.0.50.0/24" } # ------ ALB 설정 ------ # ALB의 Security group 설정 alb_ports = [80, 443] // ALB ingress에 허용될 ports 입력 alb_cidr_blocks = ["0.0.0.0/0"] // ALB ingress에 허용될 cidr_block 입력 alb_security_groups = [] // ALB ingress에 허용될 security_groups 입력 # ------ EC2 설정 ------ # EC2 관련 설정 ec2_amount = ["1", "2"] // 생성될 EC2 수량 instance_type = "t2.micro" // 필요한 인스턴스 유형 입력 volume_size = "8" // 필요한 루트 볼륨 사이즈 입력 (최소8)
- aaa-variable.tf
- aaa.auto.tfvars의 변수 정의 값들을 받아 aaa-main.tf로 전달하는 중간 파일
- default 등을 설정하여 기본값을 설정할 수 있음
- ex)
# ------ 프로젝트 공통 설정 ------ variable "name_prefix" {} variable "env" {} # ------ Network 설정 ------ variable "vpc_cidr" {} variable "subnet_cidrs" {} # ------ ALB 설정 ------ variable "alb_ports" {} variable "alb_cidr_blocks" {} variable "alb_security_groups" {} # ------ EC2 설정 ------ variable "ec2_amount" {} variable "instance_type" {} variable "volume_size" {}
- aaa-main.tf
- Child module을 가동하는 Root module
- provider, backend 및 output 설정을 해두었음
- ex)
# aaa의 네트워크 설정 module "aaa-network" { source = "../../_modules/00network" name_prefix = var.name_prefix env = var.env vpc_cidr = var.vpc_cidr subnet_cidrs = var.subnet_cidrs } # aaa의 ALB 설정 module "aaa-alb" { source = "../../_modules/01alb" name_prefix = var.name_prefix env = var.env alb_ports = var.alb_ports alb_cidr_blocks = var.alb_cidr_blocks alb_security_groups = var.alb_security_groups depends_on = [module.aaa-network] } # aaa의 EC2 설정 module "aaa-ec2" { source = "../../_modules/02ec2" name_prefix = var.name_prefix env = var.env ec2_amount = var.ec2_amount instance_type = var.instance_type volume_size = var.volume_size depends_on = [module.aaa-alb] } # outputs output "alb_dns_name" { value = module.aaa-alb.alb_dns_name } output "ec2_private_ips" { value = module.aaa-ec2.ec2_private_ips }
/modules
modules 디렉토리
Root module의 설정에 따라 필요한 모듈들이 실행됨
현재 network, alb, ec2가 있고, 각 모듈별 템플릿과 Root에서 넘어온 변수값을 받는 변수 항목들이 설정됨
또한 아래의 예시처럼 앞선 모듈의 생성결과를 받아오기 위해서,
Name 태그 기반으로 필터링하여 필요한 데이터를 수신함
/modules/ec2/ec2-variable.tf
# 생성된 EC2가 지정될 ALB 대상그룹 조회
data "aws_lb_target_group" "alb" {
name = "${var.env}-${var.name_prefix}-target-grp"
}
/modules/ec2/ec2-template.tf
resource "aws_lb_target_group_attachment" "ec2" {
for_each = toset(var.ec2_amount)
target_group_arn = data.aws_lb_target_group.alb.arn
target_id = aws_instance.ec2[each.key].id
port = 80
}
생성 결과
전체생성까지 약 8분이 소요
output으로 출력된 ALB로 CURL하면 EC2들이 응답하는것을 볼 수 있음
개선해야할 부분
- 디렉토리 구조 개선
- 기본적으로 반복되는 부분을 모듈화 해야함
- VPC/서브넷의 경우 S3버킷처럼 자주 생성되지 않기 때문에 별도로 분리 예정
- Data source 걷어내고 output 사용
- ~.auto.tfvars로 필요값만 수정할 것이기에 ~-main.tf 안에 다른 모듈 output을 넣어도 눈에 안띔
- 모듈들이 동시에 작동하면서 생성시간도 빨라질 것이라 기대
- 반복 생성되는 부분의 코드 단축
- 스터디 초기에 짜놨던 서브넷의 경우 필요한 만큼 반복하면서 생성
- 추후 count, for_each, 등 반복문 숙달되고 정리
- 모듈 내 더 많은 리소스 포함
- 단순히 ALB, EC2 생성 및 연동만 구축하였음
- Route53 Record 등록, 80>443 HTTPS 적용, EC2 추가볼륨 생성 및 마운트 등 모듈 내 더 많은 리소스 자동화 가능
Leave a comment