티스토리 뷰

클라우드 인프라를 구축하는 방법은 0) 프로바이더가 제공하는 웹 대시보드에서 생성 1) 애드 훅 스크립트(bash, shell script) 사용 2) 구성 관리 도구(Chef, Puppet, Ansible, Saltstack) 3) 서버 템플릿 도구(Docker, Packer, Vagrant) 4) 서버 프로비전 도구(Terraform, CloudFormation, Openstack Heat) 이렇게나 다양합니다.

오늘은 테라폼을 통해 GCP 인프라를 구축하기 위해 그 뼈대가 되는 모듈 구조에 관해 이야기해봅니다. 본문에서 사용하는 코드는 GitHub으로 제공되니 전체 코드가 궁금하신 분은 링크를 참고하시면 되겠습니다. 이 글은 GCP 기준으로 작성했지만, AWS, Azure 등 어떤 provider를 사용하든지 상관없습니다. 중요한 건 아래에서 이야기하는 전체적인 모듈 구조입니다.

 

# 서비스 계정 KEY FILE 생성

우선 테라폼으로 GCP 인프라를 조작하기 위해서는 권한이 있는 키 파일이 필요합니다. GCP 콘솔에 접속해서 key file을 생성해봅시다. 아래 이미지를 따라오시면 됩니다.

console.cloud.google.com

적당한 “Service account name”을 입력하고 “CREATE AND CONTINUE” 합니다

서비스 계정 이름은 크게 중요하지 않습니다. 적당히 구분 가능한 이름을 사용해줍시다

다음으로 “Role”은 “Editor”를 주고 “DONE”으로 끝냅니다. 지금 생성하는 서비스 계정을 이용해서 테라폼이 GCP 리소스를 생성/수정/삭제하게 될 겁니다. 그렇기 때문에 Editor 권한이 필요합니다.

Role은 Editor로 설정

계정이 아래처럼 생성된 게 확인될 겁니다. 

이제 방금 생성한 서비스 계정의 키 파일을 뽑아낼 차례입니다. 계정을 선택하면 아래와 같이 수정 가능한 화면이 나오는데요. 이때 상단 내비게이션에서 “KEYS”를 선택하고 중간쯤 있는 “ADD KEY”를 클릭합니다.

그럼 아래와 같은 선택 박스가 나오는데 “Create new key”를 선택해줍니다.

이제 아래와 같은 팝업이 나옵니다. 여기서 JSON 파일로 생성하도록 합시다. 

이렇게 생성된 키 파일은 앞서 인프라 전체에 대해 Editor 권한을 줬기 때문에 굉장히 막강합니다. 외부에 절대 노출되지 않도록 주의하도록 하세요. 특히 GitHub에 해당 파일이 올라가는 일이 없도록 주의가 필요합니다. 애초에 Json 파일은 GitHub에 올리지 않도록 gitignore 파일에 설정하는 것도 좋은 방법입니다. 여기까지 잘 따라오셨으면 이제 터미널에서 테라폼 관련 환경변수를 등록해 봅시다.

 

# 터미널 환경 변수 세팅

테라폼에서 사용하게 될 터미널 환경 변수를 세팅해줍니다. 우선 위에서 발급받은 키 파일의 경로를 GOOGLE_CLOUD_KEYFILE_JSON로 지정해줍니다.

$ export GOOGLE_CLOUD_KEYFILE_JSON=/path/to/file

다음으로 TF_VAR_project_id에 GCP 콘솔에서 프로젝트 ID를 확인하고 입력해줍니다. ( * Project name이 아니라 Project ID입니다 )

$ export TF_VAR_project_id="foo-1234"

GOOGLE_CLOUD_KEYFILE_JSON는 테라폼에서 약속된 이름이고 TF_VAR_project_id 임의로 만들어낸 변수입니다. 즉, TF_VAR 다음에 붙어있는 이름은 변경이 가능합니다. 자, 이제 변수 세팅까지 끝났으면 HashiCorp Configuration Language (HCL)로 신나게 코드를 작성해봅시다. intellij 제품을 사용하는 경우 HCL plugin을 지원하기 때문에 한결 수월하게 인프라를 개발할 수 있습니다. 

 

