DBAtlas · データベース運用アトラス

Terraform によるデータベースのプロビジョニング (IaC)

症状

目標: Terraform でデータベースインフラ (インスタンス、パラメータグループ、ネットワーク) をコードとして定義し (Infrastructure as Code)、plan/apply のフロー、リモートステート、ドリフト管理を構築する。

1. プロバイダーを選択して構成する (aws / google / azurerm)

使用するクラウドのプロバイダーを定義し、バージョンを固定する。required_providers ブロックで、AWS には hashicorp/aws、GCP には hashicorp/google、Azure には hashicorp/azurerm のプロバイダーを固定する。バージョン固定によりチーム全体で一貫した動作が保証される。

PostgreSQL

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

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

2. リモートステートとロックを構築する

ステートファイルをローカルに保持せず、チーム作業とセキュリティのためにリモートバックエンドを使う。一般的な選択肢は、AWS では S3 バックエンド + DynamoDB のロックテーブル、GCP では gcs バックエンド、Azure では azurerm バックエンド (Storage Account) である。リモートステートはロックにより同時の apply を防ぎ、ステートを唯一の信頼できる情報源にする。ステートにはパスワードが平文で含まれうるため、バックエンドは暗号化しアクセスを制限する。

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. パラメータグループをコードとして書く

データベースの設定 (max_connections、work_mem、文字セット) は、コンソールで手動で設定するのではなくパラメータリソースで定義する。AWS では aws_db_parameter_group、GCP では google_sql_database_instance 内の settings.database_flags、Azure ではサーバー構成リソースを使う。これにより設定がバージョン管理され、再現可能になる。

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. シークレットを変数とシークレットマネージャー経由で渡す

パスワードを .tf ファイルにハードコードせず、機密変数 (variable ... sensitive = true) として定義し、値は TF_VAR_ 環境変数、シークレットマネージャー (AWS Secrets Manager、Vault)、または CI のシークレットで渡す。注意: 値はステートファイルに書き込まれるため、リモートステートは暗号化する必要がある。最もクリーンな方法は、パスワードを Terraform に一切渡さず、シークレットマネージャーのリソースから読み取ることである。

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 でプロバイダー/バックエンドを準備し、terraform plan で何が変わるかを確認し、承認してから terraform apply で適用する。plan の出力を確認せずに apply しないこと。CI での安全なフローは、'plan' を PR のコメントとして投稿し、人による承認後に apply することである。破壊的な変更 (例: インスタンスの再作成) は plan 出力の -/+ マーカーで見つける。

PostgreSQL

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

7. ドリフトを検出して修正する

誰かがコンソールで手動変更を行うと、実際のインフラとステートが乖離する (ドリフト)。「何も変わっていないはずなのに」terraform plan に差分が現れることでドリフトが明らかになり、差分は 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 で開く →