Gurumee 2021. 6. 11. 22:49
반응형

이 문서는 책 "테라폼 설치에서 운영까지"을 읽고 작성되었습니다. 최대한 요약해서 책 내용을 최소로 하는데 목표를 두고 있어서, 더 친절하고 정확한 내용을 원하신다면 책을 구매하시는 것을 추천드립니다. => 책 링크

3장을 모두 진행하고 오면 디렉토리 구조는 다음과 같다.

src
  |- ch03
        |- global
            |- s3
        |- stage
            |- data-stores
                |- mysql
            |- services
                |- webserver-cluster    

여기서 4장 코드는 stage를 복사해서 prod를 만들어서 다음과 같은 구조로 변경한다.

src
  |- ch04
        |- global
            |- s3
        |- stage
            |- data-stores
                |- mysql
            |- services
                |- webserver-cluster  
        |- prod (stage 복사)
            |- data-stores
                |- mysql
            |- services
                |- webserver-cluster  

보통 stage는 개발 환경, prod는 상용 환경을 뜻한다. 그리고 이들의 코드는 각 환경에 의존해야 하는 코드 외 거의 유사하다. 때문에 필연적으로 중복되는 코드가 발생한다. moduleTerraform에서 코드 중복을 제거하는 주요한 기능 중 하나이다. 이번 장에서는 이를 다룬다.

모듈 만드는 법

이제 data-stores/mysql 코드를 모듈로 변경해보자. 먼저 다음과 같이 디렉토리 구조로 변경한다.

src
  |- ch04
        |- global
            |- s3
        |- modules
            |- data-stores
                |- mysql
            |- services
                |- webserver-cluster  
        |- stage
            |- data-stores
                |- mysql
            |- services
                |- webserver-cluster  
        |- prod (stage 복사)
            |- data-stores
                |- mysql
            |- services
                |- webserver-cluster  

먼저 3장에서 코드는 다음과 같았다.

 

src/ch03/stage/data-stores/mysql/main.tf

terraform {
  backend "s3" {
    bucket         = "gurumee-terraform-state"
    key            = "prod/data-stores/mysql/terraform.tfstate"
    region         = "us-east-1"
    encrypt        = true
    dynamodb_table = "gurumee-terraform-lock"
  }
}

provider "aws" {
  region = "us-east-1"
}

resource "aws_db_instance" "example" {
  engine              = "mysql"
  allocated_storage   = 10
  instance_class      = "db.t2.micro"
  name                = "example_database"
  username            = "admin"
  password            = var.db_password
  skip_final_snapshot = true
}

여기서 resource 부분만 따로 복사해서 modules/data-stores/mysql/main.tf에 붙여 넣는다. 그 후 다음과 같이 수정한다.

 

src/ch04/modules/data-stores/mysql/main.tf

resource "aws_db_instance" "mysql" {
  engine              = "mysql"
  allocated_storage   = var.db_allocated_storage
  instance_class      = var.db_instance_class
  name                = var.db_name
  username            = var.db_username
  password            = var.db_password
  skip_final_snapshot = true
}

위 코드는 프로그래밍 언어로 치면 함수 본문과 같다. 그리고 같은 경로에 다음과 같이 variables.tf를 만든다.

 

src/ch04/modules/data-stores/mysql/variables.tf

variable "db_allocated_storage" {
  type        = number
  description = "The allocated storage for the database"
}

variable "db_instance_class" {
  type        = string
  description = "The instance class for the database"
}

variable "db_name" {
  type        = string
  description = "The name for the database"
}

variable "db_username" {
  type        = string
  description = "The username for the database"
}

variable "db_password" {
  type        = string
  description = "The password for the database"
}

module에서 variables.tf는 프로그래밍 언어로 치면 입력과 같다. 이제 출력을 담당하는 outputs.tf를 다음과 같이 만들어둔다.

 

src/ch04/modules/data-stores/mysql/outputs.tf

output "address" {
  value = aws_db_instance.mysql.address
}

output "port" {
  value = aws_db_instance.mysql.port
}

