Terraform 생존기(2) - 스타트업 테라폼 입문기

Jinwoo Baek·2021년 12월 19일
1

Terraform

목록 보기
2/2
post-thumbnail

1. Terraform 도입 계기

안녕하세요. 스타트업에서 software engineering 직책을 맡고 있는 백진우입니다.
회사에서 다양한 aws 서비스를 사용하게 되면서, 그에 따라 아래와 같은 이유로 다양한 환경을 생성하거나 관리하기 어려워지기 시작하였습니다.

  • prod, stage, develop, master 환경이 있는데, aws에서 서비스 한개를 새롭게 만들고자 하면, 똑같은 과정을 4번 반복해야 한다. ( 환경마다 하나씩)
  • s3와 lambda가 많이지면서 안 쓰는 것들이 많아 현재 어떤 것이 작동중인지 찾기 어려울 때가 있습니다.

따라서, 저는 위와 같은 문제를 해결하면서 인프라를 효율적으로 구상할 필요를 느껴 Infrastructure as Code, IaC 도구인 Terraform을 사용하기로 하였습니다.

도입하면서 배웠던 점, 시행착오들을 정리해보고자 합니다. 이 글을 잃고 다양한 의견과 조언해주시면 감사하겠습니다:)

2. Terraform 도입 전 고민 사항들

2.1 Terraform code structure

세 개의 아티클을 참고하여 마지막 large-terraform 구조를 사용하기로 결정하였습니다.

Standard Module Structure : terraform 기본 구조를 어떻게 해야하는 지 설명
Terraform code structures : terraform 전체적인 코드 구조 설명 및 예제
large-terraform : terraform best practices의 large terraform github 코드 예제

아래는 제가 만든 terraform 초기 module 구조입니다. 세부적인 코드 설명은 아래에서 진행하도록 하겠습니다!

├── prod
├── stage
├── develop
├── master
│   ├── main.tf
│   ├── outputs.tf
│   ├── terraform.tfvars
│   └── variables.tf
├── modules
│   ├── database
│   │   ├── aurora
│   │   │   ├── main.tf
│   │   │   ├── outputs.tf
│   │   └── └── variables.tf
└── └── s3 

2.2 Terraform Backend 활용

Terraform으로 인프라를 관리할 때 설정파일을 만들고 이를 terraform apply하면 terraform.tfstate 파일이 생기게 됩니다. Terraform Backend 는 terraform.tfstate을 어디에 저장을 하고, 가져올지에 대한 설정입니다. 기본적으로는 로컬에 저장이 되지만, aws s3등에 저장할 수 있게 설정할 수 있습니다. 아래는 backend 적용시 장점입니다.

  • Locking: terraform 코드를 여러명이 작성시 동시에 같은 state를 접근하는 것을 방지해줍니다.
  • Backup: 로컬에서의 유실 가능성을 방지해줍니다.
├── prod
├── stage
├── develop
├── master
│   ├── main.tf
│   ├── outputs.tf
│   ├── terraform.tfvars
│   └── variables.tf
├── modules
│   ├── database
│   │   ├── aurora-postgresql
│   │   │   ├── main.tf
│   │   │   ├── outputs.tf
│   │   └── └── variables.tf
└── ├── s3 
├── init # terraform backend 코드가 있는 곳
└── └── init.tf

Terraform backend는 한번 만 생성되면 되기 때문에 init이라는 폴더를 따로 만들어 backend를 관리해주었습니다.

  • init.tf
provider "aws" {
  region = "ap-northeast-2" # Please use the default region ID
}

# S3 bucket for backend
resource "aws_s3_bucket" "terraform_tfstate" {
  bucket = "terraform-tfstate"

  versioning {
    enabled = true # Prevent from deleting tfstate file
  }
}

# DynamoDB for terraform state lock
resource "aws_dynamodb_table" "terraform_lock" {
  name         = "silvia-terraform-lock"
  hash_key     = "LockID"
  billing_mode = "PAY_PER_REQUEST"

  attribute {
    name = "LockID"
    type = "S"
  }
}
  • S3 bucket as backend
    테라폼의 상태를 저장하기 위해 S3 버킷을 생성합니다.

  • DynamoDB Table for Lock
    동시에 같은 파일을 수정하지 못하도록 하기 위해 DynamoDB에 작업에 대한 Lock을 생성합니다.

3. Terraform 코드 설명

3.1 Terraform module

