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.name5. シークレットを変数とシークレットマネージャー経由で渡す
パスワードを .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.tfplan7. ドリフトを検出して修正する
誰かがコンソールで手動変更を行うと、実際のインフラとステートが乖離する (ドリフト)。「何も変わっていないはずなのに」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