# 테라폼 기본 골격 생성

이번 글에서는 테라폼 콘셉트나 코드에 대한 설명은 다루지 않습니다. 관련된 글은 아래 링크를 참고해주세요. 

우리가 코드를 개발할 때 여러 가지 방법론이 있듯이 테라폼의 기본 골격에도 여러 종류가 있습니다. 어디까지나 개인 취향이 반영되어 있으므로 참고해서 봐주시면 됩니다. 우선 심플하게 접근해보면 한 개의 테라폼 코드에 모든 인프라 리소스가 들어있는 구조를 떠올려볼 수 있습니다. 아래와 같은 형상이 될 겁니다. 

# provider 지정
provider "google" {}

# 변수 지정. TF_VAR_project_id가 세팅되어 있지 않다면 terraform 명령어를 사용할 때 입력해야 함
variable "project_id" {
  type  = string
  description = "Enter a unique project-id"
}

# 로컬 변수 선언
locals {
  service_name  = "terraform"
}

# 리소스 생성
resource "google_compute_network" "vpc_network" {
  project = var.project_id
  name    = "${var.service_name}-network"
}

위에 코드는 VPC를 생성하는 테라폼 코드입니다. 몇 줄 되지 않기 때문에 간단합니다. terraform apply의 결과물은 아래와 같을겁니다.

 

조금 더 많은 리소스를 생성해야 하는 복잡한 아키텍처를 구현하게 되면 한 개 파일로는 유지보수에 어려움이 생깁니다. 이런 경우 코드 재사용도 어렵게 됩니다. 오랜만에 코드를 열어보면 살펴야 할 것도 많고 어디쯤에 뭘 끼워 넣어야 할지도 감이 오지 않을 겁니다. 다행히도 테라폼에서 사용하는 HCL도 다른 프로그래밍 언어처럼 파일을 분리하고 import, include 같은 개념을 사용할 수 있습니다. 여기서는 “module”이라는 이름으로 사용이 되는데요. 아래를 처럼 구조를 잡을 수 있습니다.

├── main.tf
└── modules
    └── vpc_network
        ├── main.tf
        └── variables.tf

위에 디렉터리 구조에서 root에 있는 main.tf의 코드는 아래와 같습니다.

# provider 지정
provider "google" {}

# 변수 지정. TF_VAR_project_id가 세팅되어 있지 않다면 terraform 명령어를 사용할 때 입력해야 함
variable "project_id" {
  type  = string
  description = "Enter a unique project-id"
}

# 로컬 변수 선언
locals {
  service_name  = "terraform"
}

# 모듈을 호출
module "vpc_network" {
  source       = "./modules/vpc_network"
  project_id   = var.project_id
  service_name = local.service_name
}

다음으로 modules/vpc_network/main.tf 는 아래와 같습니다

resource "google_compute_network" "vpc_network" {
  project = var.project_id
  name    = "${var.service_name}-network"
}

modules/vpc_network/variables.tf에는 아래와 같은 내용이 들어있습니다.

variable "project_id" {
  description = "project_id"
  type        = string
}

variable "service_name" {
  description = "service_name"
  type        = string
}

개발에 익숙하신 분은 구조만 봐도 어떤 느낌인지 감이 오실텐데요. main.tf에서 선언한 변수를 modules/vpc_network/main.tf로 전달하기 위해 variables.tf를 만든 겁니다. 

자, 이번에는 조금 더 복잡한 리소스를 살펴보겠습니다. 아래는 EKS를 생성하는 예제의 디렉터리 구조입니다.

├── main.tf
└── modules
    ├── google_container_cluster
    │   ├── main.tf
    │   ├── output.tf
    │   └── variables.tf
    ├── google_container_node_pool
    │   ├── main.tf
    │   └── variables.tf
    ├── google_service_account
    │   ├── main.tf
    │   ├── output.tf
    │   └── variables.tf
    └── vpc_network
        ├── main.tf
        ├── output.tf
        └── variables.tf