terraform의 aws module 코드는 Terraform AWS 에서 가져와 사용하였습니다. 모듈중에 aws aurora의 postgresql을 생성해보겠습니다.

main.tf

locals {
  region = "ap-northeast-2"
}

#####
# VPC and subnets
#####
data "aws_vpc" "default" {
  default = true
}

data "aws_subnet_ids" "all" {
  vpc_id = data.aws_vpc.default.id
}

module "aurora_postgresql" {
  source = "terraform-aws-modules/rds-aurora/aws"

  name = var.name
  master_username = var.master_username
  master_password = var.master_password

  vpc_id  = data.aws_vpc.default.id
  subnets = data.aws_subnet_ids.all.ids

  engine         = "aurora-postgresql"
  engine_version = "12.7"
  engine_mode    = "provisioned"
  instance_class = var.instance_class
  instances = {
    1 = {}
    2 = {}
  }
  preferred_backup_window = "07:00-09:00"

  autoscaling_min_capacity = 2
  autoscaling_max_capacity = 5
  backup_retention_period  = 7
  port                     = 5432

  publicly_accessible   = true
  apply_immediately     = true
  skip_final_snapshot   = true
  storage_encrypted     = true
  autoscaling_enabled   = true
  copy_tags_to_snapshot = true

  # Disable creation of subnet group - provide a subnet group
  create_db_subnet_group = false

  # Disable creation of security group - provide a security group
  create_security_group = false

  # Disable creation of monitoring IAM role - provide a role ARN
  create_monitoring_role = false

  # Disable creation of random password - AWS API provides the password
  create_random_password = false

  # tags = {
  #   Environment = var.env
  #   Engine      = "aurora-postgresql"
  # }
}
  • VPC와 Subnet
data "aws_vpc" "default" {
  default = true
}

data "aws_subnet_ids" "all" {
  vpc_id = data.aws_vpc.default.id
}

Terraform에서 Data Sources를 통해 Terraform 외부에서 정의되거나 다른 별도의 Terraform 구성에 의해 정의되거나 기능에 의해 수정된 정보를 사용할 수 있습니다. 위 코드는 아래와 같이 작동됩니다.

  1. aws_vpc에서 default란 이름의 vpc를 가져옵니다.
  2. 1번에서 가져온 vpc의 subnet들을 aws_subnet_ids를 통해 가져옵니다.

해당 부분은 추후에 별개의 vpc로 구성할 예정입니다.

  • aws aurora postgresql
module "aurora_postgresql" {
  source = "terraform-aws-modules/rds-aurora/aws"

  name = var.name
  master_username = var.master_username
  master_password = var.master_password

  vpc_id  = data.aws_vpc.default.id
  subnets = data.aws_subnet_ids.all.ids

우선 Terraform Registry 에서 다른 사람들이 구현해 놓은 aurora_postgresql을 찾아 source를 통해 해당 부분을 적용시켰습니다. 아키텍쳐가 쉬운 경우에는 Terraform AWS 찾아도 괜찮습니다.

The source argument in a module block tells Terraform where to find the source code for the desired child module.

  name = var.name
  master_username = var.master_username
  master_password = var.master_password

위의 부분은 variables.tf 에서 정의된 변수를 사용한 부분으로 유동적으로 바뀔 수 있는 부분입니다.
vpc_id와 subnets은 data sources에서 가져온 것을 그대로 사용하였습니다.

outputs.tf

output "cluster_id" {
  description = "The RDS Cluster Identifier"
  value       = module.aurora_postgresql.cluster_id
}

output "cluster_master_username" {
  description = "The database master username"
  value       = module.aurora_postgresql.cluster_master_username
  sensitive   = true
}

output "cluster_master_password" {
  description = "The database master password"
  value       = module.aurora_postgresql.cluster_master_password
  sensitive   = true
}
  • Root module 에서는 terraform apply 를 통해 infrastructure 가 구성된 후 정의된 outputs 들이 command line 창에 나타나게 됩니다.

  • Child module 에서는 outputs 들이 attribute 로 root module 에 전달됩니다.

