모듈
테라폼 모듈
소개 추천 영상 : (당근페이) 박병진님 - 확장 가능한 테라폼 코드 관리를 위한 원칙
https://youtu.be/yWhwZpzJ3no?t=2504
소개
배경
: 둘 이 상의 환경에서 코드 재사용, 여러 테라폼 리소스를 하나의 논리적 그룹으로 관리하기 위해 사용
graph LR; A(((User))); F{Elastic Load Balancer}; G{Elastic Load Balancer}; A --> F; A --> G; subgraph Staging; F --> B(EC2 Instance); F --> C(EC2 Instance); F --> D(EC2 Instance); F --> E(EC2 more...); B --> L[(MySQL on RDS)]; C --> L; D --> L; E --> L; end; subgraph Production; G --> H(EC2 Instance); G --> I(EC2 Instance); G --> J(EC2 Instance); G --> K(EC2 more...); H --> M[(MySQL on RDS)]; I --> M; J --> M; K --> M; end;
Staging 와 Production 환경에서 동일한 코드를 복사하여 사용하는 대신, 두 환경에서 동일한 모듈의 코드를 재사용할 수 있음
분류 : Root 모듈(실제로 수행하게 되는 작업 디렉터리의 테라폼 코드 모음) , Child 모듈, Published 모듈 - 링크
- Child : 다른 모듈의 테라폼 코드 내에서 호출(참조)하기 위한 목적으로 작성된 테라폼 코드 모음
모듈 소스 : Local paths, Terraform Registry, Github, G3 Buckets, GCS buckets 등 - 링크
- Terraform Registry : 하시코프에서 공식적으로 운영하는 테라폼 프로바이더 및 모듈 저장소, 공개된 모듈을 쉽게 활용 가능 - 링크
- [Github] terraform-aws-modules : 하시코프 ambassador 중 한 명인 Anton Babenko 가 리드, 가장 인기 있는 AWS 테라폼 모듈을 관리하는 Github 조직 - 링크
학습 내용 : 모듈 기본 basics, 모듈 입력 inputs, 모듈과 로컬 locals, 모듈 출력 outputs, 모듈 주의 사항 gotchas, 모듈 버전 관리 versioning
- [Github] terraform-aws-modules : 하시코프 ambassador 중 한 명인 Anton Babenko 가 리드, 가장 인기 있는 AWS 테라폼 모듈을 관리하는 Github 조직 - 링크
[실습1] 로컬 Local
참고 링크
: spacelift gentledev10 tutorials
실습 따라하기
: iam user 생성
- 신규 폴더 생성 후 작업
cat <<EOT > iamuser.tf
provider "aws" {
region = "ap-northeast-2"
}
locals {
name = "mytest"
team = {
group = "dev"
}
}
resource "aws_iam_user" "myiamuser1" {
name = "\${local.name}1"
tags = local.team
}
resource "aws_iam_user" "myiamuser2" {
name = "\${local.name}2"
tags = local.team
}
EOT
확인
#
terraform init && terraform plan && terraform apply -auto-approve
# iam 사용자 리스트 확인
aws iam list-users | jq
# 삭제
terraform destroy -auto-approve
모듈 기본 Module Basics
모듈
: 폴더에 있는 모든 테라폼 구성 파일이 모듈임. 참고로 현재 작업 디렉터리의 모듈은 루트 root 모듈이라고 함
설명
: 예를 들어 ASG, ALB, 보안 그룹 등 리소스의 코드 stage/services/webserver-cluster 를 재사용 가능한 모듈로 바꾸어 보기
- stage/services/webserver-cluster 리소스를 먼저 정리
- modules 라는 최상위 폴더를 생성하고 모든 파일을 stage/services/webserver-cluster 에서 modules/services/webserver-cluster 로 이동함
- 이 과정은 스테이징 환경과 프로덕션 환경을 참조하기 위한 modules 디렉터리를 생성한 것임.
- 세 번째로 modules**/services/webserver-cluster 에서 main.tf 파일을 생성하고 **provider 정의를 제거함.
- 이제 스테이징 환경에서 이 모듈을 사용 가능. 사용 구문은 아래와 같음.
module "<NAME>" {
source = "<SOURCE>"
[CONFIG ...]
}
- NAME은 테라폼 코드 전체에서 참조하기 위해 사용하는 식별자이고, SOURCE는 modules**/services/webserver-cluster 같은 모듈 코드를 찾을 수 있는 **경로이며,
- CONFIG는 그 모듈과 관련된 특정한 하나 이상의 인수로 구성됨.
Staging 예시
예를 들어 stage/services/webserver-cluster/main.tf 에 새 파일을 만들고 다음과 같이 webserver-cluster 모듈을 사용 할 수 있다
provider "aws" {
region = "us-east-2"
}
module "webserver_cluster" {
source = "../../../modules/services/webserver-cluster"
}
Prodution 예시
그리고 아래와 같이 prod/services/webserver-cluster/main.tf 에 새 파일을 만들어 동일한 모듈을 프로덕션 환경에서 재사용할 수 있음
provider "aws" {
region = "us-east-2"
}
module "webserver_cluster" {
source = "../../../modules/services/webserver-cluster"
}
- 모듈을 적용하거나 source 파라미터를 수정하는 경우 반드시 terrraform init 실행이 필요하다
- 즉, init 명령어 하나로 손쉽게 공급자와 모듈을 다운로드하고 백엔드를 구성함. ```bash $ terraform init Initializing modules…
- webserver_cluster in ../../../modules/services/webserver-cluster
Brikman, Yevgeniy. Terraform: Up and Running (p. 214). O’Reilly Media. Kindle Edition.
### 마지막으로 실제 적용 시 실패 → **이유는 이름이 하드코딩 되어 있어서 중복 이름으로 실패** ⇒ **모듈**에도 **입력** 매개 변수를 만들자
## 모듈 입력 Module Inputs
### `입력` : 모듈에도 입력 변수 input variables 사용 가능
#### `설명` : 모듈에서 입력 변수 활용
##### 1. ***modules**/services/webserver-cluster/**variables**.tf* 에 새로운 **입력 변수 3개**를 추가
```bash
variable "cluster_name" {
description = "The name to use for all the cluster resources"
type = string
}
variable "db_remote_state_bucket" {
description = "The name of the S3 bucket for the database's remote state"
type = string
}
variable "db_remote_state_key" {
description = "The path for the database's remote state in S3"
type = string
}
2. modules**/services/webserver-cluster/**main**.tf 에 하드 코딩된 이름 대신 **var.cluster_name 을 사용 → 아래는 ALB가 사용할 보안 그룹 이름에 사용
resource "aws_security_group" "alb" {
name = "${var.cluster_name}-alb"
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
- 그외 AWS 리소스도 하드 코딩되어 중복되지 않게 변경 : ex) 인스턴스 tag 리소스 : ”${var.cluster_name}-instance”
3. terrroform_remote_state 데이터소스 업데이트 : bucket 과 key 매개 변수 수정
data "terraform_remote_state" "db" {
backend = "s3"
config = {
bucket = var.db_remote_state_bucket
key = var.db_remote_state_key
region = "us-east-2"
}
}
4. [스테이징 환경] stage/services/webserver-cluster/main.tf 에서 아래와 같이 새로운 입력 변수를 설정
module "webserver_cluster" {
source = "../../../modules/services/webserver-cluster"
cluster_name = "webservers-stage"
db_remote_state_bucket = "(YOUR_BUCKET_NAME)"
db_remote_state_key = "stage/data-stores/mysql/terraform.tfstate"
}
5. [프로덕션 환경] prod/services/webserver-cluster/main.tf 에서 아래와 같이 새로운 입력 변수를 설정
module "webserver_cluster" {
source = "../../../modules/services/webserver-cluster"
cluster_name = "webservers-prod"
db_remote_state_bucket = "(YOUR_BUCKET_NAME)"
db_remote_state_key = "prod/data-stores/mysql/terraform.tfstate"
}
- (옵션) 환경별 인스턴스 타입과 min/max 사이즈를 다르게 설정해보기. 이를 위해 modules/services/webserver-cluster/variables.tf 에 입력 변수 3개 추가.
variable "instance_type" {
description = "The type of EC2 Instances to run (e.g. t2.micro)"
type = string
}
variable "min_size" {
description = "The minimum number of EC2 Instances in the ASG"
type = number
}
variable "max_size" {
description = "The maximum number of EC2 Instances in the ASG"
type = number
}
7. 이후 modules/services/webserver-cluster/main.tf 에서 시작 구성을 업데이트
resource "aws_launch_configuration" "example" {
image_id = "ami-0fb653ca2d3203ac1"
instance_type = var.instance_type
security_groups = [aws_security_group.instance.id]
user_data = templatefile("user-data.sh", {
server_port = var.server_port
db_address = data.terraform_remote_state.db.outputs.address
db_port = data.terraform_remote_state.db.outputs.port
})
# Required when using a launch configuration with an auto scaling group.
lifecycle {
create_before_destroy = true
}
}
8. 마찬가지로 ASG 에 업데이트
resource "aws_autoscaling_group" "example" {
launch_configuration = aws_launch_configuration.example.name
vpc_zone_identifier = data.aws_subnets.default.ids
target_group_arns = [aws_lb_target_group.asg.arn]
health_check_type = "ELB"
min_size = var.min_size
max_size = var.max_size
tag {
key = "Name"
value = var.cluster_name
propagate_at_launch = true
}
}
9. [스테이징 환경] 에서 stage/services/webserver-cluster/main.tf 에 설정
module "webserver_cluster" {
source = "../../../modules/services/webserver-cluster"
cluster_name = "webservers-stage"
db_remote_state_bucket = "(YOUR_BUCKET_NAME)"
db_remote_state_key = "stage/data-stores/mysql/terraform.tfstate"
instance_type = "t2.micro"
min_size = 2
max_size = 2
}
10. [프로덕션 환경] 에서 prod/services/webserver-cluster/main.tf 에 설정
module "webserver_cluster" {
source = "../../../modules/services/webserver-cluster"
cluster_name = "webservers-prod"
db_remote_state_bucket = "(YOUR_BUCKET_NAME)"
db_remote_state_key = "prod/data-stores/mysql/terraform.tfstate"
instance_type = "m4.large"
min_size = 2
max_size = 10
}
모듈과 지역변수 Module Locals
지역변수
: 입력 변수 대신 모듈에서 변수를 정의
설명
: 로컬 Locals 는 코드를 보다 쉽게 읽기 유지 관리할 수 있게 도움을 줌
1. 현재 ALB 보안 그룹에 하드 코딩된 값
resource "aws_security_group" "alb" {
name = "${var.cluster_name}-alb"
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
2. 입력 변수 대신 locals 블록에서 로컬 값 local values 으로 정의
locals {
http_port = 80
any_port = 0
any_protocol = "-1"
tcp_protocol = "tcp"
all_ips = ["0.0.0.0/0"]
}
3. 모듈에서 로컬 값 사용 시 이름은 모듈 내에서만 표시되므로 다른 모듈에는 영향을 미치지 않으며, 모듈 외부에서 이 값을 재정의할 수 없습니다.
- 로컬 값을 읽으려면 아래 구문으로 된 로컬 참조 local reference 를 사용해야 함.
local.<NAME>
4. ALB 보안그룹을 포함해 모듈의 모든 보안 그룹에 적용
resource "aws_security_group" "alb" {
name = "${var.cluster_name}-alb"
ingress {
from_port = local.http_port
to_port = local.http_port
protocol = local.tcp_protocol
cidr_blocks = local.all_ips
}
egress {
from_port = local.any_port
to_port = local.any_port
protocol = local.any_protocol
cidr_blocks = local.all_ips
}
모듈 출력 Module Outputs
출력
: 모듈에 출력 Output 값을 활용
- 설명
: 모듈에 ASG 이름 출력 후 [프로덕션] 환경의 ASG에 스케줄 기반 증감 설정 → 5장 조건부 활용
1. (예시) [프로덕션] 환경의 ASG에 스케줄 기반 증감 정책 : 매일 오전 8시 10대 증가 → 매일 오후 9시 2대로 감소
- autoscaling_group_name 정보가 필요
resource "aws_autoscaling_schedule" "scale_out_during_business_hours" {
scheduled_action_name = "scale-out-during-business-hours"
min_size = 2
max_size = 10
desired_capacity = 10
recurrence = "0 8 * * *"
}
resource "aws_autoscaling_schedule" "scale_in_at_night" {
scheduled_action_name = "scale-in-at-night"
min_size = 2
max_size = 10
desired_capacity = 2
recurrence = "0 21 * * *"
}
2. 모듈에서 ASG 이름을 출력 변수로 설정 : /modules/services/webserver-cluster/outputs.tf
output "asg_name" {
value = aws_autoscaling_group.example.name
description = "The name of the Auto Scaling Group"
}
3. 사용 방법
# 사용 구문
module.<MODULE_NAME>.<OUTPUT_NAME>
# 다음과 같이 사용
module.frontend.asg_name
4. ASG 코드 내용에 추가 : prod/services/webserver-cluster/main.tf
resource "aws_autoscaling_schedule" "scale_out_during_business_hours" {
scheduled_action_name = "scale-out-during-business-hours"
min_size = 2
max_size = 10
desired_capacity = 10
recurrence = "0 9 * * *"
autoscaling_group_name = module.webserver_cluster.asg_name
}
resource "aws_autoscaling_schedule" "scale_in_at_night" {
scheduled_action_name = "scale-in-at-night"
min_size = 2
max_size = 10
desired_capacity = 2
recurrence = "0 17 * * *"
autoscaling_group_name = module.webserver_cluster.asg_name
}
5. (옵션) ALB DNS 노출 : /modules/services/webserver-cluster/outputs.tf
output "alb_dns_name" {
value = aws_lb.example.dns_name
description = "The domain name of the load balancer"
}
- 그런 다음 에 output 추가 : stage/services/webserver-cluster/outputs.tf , prod/services/webserver-cluster/outputs.tf
output "alb_dns_name" {
value = module.webserver_cluster.alb_dns_name
description = "The domain name of the load balancer"
}