이렇게 하면 mysql 데이터베이스를 생성하는 module의 만들어진 것이다. 이런 방식으로 services/webserver-cluster 역시 module로 만들 수 있다. 이는 스스로 해본다. 코드는 다음을 확인하면 된다.

 

모듈 사용하는 법

이제 module을 사용해보자. prod/data-stores/mysqlmain.tf를 다음과 같이 수정한다.

 

src/ch04/prod/data-stores/mysql/main.tf

# ...

# resource 부분을 아래와 같이 변경한다.
module "mysql" {
  source = "../../../modules/data-stores/mysql"
  db_allocated_storage = 10
  db_instance_class = "db.t2.micro"
  db_name = "prod_db"
  db_username = "admin"
  db_password = var.db_password
}

위와 같이 함수의 입력을 파라미터 전달하듯이, modulevariables.tf에서 선언했던 변수들의 값을 할당해주어야 한다. 이제 outputs.tf를 다음과 같이 변경한다.

 

src/ch04/prod/data-stores/mysql/outputs.tf

output "address" {
  value = module.mysql.address
}

output "port" {
  value = module.mysql.port
}

그렇다. module.<모듈 이름>.<모듈에서 선언한 output 이름>으로 module의 출력 값을 다른 코드에서 쓸 수가 있다. 그리고 module로 코드로 변경했을 때 terraform plan이나 terraform apply 등의 명령어를 쓸 때 terraform init 명령어를 먼저 해주어야 한다.

 

module을 사용할 경우 terraform init 명령어 사용 시, .terraform/modules라는 디렉토리가 생성되는데 이 때 필요한 코드가 저장되어 있다. 따라서 module을 추가/변경이 있을 경우 반드시 terraform init 명령어가 필요하다.

github에서 모듈 가져오기

이제 github에서 소스코드를 가져와보자. 이번에는 stage 환경에서 진행한다. main.tf를 다음과 같이 변경한다.

 

src/ch04/stage/data-stores/mysql/main.tf

# ...

# resource 부분을 아래와 같이 변경한다.
module "mysql" {
  source               = "github.com/gurumee92/today-i-learned//terraform_up_and_running/src/ch04/modules/data-stores/mysql"
  db_allocated_storage = 10
  db_instance_class    = "db.t2.micro"
  db_name              = "stage_db"
  db_username          = "admin"
  db_password          = var.db_password
}

역시 outputs.tfprod 때와 같이 moduleoutput을 이용하도록 코드를 변경한다.

 

src/ch04/stage/data-stores/mysql/outputs.tf

output "address" {
  value = module.mysql.address
}

output "port" {
  value = module.mysql.port
}

이제 terraform init 후, terraform apply 하면 역시 mysql 1대를 구성할 수 있음을 확인할 수 있다. 이 때 주의할 점은 해당 레포지토리 다음 경로를 "//" 이렇게 잡아주어야 한다.

 

현재 내 레포지토리는 "github.com/gurumee92/today-i-learned"이다. 그리고 현재 작업한 코드 경로는 "terraform_up_and_running/src/ch04/modules/data-stores/mysql"에 있다.

 

따라서 "../today-i-learned//terraform_up_and_running/.." 이런식으로 경로를 잡아주어야 한다. 외부에서 코드를 가져올 때는 항상 최신인지 확인이 필요하다. 만약 최신 버전이 아니라면 다음 명령어로 업데이트가 가능한다.

$ terraform get -update

모듈 주의점

모듈을 만들 때, 다음과 같은 사항을 주의해야 한다.

  • 파일 경로
  • 인라인 블록

먼저 인라인 블록의 경우는 최대한 분리하는 것이 좋다. 3장에서 elbsecurity group의 코드는 다음과 같았다.

 

src/ch03/stage/services/webserver-cluster/main.tf

# ... 
resource "aws_security_group" "elb" {
  name = "terraform-example-elb"
  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"]
  }
}
# ...

이 때 ingress, egress 모두 다음과 같이 분리할 수 있다.

 

src/ch04/modules/services/webserver-cluster/main.tf

