TerraformでRDSを構築してみよう!
こんにちは株式会社ベンジャミンの市川です!
暑さが日ごとに加わってまいりましたが、お健やかにお過ごしでしょうか。
今回は、弊社の案件でも利用しているインフラをコード化できるIaCの1つであるTerraformを使って、Amazon RDSを作成してみましたので、その方法について書いていきます。
※注意※ AWS無料利用枠の範囲外のリソースが含まれます。作成した後は必要に応じて、リソースを削除してください。
目次
環境情報
- Terraform v1.4.4
- macOS Monterey 12.5
前提
- Terraform v1.4.4以上 がインストール済みであること
- AWSアカウントを持っていること
- アクセスキー、シークレットアクセスキーを発行済みであること
AWS構成図
ディレクトリ構成
今回のハンズオンで作成していく最終的なディレクトリ構成は以下のとおりです。
root/
├ main.tf # 全体設定および/modulesのresourceに渡す変数の値を定義・呼び出し
├ terraform.tfvars # 環境名、認証情報、リージョンを定義 ※機密情報であるため必ずgit管理から外すこと
└ modules/ # 各リソースのファイル
├ rds/ # リソースの性能・パラメータを定義したテンプレート。基本的にはいじらない(いじりたい値がある場合はvariableとして切り出し、main.tfから注入する)
│ ├ cluster.tf
│ ├ instance.tf
│ ├ parameter.tf
│ ├ variables.tf # modules/rds/の各.tfで使用する型情報、初期値などを定義
│ └ outputs.tf # 他のリソースへ変数の値を公開
└ vpc/ # リソースの性能・パラメータを定義したテンプレート。基本的にはいじらない(いじりたい値がある場合はvariableとして切り出し、main.tfから注入する)
├ vpc
│ └ vpc.tf
├ private_subnet
│ └ private_subnet.tf
├ security_group
│ └ security_group.tf
├ route_table
│ └ route_table.tf
└ route_table_association
└ route_table_association.tf
1.Terraformのバージョンを確認する
まず、Terraformがインストール済みであるか、かつバージョンがv1.4.4以上であるかを確認しておきます。次のコマンドを実行し、確認してみましょう!
terraform --version
実行後、以下の例のように出力されていればインストールできています。
xxxxxxxxx@xxxxxxxxMacBook-Pro rds % terraform --version
Terraform v1.4.4
on darwin_arm64
2.Terraformの初期設定をする
次に、TerraformからAWSリソースにアクセスするためのクレデンシャル情報(リソース作成・削除の実行権限があるアカウントの)やバージョンなどを設定していきましょう!
2-1. コードの作成
以下のとおりファイルを作成し、コードを書いてみてください。
- terraform.tfvars
environment = "test"
access_key = "XXXXXXXXXXXXXXXXXXXX" # ご自分のアカウントのアクセスキーに置き換えてください
secret_key = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" # ご自分のアカウントのシークレットキーに置き換えてください
region = "ap-northeast-1"
- main.tf
variable "environment" { type = string }
variable "access_key" { type = string }
variable "secret_key" { type = string }
variable "region" { type = string }
locals {
service = "rds-handson"
}
terraform {
required_version = ">=1.4.4"
required_providers {
aws = {
source = "hashicorp/aws"
version = "4.60.0"
}
}
}
provider "aws" {
access_key = var.access_key
secret_key = var.secret_key
region = var.region
default_tags {
tags = {
Environment = var.environment
Service = local.service
}
}
}
コード補足
`default_tags{…}` を書くことで、Terraformで作成する全てのリソースに自動的にタグを付加することができます。タグを付けるとリソースの管理や検索がしやすくなります!
default_tags {
tags = {
Environment = var.environment
Service = local.service
}
}
2-2. 作業ディレクトリの初期化、必要なプラグインのインストール
コードをもとに初期化するため、ターミナルから以下のコマンドを実行しましょう。
terraform init
3.VPC関連を作成する
RDSを作成する前に、RDSを立ち上げるためのVPC関連(VPC、サブネット、セキュリティグループ、ルートテーブル、ルート)を作成していきましょう。
また、今回のハンズオンではAWSリソースの再利用性を高めるため、ソースコードをテンプレート化できる機能「Terraform Module」を使って構築していきます。
この機能を使えば、moduleブロックからresourceブロックに変数(variable)の値を渡し、呼び出すことで、その箇所だけをカスタマイズしたリソースを好きな数だけ作成することができます!
3-1. コードの作成
以下のとおりファイルを作成し、コードを書いてみてください。※main.tfは追加で記述
- main.tf
/* ~~~ 省略(これまでのmain.tfに以下のコードを追加してください) ~~~ */
module "vpc" {
source = "./modules/vpc/vpc"
vpc_config = {
cidr_block = "10.100.0.0/16"
name = "${var.environment}-${local.service}-vpc"
}
}
module "private_subnet_aurora_1a" {
source = "./modules/vpc/private_subnet"
vpc_subnets_private = {
vpc_id = module.vpc.vpc_id
availability_zone = "ap-northeast-1a"
cidr_block = "10.100.20.0/24"
name = "${var.environment}-${local.service}-subnet-private-a1-aurora"
}
}
module "private_subnet_aurora_1c" {
source = "./modules/vpc/private_subnet"
vpc_subnets_private = {
vpc_id = module.vpc.vpc_id
availability_zone = "ap-northeast-1c"
cidr_block = "10.100.30.0/24"
name = "${var.environment}-${local.service}-subnet-private-c1-aurora"
}
}
module "security_group_aurora" {
source = "./modules/vpc/security_group"
vpc_security_group = {
vpc_id = module.vpc.vpc_id
name = "${var.environment}-${local.service}-sg-aurora"
description = "for Aurora"
}
# インバウンドルール
vpc_security_group_ingress_rule = {
security_groups = [] # 今回は指定していませんが、通常はFargate のセキュリティグループなどを指定します
protocol = "tcp"
from_port = 3306
to_port = 3306
}
# アウトバウンドルール
vpc_security_group_egress_rule = {
cidr_blocks = ["0.0.0.0/0"]
protocol = "tcp"
from_port = 0
to_port = 65535
description = "Outbound ALL"
}
}
module "route_table_private_aurora" {
source = "./modules/vpc/route_table"
vpc_route_table = {
vpc_id = module.vpc.vpc_id
name = "${var.environment}-${local.service}-rtb-private-aurora"
}
}
module "route_table_association_private_aurora_1a" {
source = "./modules/vpc/route_table_association"
vpc_route_table_association = {
subnet_id = module.private_subnet_aurora_1a.private_subnet_id
route_table_id = module.route_table_private_aurora.route_table_id
}
}
module "route_table_association_private_aurora_1c" {
source = "./modules/vpc/route_table_association"
vpc_route_table_association = {
subnet_id = module.private_subnet_aurora_1c.private_subnet_id
route_table_id = module.route_table_private_aurora.route_table_id
}
}
- 【VPC】/modules/vpc/vpc.tf
variable "vpc_config" {
type = object({
cidr_block = string
name = string
})
}
output "vpc_id" {
value = aws_vpc.default.id
}
resource "aws_vpc" "default" {
cidr_block = var.vpc_config.cidr_block
enable_dns_support = true
enable_dns_hostnames = true
tags = {
Name = var.vpc_config.name
}
}
- 【プライベートサブネット】/modules/vpc/private_subnet.tf
variable "vpc_subnets_private" {
type = object({
vpc_id = string
availability_zone = string
cidr_block = string
name = string
})
}
output "private_subnet_id" {
value = aws_subnet.private.id
}
resource "aws_subnet" "private" {
vpc_id = var.vpc_subnets_private.vpc_id
map_public_ip_on_launch = false # プライベート
availability_zone = var.vpc_subnets_private.availability_zone
cidr_block = var.vpc_subnets_private.cidr_block
tags = {
Name = var.vpc_subnets_private.name
}
}
- 【セキュリティグループ】/modules/vpc/security_group.tf
variable "vpc_security_group" {
type = object({
name = string
vpc_id = string
description = optional(string)
})
}
variable "vpc_security_group_ingress_rule" {
type = object({
security_groups = optional(list(string))
cidr_blocks = optional(list(string))
protocol = string
from_port = number
to_port = number
description = optional(string)
})
}
variable "vpc_security_group_egress_rule" {
type = object({
cidr_blocks = list(string)
protocol = string
from_port = number
to_port = number
description = optional(string)
})
}
output "security_group_id" {
value = aws_security_group.default.id
}
resource "aws_security_group" "default" {
name = var.vpc_security_group.name
vpc_id = var.vpc_security_group.vpc_id
description = var.vpc_security_group.description
// インバウンドトラフィック
ingress {
security_groups = var.vpc_security_group_ingress_rule.security_groups
cidr_blocks = var.vpc_security_group_ingress_rule.cidr_blocks
protocol = var.vpc_security_group_ingress_rule.protocol
from_port = var.vpc_security_group_ingress_rule.from_port
to_port = var.vpc_security_group_ingress_rule.to_port
description = var.vpc_security_group_ingress_rule.description
}
// アウトバウンドトラフィック
egress {
cidr_blocks = var.vpc_security_group_egress_rule.cidr_blocks
protocol = var.vpc_security_group_egress_rule.protocol
from_port = var.vpc_security_group_egress_rule.from_port
to_port = var.vpc_security_group_egress_rule.to_port
description = var.vpc_security_group_egress_rule.description
}
}
- 【ルートテーブル】/modules/vpc/route_table.tf
variable "vpc_route_table" {
type = object({
vpc_id = string
name = string
})
}
output "route_table_id" {
value = aws_route_table.default.id
}
resource "aws_route_table" "default" {
vpc_id = var.vpc_route_table.vpc_id
tags = {
Name = var.vpc_route_table.name
}
}
- 【ルート】/modules/vpc/route_table_association.tf
variable "vpc_route_table_association" {
type = object({
subnet_id = string
route_table_id = string
})
}
resource "aws_route_table_association" "default" {
subnet_id = var.vpc_route_table_association.subnet_id
route_table_id = var.vpc_route_table_association.route_table_id
}
3-2. TerraformからAWS環境にリソースを作成
VPCの各ファイルを作成してきたところで、一度リソースをAWS環境へ作成してみましょう!
次の手順に沿って進めてみてください。
- ①依存関係を更新する
moduleブロックを追加したとき、/modules配下の子モジュール(.tfファイル)をimportするためには`terraform init`
で依存関係を更新する必要があります。
※ moduleブロックを追加するたびに更新が必要ですので注意しましょう
ターミナルから以下のコマンドを実行しましょう。
terraform init
- ②Terraform による実行計画を確認する
AWS環境にリソースを作成する前の確認作業として、`terraform plan` で.tf ファイルに記載された情報を元に、どのようなリソースが作成 / 修正 / 削除されるかを確認しておきましょう。
terraform plan
実行後、以下のように出力されているはずです。「Plan: 7 to add」で分かる通り、main.tfに記載されたVPC関連のリソース(moduleブロック)の作成が計画されていることが分かります。
# 出力結果
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
~~~省略~~~
Plan: 7 to add, 0 to change, 0 to destroy.
- ③TerraformのコードからAWS環境にリソースを作成する
実行計画を確認できたところで、実際にAWS環境にリソースを作成してみましょう。
次のコマンドを実行してください。
※ -auto-approve
オプションを付けることで、コマンド実行前の確認「yes」の入力が不要になります
terraform apply -auto-approve
実行後、以下のように出力されていれば無事にリソースの作成ができています。
Plan: 7 to add, 0 to change, 0 to destroy.
module.vpc.aws_vpc.default: Creating...
module.vpc.aws_vpc.default: Still creating... [10s elapsed]
module.vpc.aws_vpc.default: Creation complete after 12s [id=vpc-xxxxxxxxxxxxxxxxx]
module.route_table_private_aurora.aws_route_table.default: Creating...
module.private_subnet_aurora_1c.aws_subnet.private: Creating...
module.private_subnet_aurora_1a.aws_subnet.private: Creating...
module.security_group_aurora.aws_security_group.default: Creating...
module.route_table_private_aurora.aws_route_table.default: Creation complete after 1s [id=rtb-xxxxxxxxxxxxxxxxx]
module.private_subnet_aurora_1c.aws_subnet.private: Creation complete after 1s [id=subnet-xxxxxxxxxxxxxxxxx]
module.private_subnet_aurora_1a.aws_subnet.private: Creation complete after 1s [id=subnet-xxxxxxxxxxxxxxxxx]
module.route_table_association_private_aurora_1c.aws_route_table_association.default: Creating...
module.route_table_association_private_aurora_1a.aws_route_table_association.default: Creating...
module.route_table_association_private_aurora_1c.aws_route_table_association.default: Creation complete after 0s [id=rtbassoc-xxxxxxxxxxxxxxxxx]
module.route_table_association_private_aurora_1a.aws_route_table_association.default: Creation complete after 0s [id=rtbassoc-xxxxxxxxxxxxxxxxx]
module.security_group_aurora.aws_security_group.default: Creation complete after 3s [id=sg-xxxxxxxxxxxxxxxxx]
Apply complete! Resources: 7 added, 0 changed, 0 destroyed.
念のため、AWSコンソールにアクセスし、作成できているか確認しておきましょう。
【VPC】https://ap-northeast-1.console.aws.amazon.com/vpc/home?region=ap-northeast-1#vpcs:search=rds-handson
【セキュリティグループ】https://ap-northeast-1.console.aws.amazon.com/vpc/home?region=ap-northeast-1#SecurityGroups:search=rds-handson
- 表示されたVPC ID、セキュリティグループIDをクリック
- applyした内容で実際に作成されていることが確認できる
4.RDSを作成する
これまでTerraformの初期設定や、RDSを作成するためのVPCを準備してきました。
本題のRDSを作成していきましょう!
4-1. コードの作成
以下のとおりファイルを作成し、コードを書いてみてください。※main.tfは追加で記述
- main.tf
/* ~~~ 省略(これまでのmain.tfに以下のコードを追加してください) ~~~ */
module "aurora" {
source = "./modules/rds"
aurora_subnet_group = {
subnet_ids = [module.private_subnet_aurora_1a.private_subnet_id, module.private_subnet_aurora_1c.private_subnet_id]
name = "${var.environment}-${local.service}-aurora-subnet-group"
description = "${var.environment}-${local.service}-aurora-subnet-group"
}
aurora_parameter_group = {
famiily = "aurora-mysql8.0"
cluster = {
name = "${var.environment}-${local.service}-aurora-param-group-mysql-80-cluster"
description = "${var.environment}-${local.service}-aurora-param-group-mysql-80-cluster"
}
instance = {
name = "${var.environment}-${local.service}-aurora-param-group-mysql-80"
description = "${var.environment}-${local.service}-aurora-param-group-mysql-80"
}
}
aurora_config = {
engine = "aurora-mysql"
engine_version = "8.0.mysql_aurora.3.03.1"
engine_mode = "provisioned"
}
aurora_cluster = {
identifier = "${var.environment}-${local.service}-aurora-cluster"
vpc_security_group_ids = [
module.security_group_aurora.security_group_id
]
availability_zones = ["ap-northeast-1a", "ap-northeast-1c"]
database_name = "${var.environment}_rds_handson_db"
master_username = "${var.environment}_rds_handson_user"
}
aurora_instance = {
identifier = "${var.environment}-${local.service}-aurora-instance"
availability_zone = "ap-northeast-1a"
instance_class = "db.t3.medium"
number_of_instance = 1
}
}
- 【クラスター】/modules/rds/cluster.tf
resource "aws_rds_cluster" "default" {
cluster_identifier = var.aurora_cluster.identifier
engine = var.aurora_config.engine
engine_version = var.aurora_config.engine_version
engine_mode = var.aurora_config.engine_mode
database_name = var.aurora_cluster.database_name
master_username = var.aurora_cluster.master_username
master_password = random_password.DB_Password.result
db_subnet_group_name = aws_db_subnet_group.default.name
vpc_security_group_ids = var.aurora_cluster.vpc_security_group_ids
availability_zones = var.aurora_cluster.availability_zones
port = 3306
db_cluster_parameter_group_name = aws_rds_cluster_parameter_group.default.name
backup_retention_period = 7
storage_encrypted = true
backtrack_window = 0
preferred_backup_window = "18:30-19:00"
preferred_maintenance_window = "tue:16:00-tue:16:30"
deletion_protection = false # 保護モード(強制的に削除を許可しない or 許可する)
iam_database_authentication_enabled = false
skip_final_snapshot = true
copy_tags_to_snapshot = true
lifecycle {
ignore_changes = [
master_password
]
}
}
resource "random_password" "DB_Password" {
length = 25
special = false
}
- 【インスタンス】/modules/rds/instance.tf
resource "aws_rds_cluster_instance" "default" {
count = var.aurora_instance.number_of_instance
cluster_identifier = aws_rds_cluster.default.id
db_parameter_group_name = aws_db_parameter_group.default.name
db_subnet_group_name = aws_db_subnet_group.default.name
publicly_accessible = false
promotion_tier = 1
monitoring_interval = 0
auto_minor_version_upgrade = false
identifier = "${var.aurora_instance.identifier}-${count.index + 1}" // 配列base0を1に変換
engine = var.aurora_config.engine
engine_version = var.aurora_config.engine_version
availability_zone = var.aurora_instance.availability_zone
instance_class = var.aurora_instance.instance_class
}
- 【パラメータ】/modules/rds/parameter.tf
resource "aws_db_subnet_group" "default" {
name = var.aurora_subnet_group.name
description = var.aurora_subnet_group.description
subnet_ids = var.aurora_subnet_group.subnet_ids
}
resource "aws_rds_cluster_parameter_group" "default" {
name = var.aurora_parameter_group.cluster.name
description = var.aurora_parameter_group.cluster.description
family = var.aurora_parameter_group.famiily
}
resource "aws_db_parameter_group" "default" {
name = var.aurora_parameter_group.instance.name
description = var.aurora_parameter_group.instance.description
family = var.aurora_parameter_group.famiily
}
resource "aws_ssm_parameter" "Aurora_Username" {
name = "/rds/${var.aurora_cluster.identifier}/username"
value = aws_rds_cluster.default.master_username
type = "SecureString"
}
resource "aws_ssm_parameter" "Aurora_Password" {
name = "/rds/${var.aurora_cluster.identifier}/password"
value = aws_rds_cluster.default.master_password
type = "SecureString"
}
- 【変数定義ファイル】/modules/rds/variables.tf
variable "aurora_subnet_group" {
type = object({
name = string
description = string
subnet_ids = list(string)
})
}
variable "aurora_parameter_group" {
type = object({
famiily = string
cluster = object({
name = string
description = string
})
instance = object({
name = string
description = string
})
})
}
variable "aurora_config" {
type = object({
engine = string
engine_version = string
engine_mode = string
})
}
variable "aurora_cluster" {
type = object({
identifier = string
vpc_security_group_ids = list(string)
availability_zones = list(string)
database_name = string
master_username = string
})
}
variable "aurora_instance" {
type = object({
identifier = string
availability_zone = string
instance_class = string
number_of_instance = number
})
}
- 【出力変数ファイル】/modules/rds/outputs.tf
output "db_host" {
value = aws_rds_cluster.default.endpoint
}
output "db_port" {
value = aws_rds_cluster.default.port
}
output "db_name" {
value = aws_rds_cluster.default.database_name
}
コード補足
- cluster.tf
Terraformで作成した全リソースを削除するコマンド `terraform destroy
` を実行するとき、保護モードがオンになっているとエラーとなり削除できないため “オフ” にしています。
deletion_protection = false # 保護モード(強制的に削除を許可しない or 許可する)
- parameter.tf
将来的にアプリ側のサーバ(ECSなど)からデータベースのユーザー名やパスワードを利用できるようにしておくため、AWS Systems Manager(SSM)を作成し、そこへ値を格納しています。
※AWS Secrets Managerを使うケースもあります
resource "aws_ssm_parameter" "Aurora_Username" {
name = "/rds/${var.aurora_cluster.identifier}/username"
value = aws_rds_cluster.default.master_username
type = "SecureString"
}
resource "aws_ssm_parameter" "Aurora_Password" {
name = "/rds/${var.aurora_cluster.identifier}/password"
value = aws_rds_cluster.default.master_password
type = "SecureString"
}
4-2. TerraformからAWS環境にリソースを作成
RDSの各ファイルを作成したあとは、VPCの作成で解説した「4-2. TerraformからAWS環境にリソースを作成」と同様に、各コマンドを1つずつ実行し、実際にリソースを作成しましょう!
terraform init
terraform plan
terraform apply -auto-approve
実行後、以下のように出力されていれば無事にリソースの作成ができています。
※ 作成完了まで数分以上かかります
Plan: 8 to add, 1 to change, 0 to destroy.
module.aurora.random_password.DB_Password: Creating...
module.aurora.random_password.DB_Password: Creation complete after 0s [id=none]
module.aurora.aws_rds_cluster_parameter_group.default: Creating...
~~~省略~~~
Apply complete! Resources: 8 added, 1 changed, 0 destroyed.
念のため、AWSコンソールにアクセスし、作成できているか確認しておきましょう。
【RDS】https://ap-northeast-1.console.aws.amazon.com/rds/home?region=ap-northeast-1#databases:
- applyした内容で実際に作成されていることが確認できる
5.リソースを削除する
Terraformでリソースを作成してきました。必要がなければ、稼働している間の料金がかかってしまわないように、全て削除してしまいましょう。以下のコマンドを実行してください。
terraform destroy -auto-approve
実行後、次のように表示されていれば無事に削除できています!
Plan: 0 to add, 0 to change, 15 to destroy.
module.route_table_association_private_aurora_1a.aws_route_table_association.default: Destroying... [id=rtbassoc-xxxxxxxxxxxxxxxxx]
module.route_table_association_private_aurora_1c.aws_route_table_association.default: Destroying... [id=rtbassoc-xxxxxxxxxxxxxxxxx]
module.aurora.aws_ssm_parameter.Aurora_Username: Destroying... [id=/rds/rds-handson-aurora-cluster/username]
~~~省略~~~
Destroy complete! Resources: 15 destroyed.
あとがき
いかがでしたでしょうか?Terraformを使うとインフラをコードで管理することができ、構築と削除をコマンドからすばやく行うことができます!
また、Terraform Moduleを使うことで、必要なリソースを好きな分だけ、変数を渡して簡単にカスタマイズ & 再利用することができます。
以上、この記事が皆様のお役に立てれば幸いです。それでは。