  • output으로 내보낼 수 있는 것은 정해져 있습니다. ( aws aurora output )

variables.tf

variable "name" {
  description = "Name used across resources created"
  type        = string
}

variable "instance_class" {
  description = "instance class"
  type        = string
  default     = "db.r6g.large"
}

variable "master_username" {
  description = "(Required) Username for the master DB user"
  type        = string
}

variable "master_password" {
  description = "(Required) Password for the master DB user"
  type        = string
}

필요한 변수를 정의하는 곳입니다. 정의한 변수에 값을 주입하기 위해 가장 일반적인 방법은 terraform.tfvars 파일을 생성하는 것입니다.

3.2 Terraform environment

Environment Code는 실제로 생성할 환경에 대한 다양한 설정값을 명시한 코드입니다.
아래 코드는 develop 환경 기준입니다!

main.tf

terraform {
    backend "s3" {
      bucket         = "terraform-tfstate"
      key            = "terraform/develop/database/aurora_postgresql/terraform.tfstate" # develop
      region         = "ap-northeast-2"  
      encrypt        = true
      dynamodb_table = "terraform-lock"
    }
}

provider "aws" {
  region = "ap-northeast-2" # Please use the default region ID
}

module "aurora_postgresql" {
  source = "../../../../modules/database/aurora-postgresql"

  name            = var.postgresql_cluster_name[terraform.workspace]
  instance_class  = var.postgresql_instance_class[terraform.workspace]
  master_username = var.postgresql_master_username
  master_password = var.postgresql_master_password
}

module에서 작성한 aurora_postgresql을 사용하기 위해 source는 module 위치로 지정해줍니다. 또한 backend을 init.tf 에서 만든 s3로 지정해줍니다.

outputs.tf

위 module 코드와 동일합니다.

variables.tf

variable "postgresql_cluster_name" {
  description = "Name used across resources created"
  type        = string
}

variable "postgresql_master_username" {
  description = "(Required) Username for the master DB user"
  type        = string
}

variable "postgresql_master_password" {
  description = "(Required) Password for the master DB user"
  type        = string
}

variable "postgresql_instance_class" {
  description = "instance_class"
  type        = string
  default     = "db.t4g.medium"
}

필요한 변수를 정의하는 곳입니다. terraform.tfvars 에서 해당 변수에 값을 주입합니다.

terraform.tfvars

postgresql_cluster_name = "develop"
postgresql_instance_class = "db.t4g.medium"
postgresql_master_password = "admin"
postgresql_master_password = "admin"

variables.tf 에서 선언한 변수들에 값을 주입합니다.

4. Terraform Workspace

현재 테라폼 구조는 prod, stage, develop, master 환경마다 같은 코드를 반복 작성해야 합니다. 이는 서비스 추가시마다 성가신 일이기 때문에 다른 방법이 없는지 찾아보다 Terrafrom workspace 를 발견하였습니다.

4.1 Terraform workspace

Terraform workspace는 Terraform state를 담는 그릇으로 생각할 수 있습니다. workspace를 쓰게 되면 코드베이스를 하나만 관리해주면 됩니다. 명령어는 아래와 같이 사용하면 됩니다.

# 워크스페이스 생성
$ terraform workspace new prod

# 워크스페이스 선택
$ terraform workspace select prod

# Plan or Apply
$ terraform plan
$ terraform apply

4.2 Terraform code structure 변경

├── applications # 실제 terraform apply 할 폴더
│   ├── backend
│   │	├── database
│   │   │	├── aurora-postgresql
│   │   │	│   ├── main.tf
│   │   │	│   ├── outputs.tf
│   │   │	│   ├── terraform.tfvars
│   │   └──	└── └── variables.tf
│   └── frontend
├── modules
│   ├── database
│   │   ├── aurora-postgresql
│   │   │   ├── main.tf
│   │   │   ├── outputs.tf
│   │   │   ├── terraform.tfvars
│   │   └── └── variables.tf
└── ├── s3 
├── init 
└── └── init.tf

기존에 있던 prod, stage, master, develop 폴더를 없애고 application이라는 폴더로 통합관리를 하도록 하였습니다. 만약 aws aurora-postgresql를 만들고 싶으면 applications/backend/database/aurora-postgresql 폴더에 들어가서 아래의 명령어를 입력해주면 됩니다.

# develop workspace 생성
$ terraform workspace new aurora-db-develop

# develop workspace 선택
$ terraform workspace select aurora-db-develop

# Plan or Apply
$ terraform plan
$ terraform apply

해당 글을 참고하여 workspace의 이름을 결정하였습니다.

5. 참고 사이트

https://helloworld.kurly.com/blog/terraform-adventure/

https://techblog.woowahan.com/2646/

https://terraform101.inflearn.devopsart.dev/advanced/backend/

도움이 되셨다면 좋아요 부탁드립니다!

profile
Odds and Ends

0개의 댓글