ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 04. 테라폼 모듈
    레거시/Terraform Up and Running 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장 전체 코드는 다음 링크에서 확인할 수 있다.

Designed by Tistory.