TerraformでRDSをプロビジョニングする方法|aws_db_instanceとサブネットグループ・パラメータグループの実践設計

宮崎智広 この記事の監修:宮崎智広(Linux実務・教育歴20年以上・受講者3,100名超)
HOMELinux技術 リナックスマスター.JP(Linuxマスター.JP)Terraform > TerraformでRDSをプロビジョニングする方法|aws_db_instanceとサブネットグループ・パラメータグループの実践設計
「TerraformでRDSを追加しようとしたら、サブネットグループ・パラメータグループ・セキュリティグループと関連リソースが多くてどこから手をつけるべきかわからない」
こうした声はよく聞きます。RDSはEC2やVPCと比べてリソース間の依存関係が多く、サブネットグループの設定漏れやセキュリティグループの許可ポートミスで apply 直後にエラーになるケースが後を絶ちません。

この記事では、aws_db_instance を中心に、aws_db_subnet_group・aws_db_parameter_group・aws_security_group を組み合わせたRDSのプロビジョニング手順を実践解説します。MySQLを題材に、削除保護・マルチAZ・自動バックアップといった本番環境の必須設定も含めてカバーします。

動作確認環境: Terraform 1.8.5 / AWS Provider 5.50.0(ap-northeast-1リージョン、MySQL 8.0.35)

この記事のポイント

・aws_db_instanceの前にサブネットグループとセキュリティグループの定義が必要
・aws_db_parameter_groupで文字コード・タイムゾーンをコードで管理できる
・本番環境ではdeletion_protection=trueとskip_final_snapshot=falseを必ず設定する
・terraform applyは完了まで最大20分かかるためCI/CDのタイムアウト設定に注意


「このままじゃマズい」と感じていませんか?
参考書を開く気力もない、同年代に取り残される不安——
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

なぜRDSをTerraformで管理するのか

AWSコンソールからRDSを手作業で構築すると、パラメータグループの設定値・サブネットグループのAZ構成・セキュリティグループのingressルールが別々の画面に散らばります。後からどの設定が本番と開発で異なるのかを把握しにくく、「なぜかstgで動いてprodで動かない」問題の温床になります。

Terraformで管理することで次の3つの問題が解消されます。

設定の再現性:terraform applyを実行すれば同じ構成のRDSを何度でも作れる
差分の可視化:terraform planでパラメータ変更の影響範囲を事前に確認できる
レビュー可能性:GitのPRでRDSの設定変更をチームでレビューできる

ただしRDSはリソース間の依存関係が明確なため、作成順序を間違えると apply 時にエラーになります。次のセクションで依存関係の全体像と具体的な設定手順を解説します。

aws_db_instanceの基本設定|依存リソースの作成順序

1. aws_db_subnet_groupでサブネットグループを作成する

RDSを作成するには必ず事前にサブネットグループが必要です。サブネットグループは「RDSを配置できるサブネットの集合」で、マルチAZ構成ではサブネットグループに2つ以上のAZのサブネットを含める必要があります。

# subnet_group.tf resource "aws_db_subnet_group" "main" { name = "main-db-subnet-group" subnet_ids = [ aws_subnet.private_a.id, # ap-northeast-1a のプライベートサブネット aws_subnet.private_c.id, # ap-northeast-1c のプライベートサブネット ] tags = { Name = "main-db-subnet-group" Environment = "production" } }

subnet_ids には必ず2つ以上のAZのサブネットIDを渡してください。同一AZのサブネットを2つ指定しても「少なくとも2つのアベイラビリティゾーンが必要」というエラーになります。

2. セキュリティグループを定義する

RDS用のセキュリティグループでは、アクセス元(アプリケーションサーバーのセキュリティグループ)からのみ3306番ポートを許可します。0.0.0.0/0 の全開放は絶対に避けてください。

# security_group.tf resource "aws_security_group" "rds" { name = "rds-sg" description = "Security group for RDS MySQL" vpc_id = aws_vpc.main.id ingress { from_port = 3306 to_port = 3306 protocol = "tcp" # アプリケーションサーバーのSGからのみ許可 security_groups = [aws_security_group.app.id] description = "MySQL from app servers" } egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] } tags = { Name = "rds-sg" } }

3. aws_db_instanceでMySQLを起動する

サブネットグループとセキュリティグループが揃ったら aws_db_instance を定義します。

