DBAtlas · 데이터베이스 운영 아틀라스

Terraform을 이용한 데이터베이스 프로비저닝(IaC)

증상

목표: Terraform으로 데이터베이스 인프라(인스턴스, parameter group, 네트워크)를 코드로 정의하고(Infrastructure as Code), plan/apply 흐름, remote state, drift 관리를 구성한다.

1. provider를 선택하고 구성한다(aws / google / azurerm)

사용하는 클라우드의 provider를 정의하고 버전을 고정한다. required_providers 블록에서 AWS는 hashicorp/aws, GCP는 hashicorp/google, Azure는 hashicorp/azurerm provider를 고정한다. 버전 고정은 팀 전체에서 일관된 동작을 보장한다.

PostgreSQL

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = "eu-central-1"
}

2. remote state와 locking을 구성한다

state 파일을 로컬에 두지 말고 팀 작업과 보안을 위해 remote backend를 사용한다. 일반적인 선택은 AWS의 S3 backend + DynamoDB lock 테이블, GCP의 gcs backend, Azure의 azurerm backend(Storage Account)다. remote state는 locking으로 동시 apply를 막고 state를 단일 진실 공급원으로 만든다. state에 비밀번호가 평문으로 남을 수 있으므로 backend는 암호화하고 접근을 제한한다.

PostgreSQL

terraform {
  backend "s3" {
    bucket         = "company-tfstate"
    key            = "db/terraform.tfstate"
    region         = "eu-central-1"
    dynamodb_table = "tf-lock"
    encrypt        = true
  }
}

3. 데이터베이스 리소스를 정의한다

엔진과 클라우드에 맞는 주 리소스를 작성한다: AWS는 aws_db_instance(Aurora는 aws_rds_cluster), GCP는 google_sql_database_instance, Azure는 azurerm_mssql_database(SQL Server용, azurerm_mssql_server 아래). 여기서 엔진, 버전, 클래스, 스토리지, 백업 시간대를 지정한다.

PostgreSQL

resource "aws_db_instance" "pg" {
  identifier        = "app-db"
  engine            = "postgres"
  engine_version    = "16"
  instance_class    = "db.t3.medium"
  allocated_storage = 50
  db_name           = "appdb"
  username          = "pgadmin"
  password          = var.db_password
  backup_retention_period = 7
}

SQL Server

resource "azurerm_mssql_database" "db" {
  name      = "appdb"
  server_id = azurerm_mssql_server.srv.id
  sku_name  = "S1"
}

4. parameter group을 코드로 작성한다

데이터베이스 설정(max_connections, work_mem, 문자 집합)을 콘솔에서 수동으로 하지 말고 파라미터 리소스로 정의한다. AWS는 aws_db_parameter_group, GCP는 google_sql_database_instance 안의 settings.database_flags, Azure는 server configuration 리소스를 사용한다. 그러면 설정이 버전 관리되고 재현 가능해진다.

PostgreSQL

resource "aws_db_parameter_group" "pg" {
  name   = "app-pg16"
  family = "postgres16"

  parameter {
    name  = "max_connections"
    value = "200"
  }
}
# inside aws_db_instance:
# parameter_group_name = aws_db_parameter_group.pg.name

5. 비밀 정보는 변수와 secrets manager로 전달한다

.tf 파일에 비밀번호를 하드코딩하지 말고 sensitive 변수(variable ... sensitive = true)로 정의하며, 값은 TF_VAR_ 환경 변수, secrets manager(AWS Secrets Manager, Vault) 또는 CI secret으로 제공한다. 주의: 값은 state 파일에 기록되므로 remote state는 암호화되어야 한다. 가장 깔끔한 방법은 비밀번호를 Terraform에 전달하지 않고 secrets manager 리소스에서 읽는 것이다.

PostgreSQL

variable "db_password" {
  type      = string
  sensitive = true
}

# CI/shell:
export TF_VAR_db_password="$(vault kv get -field=pw secret/db)"

6. plan과 apply 흐름을 실행한다

먼저 terraform init으로 provider/backend를 준비하고, terraform plan으로 무엇이 바뀔지 확인한 뒤, 승인하고 terraform apply로 적용한다. plan 출력을 검토하지 않고 apply하지 않는다. CI에서 안전한 흐름은 'plan'을 PR 코멘트로 게시하고 사람의 승인 후 apply하는 것이다. 파괴적 변경(예: 인스턴스 재생성)은 plan 출력의 -/+ 표시로 잡아낸다.

PostgreSQL

terraform init
terraform plan -out=plan.tfplan
terraform apply plan.tfplan

7. drift를 감지하고 수정한다

누군가 콘솔에서 수동으로 변경하면 실제 인프라와 state가 어긋난다(drift). '아무것도 바뀌지 않았어야 할 때' terraform plan에 나타나는 차이가 drift를 드러내며, 차이는 terraform refresh/plan으로 확인한다. 변경을 코드에 반영하거나 apply로 인프라를 코드 상태로 되돌린다. 원칙적으로 수동 변경을 금지하고 코드를 단일 진실 공급원으로 삼는다.

PostgreSQL

# drift check (when no changes are expected):
terraform plan
# expect 'No changes'; any diff means drift
# bring an existing resource into state with import:
terraform import aws_db_instance.pg app-db
DBAtlas에서 열기 →