여기서 main.tf는 아래와 같이 작성되어 있습니다. 모듈 이름만으로 어떤 리소스를 생성하는 것인지 가늠할 수 있습니다.

provider "google" {}

variable "project_id" {
  type  = string
  description = "Enter a unique project-id"
}

locals {
  service_name  = "terraform"
  region        = "us-central1"
}


module "vpc_network" {
  source       = "./modules/vpc_network"
  project_id   = var.project_id
  service_name = local.service_name
}

module "google_service_account" {
  source       = "./modules/google_service_account"
  project_id   = var.project_id
  service_name = local.service_name
}

module "google_container_cluster" {
  source       = "./modules/google_container_cluster"
  project_id   = var.project_id
  service_name = local.service_name
  region       = local.region

  google_compute_network_id = module.vpc_network.google_compute_network_id
}

module "google_container_node_pool" {
  source       = "./modules/google_container_node_pool"
  project_id   = var.project_id
  service_name = local.service_name

  machine_type = "e2-medium" # 2 vCPU, 4 GB memory

  google_service_account_email = module.google_service_account.google_service_account_email
  google_container_cluster_id  = module.google_container_cluster.google_container_cluster_id
}

terraform apply의 결과물은 다음과 같습니다! ( 실제 운영환경에서 사용하려면 artifact registry생성까지 해주면 더 좋습니다. )

 

(*중요) 이런 구조를 기반으로 작성하게 되면 실제 복수 환경에서 사용하는 것도 아주 간단해집니다. 아래처럼 dev, stg, 그리고 prd 디렉터리를 생성하고 대부분 동일한 내용의 main.tf를 작성해주면 되니까요. 그리고 modules 하위에 있는 리소스는 공통으로 사용하는 거죠. dev, stg, prd 밑에 있는 main.tf에는 각 환경에 맞는 컴퓨팅만 가져다 쓰도록 변경해주면 됩니다. 예를 들면 각 환경별로 machine_type이 다를 수 있겠네요.

├── dev
│   └── main.tf
├── stg
│   └── main.tf
├── prd
│   └── main.tf
└── modules
    ├── google_container_cluster
    │   ├── main.tf
    │   ├── output.tf
    │   └── variables.tf
    ├── google_container_node_pool
    │   ├── main.tf
    │   └── variables.tf
    ├── google_service_account
    │   ├── main.tf
    │   ├── output.tf
    │   └── variables.tf
    └── vpc_network
        ├── main.tf
        ├── output.tf
        └── variables.tf

 

# 마무리

코드 구조에 정답이 없듯이 테라폼도 마찬가지입니다. 표준은 없지만 인프라를 관리하는 팀에서 1) 유지보수가 용이하고 2) 유사한 인프라를 만들 때 코드를 재사용(예를 들어 데이터베이스는 여러 프로덕트에서 사용될 테니 테라폼 모듈의 재사용성이 높겠죠)할 수 있으며 3) 읽기 쉬운 코드라면 괜찮은 구조라고 생각해도 될 겁니다. 비단 테라폼에만 해당하는 내용은 아니죠. 한편 “terraform workspace”를 통해 개발/스테이징/운영 등 환경을 구분하는 것도 좋은 선택지가 됩니다. 관련해서는 다른 글에서 다루도록 하겠습니다.

여기서 다룬 모든 코드는 GitHub에 있습니다. 앞으로 여러 가지 예제가 추가될 예정이며 디렉터리 구조에 대한 어떤 논의도 환영합니다! 테라폼 사용, 혹은 리소스 생성에 어려움이 있다면 알려주세요. 같이 고민해보도록 하시죠. 그럼 테라폼과 함께 즐거운 GCP 여행이 되시길 바라겠습니다!

# ref.

댓글
댓글쓰기 폼