# rds.tf resource "aws_db_instance" "main" { identifier = "main-mysql" engine = "mysql" engine_version = "8.0.35" instance_class = "db.t3.micro" allocated_storage = 20 storage_type = "gp3" db_name = "appdb" username = "admin" password = var.db_password # variables.tfで定義したsensitive変数 db_subnet_group_name = aws_db_subnet_group.main.name vpc_security_group_ids = [aws_security_group.rds.id] parameter_group_name = aws_db_parameter_group.main.name # 本番必須設定 deletion_protection = true skip_final_snapshot = false final_snapshot_identifier = "main-mysql-final-snapshot" backup_retention_period = 7 backup_window = "02:00-03:00" maintenance_window = "mon:03:00-mon:04:00" tags = { Name = "main-mysql" Environment = "production" } }

password には必ず sensitive 変数を使い、コードに平文で書き込まないようにしてください。

# variables.tf variable "db_password" { description = "RDS admin password" type = string sensitive = true # terraform planとログに値が表示されなくなる }

実際に terraform apply を実行するとRDSの作成が始まります。完了までの実際の出力例を見てみましょう。

$ terraform apply -var="db_password=MySecretPass123!" aws_db_subnet_group.main: Creating... aws_db_subnet_group.main: Creation complete after 4s [id=main-db-subnet-group] aws_security_group.rds: Creating... aws_security_group.rds: Creation complete after 3s [id=sg-0a1b2c3d4e5f67890] aws_db_instance.main: Creating... aws_db_instance.main: Still creating... [10s elapsed] aws_db_instance.main: Still creating... [1m0s elapsed] # ... (RDSの作成は通常5分~20分かかる) aws_db_instance.main: Creation complete after 8m42s [id=main-mysql] Apply complete! Resources: 3 added, 0 changed, 0 destroyed.

現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、TerraformによるRDS・VPC・EC2の構成設計からモジュール分離・環境分離まで、実機を使ったハンズオン形式のセミナーを開催しています。
>> Terraform実践セミナーの詳細はこちら

パラメータグループで文字コードとタイムゾーンをコード化する

1. aws_db_parameter_groupを定義する

AWSが提供するデフォルトのパラメータグループは変更不可です。カスタム設定(文字コードやタイムゾーン)が必要な場合は aws_db_parameter_group を作成してから aws_db_instance に紐づけます。

# parameter_group.tf resource "aws_db_parameter_group" "main" { name = "main-mysql80" family = "mysql8.0" # engineとengine_versionに対応したfamilyを指定 description = "Custom parameter group for MySQL 8.0" parameter { name = "character_set_server" value = "utf8mb4" } parameter { name = "character_set_client" value = "utf8mb4" } parameter { name = "time_zone" value = "Asia/Tokyo" apply_method = "immediate" # 再起動なしで即時反映 } parameter { name = "slow_query_log" value = "1" } parameter { name = "long_query_time" value = "1" # 1秒以上のクエリをスロークエリとして記録 } tags = { Name = "main-mysql80" } }

2. apply_methodの違い(immediateとpending-reboot)

パラメータの apply_method には2種類あります。

immediate:apply直後に反映。time_zoneやslow_query_logなど動的パラメータに使用できる
pending-reboot:次のメンテナンスウィンドウまたは手動再起動後に反映。innodb_buffer_pool_sizeなどの静的パラメータに必要

静的パラメータを immediate にしてもエラーにはなりませんが反映されません。AWSドキュメントのパラメータ一覧で「Dynamic/Static」を確認してから設定してください。

実務Tips|削除保護・自動バックアップ・マルチAZ

1. 本番環境の削除保護設定

terraform destroy を実行しても本番DBが消えないよう、deletion_protection = true は必須です。削除保護が有効な状態で destroy しようとすると次のエラーになります。

$ terraform destroy Error: RDS DB Instance is protected from deletion. with aws_db_instance.main, on rds.tf line 1, in resource "aws_db_instance" "main": Please disable deletion protection before deleting.

意図的に削除する場合は deletion_protection = false に変更して terraform apply を先に実行してから destroy します。この2段階が「事故を防ぐフェンス」として機能します。

2. skip_final_snapshotとfinal_snapshot_identifierの設定

skip_final_snapshot = false(デフォルト)の場合、terraform destroy を実行するとRDS削除前に最終スナップショットが自動作成されます。final_snapshot_identifier には一意の名前を設定してください(省略するとエラーになります)。

開発環境では毎回スナップショットが作成されるとコストがかかるため、skip_final_snapshot = true にするケースもあります。環境ごとに var-file で制御する設計が一般的です。