# ...
resource "aws_security_group" "elb" {
  name = "${var.cluster_name}-elb"
}

resource "aws_security_group_rule" "allow_http_inbound" {
  type              = "ingress"
  security_group_id = aws_security_group.elb.id
  from_port         = 80
  to_port           = 80
  protocol          = "tcp"
  cidr_blocks       = ["0.0.0.0/0"]
}

resource "aws_security_group_rule" "allow_all_outbound" {
  type              = "egress"
  security_group_id = aws_security_group.elb.id
  from_port         = 0
  to_port           = 0
  protocol          = "-1"
  cidr_blocks       = ["0.0.0.0/0"]
}
# ...

이렇게 분리하면 뭐가 좋으냐.. 환경에 따라 같은 security group일지라도 다른 inbound/outbound 설정이 가능하다. 가령 security group elb의 개발 환경에서는 port 12345가 inbound 규칙에 추가되어야 한다면 어떻게 할 것인가? 위와 같이 분리가 되었다면 그 작업은 매우 쉽다. security group elb의 id를 moduleoutput으로 지정한 뒤 다음처럼 코드를 작성하면 된다.

 

src/ch04/stage/services/webserver-cluster/main.tf


module "webserver_cluster" {
  source                 = "github.com/gurumee92/today-i-learned//terraform_up_and_running/src/ch04/modules/services/webserver-cluster"
  cluster_name           = "websever-stage"
  db_remote_state_bucket = "gurumee-terraform-state"
  db_remote_state_key    = "stage/data-stores/mysql/terraform.tfstate"
  instance_type          = "t2.micro"
  min_size               = 2
  max_size               = 2
  server_port            = 8080
}

# 이렇게 추가하면 된다.
resource "aws_security_group_rule" "allow_testing_inbound" {
  type              = "ingress"
  security_group_id = module.webserver_cluster.elb_security_group_id
  from_port         = 12345
  to_port           = 12345
  protocol          = "tcp"
  cidr_blocks       = ["0.0.0.0/0"]
}

이번엔 파일 경로이다. 3장의 경우 user-data의 값을 주기 위해서 user-data.tpl을 작성하고 이에 대한 값을 넣어주었었다.

 

src/ch03/stage/services/webserver-cluster/main.tf

# ...

resource "aws_launch_configuration" "example" {
  image_id        = "ami-0d5eff06f840b45e9"
  instance_type   = "t2.micro"
  security_groups = [aws_security_group.instance.id]

  user_data = templatefile("user-data.tpl", {
    server_port = var.server_port,
    db_address  = data.terraform_remote_state.db.outputs.address,
    db_port     = data.terraform_remote_state.db.outputs.port,
  })

  lifecycle {
    create_before_destroy = true
  }
}

# ...

하지만 module/service/webserver-cluster에도 user-data.tpl을 넣더라도 정상적으로 작동하지 않을 것이다. 왜냐하면 현재 terraform 명령어가 실행된 파일 경로에서 파일을 탐색하기 때문이다. 이를 위해서 코드를 다음과 같이 변경해야 한다.

 

src/ch04/modules/services/webserver-cluster/main.tf

# ...

resource "aws_launch_configuration" "example" {
  image_id        = "ami-0d5eff06f840b45e9"
  instance_type   = var.instance_type
  security_groups = [aws_security_group.instance.id]

  # 여기가 중요하다. ${path.module}로 값을 지정해주어야 한다.
  user_data = templatefile("${path.module}/user-data.tpl", {
    server_port = var.server_port,
    db_address  = data.terraform_remote_state.db.outputs.address,
    db_port     = data.terraform_remote_state.db.outputs.port,
  })

  lifecycle {
    create_before_destroy = true
  }
}

# ...

${path.module}terraform에서 module 경로를 지정해주어야 코드가 정상적으로 동작한다. 이는 모듈 작업할 때 실수 할 수 있으니 잘 기억해두길 바란다.

4장 전체 코드

4장 전체 코드는 다음 링크에서 확인할 수 있다.

728x90
반응형