3. マルチAZ設定でフェイルオーバーを実現する

本番環境では multi_az = true を設定します。1行追加するだけですが、コスト(インスタンス料金が約2倍)と引き換えに単一AZ障害時の自動フェイルオーバーが有効になります。

resource "aws_db_instance" "main" { # ... 既存の設定 ... multi_az = true # スタンバイレプリカを別AZに自動配置 } output "rds_endpoint" { value = aws_db_instance.main.endpoint sensitive = false }

マルチAZではスタンバイに直接接続することはできません。アプリケーションのDB接続先は endpoint 属性(Writerエンドポイント)1つだけです。フェイルオーバー発生時もエンドポイントは変わらず、DNSが自動的にスタンバイを新しいプライマリとして解決します。

「InvalidParameterCombination」などのapplyエラーと対処法

1. サブネットグループのAZが1つしかない場合

マルチAZを有効にしようとしたとき、または一部のインスタンスタイプで複数AZのサブネットが要求されるときに発生します。

$ terraform apply Error: InvalidParameterCombination: Cannot specify a DB subnet group for DB instances in VPC. # またはサブネットグループのAZが不足している場合: Error: DB subnet group "main-db-subnet-group" is invalid because it must have at least 2 availability zones.

対処法: aws_db_subnet_group の subnet_ids に2つ以上のAZのサブネットIDが含まれているか確認してください。

2. パラメータグループのfamilyが一致しない場合

$ terraform apply Error: InvalidParameterValue: Could not find parameter group family "mysql8.0" for parameter group "main-mysql80" # familyの名前は厳密に確認する(例: mysql8.0, mysql8.4, aurora-mysql8.0 は別物)

対処法: engine_version と family が対応しているか確認してください。MySQL 8.0 系は family = "mysql8.0"、MySQL 8.4 系は family = "mysql8.4" になります。

3. applyが途中でタイムアウトする場合

RDSの作成は最大20分かかります。CI/CDパイプラインのデフォルトタイムアウトが短いとapplyが中断されます。Terraform側では timeouts ブロックで延長できます。

resource "aws_db_instance" "main" { # ... timeouts { create = "40m" # デフォルトは40m。CI側も合わせて延長する update = "80m" # マルチAZ変更などは時間がかかる delete = "40m" } }

本記事のまとめ

TerraformでRDSをプロビジョニングする設計のポイントをまとめます。
やりたいこと 使うリソース・設定
RDSのサブネットグループを定義する resource "aws_db_subnet_group" { subnet_ids = [...] }
RDS専用セキュリティグループを作る resource "aws_security_group" { ingress { from_port = 3306 } }
MySQLインスタンスを作成する resource "aws_db_instance" { engine = "mysql" }
文字コード・タイムゾーンを設定する resource "aws_db_parameter_group" { parameter { name = "time_zone" } }
誤削除を防止する aws_db_instance { deletion_protection = true }
マルチAZフェイルオーバーを有効にする aws_db_instance { multi_az = true }
aws_db_instance は依存リソースが多いぶん、作成順序を事前に把握してHCLを整理することが安定運用の鍵です。パスワード管理には sensitive 変数を使い、deletion_protection と skip_final_snapshot = false のセットを本番環境の最低ラインとして設定してください。
現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、TerraformによるRDS・VPC・EC2の構成設計からパラメータグループ・削除保護・マルチAZ設計まで、実機を使ったハンズオン形式のセミナーを開催しています。
>> Terraform実践セミナーの詳細はこちら

無料メルマガで学習を続ける

Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。

登録無料・いつでも解除できます

暗記不要・1時間後にはサーバーが動く

3,100名以上が実践した「型」を無料で公開中

プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。

姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

Linux無料マニュアル(図解60P) 名前とメールで30秒登録
宮崎 智広

この記事を書いた人

宮崎 智広(みやざき ともひろ)

株式会社イーネットマーキュリー代表。現役のLinuxサーバー管理者として20年以上の実務経験を持ち、これまでに累計3,100名以上のエンジニアを指導してきたLinux教育のプロフェッショナル。「現場で本当に使える技術」を体系的に伝えることをモットーに、実践型のLinuxセミナーの開催や無料マニュアルの配布を通じてLinux人材の育成に取り組んでいる。

趣味は、キャンプにカメラ、トラウト釣り。好きな食べ物は、ラーメンにお酒。休肝日が作れない、酒量を減らせないのが悩み。最近、ドラマ「フライトエンジェル」を観て涙腺が崩壊しました。