
こんにちは。よっしーです(^^)
今日は、terraformを利用したRDSクラスタの作成についてご紹介します。
背景
AWS環境でTerraformを利用したRDSのクラスタを構築する機会がありましたので、その検証用として作成したときのtfファイルを備忘として残しました。
terraformコマンドの設定
下記のコマンドで terraform の使用するバージョンを指定します。
asdf local terraform 1.5.4
詳しい設定方法は下記の記事をご覧ください。
インフラ構成図
今回構築するインフラ構成は下図のようになります。

構成内容
今回作成するファイル構成は下記のようになります。
├── main.tf
├── output.tf
├── variables.tf
├── rds
│   ├── output.tf
│   ├── rds.tf
│   └── variables.tf
├── sg
│   ├── output.tf
│   ├── sg.tf
│   └── variables.tf
└── vpc
    ├── output.tf
    ├── variables.tf
    └── vpc.tf
main.tf
terraform {
  cloud {
    organization = "(自身の組織名)"
    workspaces {
      name = "learn-tfc-aws"
    }
  }
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 4.16"
    }
  }
  required_version = ">= 1.2.0"
}
provider "aws" {
  region = "ap-northeast-1"
}
# VPC
module "vpc" {
  source = "./vpc"
  name_prefix = var.name_prefix
  region      = var.region
  az_a        = var.az_a
  az_c        = var.az_c
  tag_name    = var.tag_name
  tag_group   = var.tag_group
}
# SG
module "sg" {
  source = "./sg"
  name_prefix = var.name_prefix
  region      = var.region
  az_a        = var.az_a
  az_c        = var.az_c
  tag_name    = var.tag_name
  tag_group   = var.tag_group
  vpc_id = module.vpc.vpc_id
}
# RDS
module "rds" {
  source = "./rds"
  name_prefix = var.name_prefix
  region      = var.region
  az_a        = var.az_a
  az_c        = var.az_c
  tag_name    = var.tag_name
  tag_group   = var.tag_group
  vpc_id      = module.vpc.vpc_id
  public_a_id = module.vpc.public_a_id
  public_c_id = module.vpc.public_c_id
  sg_id       = module.sg.sg_id
}
variables.tf
variable "region" {
  default = "ap-northeast-1"
}
variable "az_a" {
  default = "ap-northeast-1a"
}
variable "az_c" {
  default = "ap-northeast-1c"
}
variable "name_prefix" {
  default = "ltar" # Learn-Terraform-Aws-Rds
}
variable "tag_name" {
  default = "learn-terraform-aws-rds"
}
variable "tag_group" {
  default = "learn-terraform-aws"
}
output.tf
output "rds_endpoint" {
  value = module.rds.endpoint
}
vpc/vpc.tf
# VPC
resource "aws_vpc" "default" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_support   = true
  enable_dns_hostnames = true # DNSホスト名を有効化
  tags = {
    Name = "${var.tag_name}"
  }
}
# Subnet
resource "aws_subnet" "public_1a" {
  vpc_id            = aws_vpc.default.id
  cidr_block        = "10.0.0.0/20"
  availability_zone = var.az_a
  tags = {
    Name = "${var.tag_name}"
  }
}
resource "aws_subnet" "public_1c" {
  vpc_id            = aws_vpc.default.id
  cidr_block        = "10.0.16.0/20"
  availability_zone = var.az_c
  tags = {
    Name = "${var.tag_name}"
  }
}
# Internet Gateway
resource "aws_internet_gateway" "default" {
  vpc_id = aws_vpc.default.id
  tags = {
    Name = "${var.tag_name}"
  }
}
# Route table
resource "aws_route_table" "default" {
  vpc_id = aws_vpc.default.id
  tags = {
    Name = "${var.tag_name}"
  }
}
resource "aws_route" "default" {
  route_table_id         = aws_route_table.default.id
  gateway_id             = aws_internet_gateway.default.id
  destination_cidr_block = "0.0.0.0/0"
}
# SubnetとRoute tableの関連付け
resource "aws_route_table_association" "public_1a" {
  subnet_id      = aws_subnet.public_1a.id
  route_table_id = aws_route_table.default.id
}
resource "aws_route_table_association" "public_1c" {
  subnet_id      = aws_subnet.public_1c.id
  route_table_id = aws_route_table.default.id
}
vpc/variables.tf
variable "region"      {}
variable "az_a"        {}
variable "az_c"        {}
variable "name_prefix" {}
variable "tag_name"    {}
variable "tag_group"   {}
vpc/output.tf
output "vpc_id" {
    value = "${aws_vpc.default.id}"
}
output "public_a_id" {
    value = "${aws_subnet.public_1a.id}"
}
output "public_c_id" {
    value = "${aws_subnet.public_1c.id}"
}
sg/sg.tf
resource "aws_security_group" "default" {
  vpc_id = "${var.vpc_id}"
  tags = {
    Name = "${var.tag_name}"
  }
}
resource "aws_security_group_rule" "mysql" {
  type              = "ingress"
  from_port         = "3306"
  to_port           = "3306"
  protocol          = "tcp"
  cidr_blocks       = ["0.0.0.0/0"]
  security_group_id = aws_security_group.default.id
}
resource "aws_security_group_rule" "out_all_allow" {
  type              = "egress"
  from_port         = 0
  to_port           = 0
  protocol          = "all"
  cidr_blocks       = ["0.0.0.0/0"]
  security_group_id = aws_security_group.default.id
}
sg/variables.tf
variable "region"      {}
variable "az_a"        {}
variable "az_c"        {}
variable "name_prefix" {}
variable "tag_name"    {}
variable "tag_group"   {}
variable "vpc_id"      {}
sg/output.tf
output "sg_id" {
    value = "${aws_security_group.default.id}"
}
rds/rds.tf
locals {
  aurora_database_name   = "learn"
  aurora_master_username = "learn_user"
  aurora_master_password = "Passw0rd"
}
resource "aws_rds_cluster" "learn" {
  cluster_identifier              = "${var.name_prefix}-rds-cluster"
  engine                          = "aurora-mysql"
  engine_version                  = "8.0.mysql_aurora.3.02.0"
  database_name                   = local.aurora_database_name
  master_username                 = local.aurora_master_username
  master_password                 = local.aurora_master_password
  port                            = 3306
  vpc_security_group_ids          = ["${var.sg_id}"]
  db_subnet_group_name            = aws_db_subnet_group.learn.name
  db_cluster_parameter_group_name = aws_rds_cluster_parameter_group.learn.name
  skip_final_snapshot = true
  apply_immediately   = true
}
resource "aws_rds_cluster_parameter_group" "learn" {
  name   = "${var.name_prefix}-database-cluster-parameter-group"
  family = "aurora-mysql8.0"
  parameter {
    name         = "character_set_client"
    value        = "utf8mb4"
    apply_method = "immediate"
  }
  parameter {
    name         = "character_set_connection"
    value        = "utf8mb4"
    apply_method = "immediate"
  }
  parameter {
    name         = "character_set_database"
    value        = "utf8mb4"
    apply_method = "immediate"
  }
  parameter {
    name         = "character_set_filesystem"
    value        = "utf8mb4"
    apply_method = "immediate"
  }
  parameter {
    name         = "character_set_results"
    value        = "utf8mb4"
    apply_method = "immediate"
  }
  parameter {
    name         = "character_set_server"
    value        = "utf8mb4"
    apply_method = "immediate"
  }
  parameter {
    name         = "collation_connection"
    value        = "utf8mb4_general_ci"
    apply_method = "immediate"
  }
  parameter {
    name         = "collation_server"
    value        = "utf8mb4_general_ci"
    apply_method = "immediate"
  }
  parameter {
    name         = "time_zone"
    value        = "Asia/Tokyo"
    apply_method = "immediate"
  }
}
resource "aws_db_subnet_group" "learn" {
  name = "${var.name_prefix}-db-subnet"
  subnet_ids = ["${var.public_a_id}", "${var.public_c_id}"]
}
resource "aws_rds_cluster_instance" "learn" {
  count = 1
  cluster_identifier = aws_rds_cluster.learn.id
  identifier              = "${var.name_prefix}-rds-instance-${count.index}"
  engine                  = aws_rds_cluster.learn.engine
  engine_version          = aws_rds_cluster.learn.engine_version
  instance_class          = "db.t3.medium"
  db_subnet_group_name    = aws_db_subnet_group.learn.name
  db_parameter_group_name = aws_db_parameter_group.learn.name
  publicly_accessible = true
}
resource "aws_rds_cluster_instance" "learn-replica" {
  count = 2
  cluster_identifier = aws_rds_cluster.learn.id
  identifier              = "${var.name_prefix}-rds-instance-${count.index}-replica"
  engine                  = aws_rds_cluster.learn.engine
  engine_version          = aws_rds_cluster.learn.engine_version
  instance_class          = "db.t3.medium"
  db_subnet_group_name    = aws_db_subnet_group.learn.name
  db_parameter_group_name = aws_db_parameter_group.learn.name
  depends_on              = [aws_rds_cluster_instance.learn]
  publicly_accessible = true
}
resource "aws_db_parameter_group" "learn" {
  name   = "${var.name_prefix}-db-parameter-group"
  family = "aurora-mysql8.0"
}
rds/variables.tf
variable "region"      {}
variable "az_a"        {}
variable "az_c"        {}
variable "name_prefix" {}
variable "tag_name"    {}
variable "tag_group"   {}
variable "vpc_id"      {}
variable "public_a_id" {}
variable "public_c_id" {}
variable "sg_id"       {}
rds/output.tf
output "endpoint" {
  value = aws_rds_cluster.learn.endpoint
}
作成
下記のコマンドを実施します。
# Terraform Cloudへのログイン
terraform login
# プロジェクトの初期化
terraform init
# フォーマット
terraform fmt
# バリデーション
terraform validate
# AWSリソースの作成内容を確認
terraform plan
# AWSリソースの作成
terraform apply
# 現在の状態の確認
terraform show
# 出力値の確認
terraform output
下記のような出力になっていれば成功です。
Apply complete! Resources: 18 added, 0 changed, 0 destroyed.
Outputs:
rds_endpoint = "xxx.ap-northeast-1.rds.amazonaws.com"
動作確認
publicに配置しているため、下記のコマンドで接続可能です。
mysql -ulearn_user -p -h xxx.ap-northeast-1.rds.amazonaws.com
パスワードは、aurora_master_password の値を入力してください。
下図のようなログインができれば成功です。
/ # mysql -ulearn_user -p -h ltar-rds-cluster.cluster-cswrkwegsb5l.ap-northeast-1.rds.amazonaws.com
Enter password: 
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MySQL connection id is 95
Server version: 8.0.23 Source distribution
Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
MySQL [(none)]> 
show databasesを実行して、learnの存在確認ができれば、動作確認は完了になります。
MySQL [(none)]> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| learn              |
| mysql              |
| performance_schema |
| sys                |
+--------------------+
5 rows in set (0.010 sec)
補足
mysqlコマンドの実行は下記のdocker環境で実施しました。
├── mysql
│   └── Dockerfile
└── compose.yml
mysql/Dockerfile
FROM alpine
RUN apk update && \
    apk add mysql-client
compose.yml
version: '3.8'
services:
  mysql-client:
    build: ./mysql
    container_name: mysql-client
    profiles:
      - tools
    networks:
      - net
networks:
  net:
実行コマンド
# ビルド
docker compose --profile tools build
# mysqlクライアントのコンテナにログイン
docker compose --profile tools run -it --rm mysql-client sh
解説
rds/rds.tf
このTerraformコードは、Amazon Web Services (AWS) のインフラストラクチャを定義し、データベースクラスターを構築するためのものです。以下にコードのセクションごとの解説を提供します。
localsブロック: このブロックは、コード内で使用する変数を定義しています。これにより、後のリソース定義でこれらの変数を使用することができます。aurora_database_nameはデータベースの名前、aurora_master_usernameはマスターユーザー名、aurora_master_passwordはマスターユーザーのパスワードを表しています。aws_rds_clusterリソース: このリソースは、Amazon RDS データベースクラスターを定義しています。Aurora MySQL データベースクラスターを作成するための設定が含まれています。以下はいくつかの重要な設定です。cluster_identifier: データベースクラスターの一意の識別子。engine,engine_version: 使用するデータベースエンジンとそのバージョン。database_name,master_username,master_password: データベースの名前と、マスターユーザーの認証情報。port: データベースがリッスンするポート番号。vpc_security_group_ids: データベースへのアクセスを制御するセキュリティグループの ID。db_subnet_group_name: データベースが配置されるサブネットグループ。db_cluster_parameter_group_name: データベースクラスターのパラメータグループの名前。skip_final_snapshot,apply_immediately: データベースの削除時に最終スナップショットを作成せずに、変更を即座に適用するかどうか。
aws_rds_cluster_parameter_groupリソース: これはデータベースクラスター用のパラメータグループを定義しています。ここでは、データベースの文字セットや照合順序などのパラメータを設定しています。aws_db_subnet_groupリソース: データベースクラスターやそのインスタンスが配置されるサブネットグループを定義しています。aws_rds_cluster_instanceリソース: これはデータベースクラスター内のインスタンスを定義しています。このリソースはマスターインスタンスを1つ定義しており、publicly_accessibleパラメータを通じてパブリックアクセスを有効にしています。aws_rds_cluster_instanceリソース (replica インスタンス): こちらはデータベースクラスターのリードレプリカインスタンスを2つ定義しています。マスターインスタンスへの依存関係を設定しており、同様にpublicly_accessibleパラメータを通じてパブリックアクセスを有効にしています。aws_db_parameter_groupリソース: データベースインスタンスのパラメータグループを定義しています。
このコード全体は、Aurora MySQL データベースクラスターを構築し、それに関連するインスタンスとパラメータ設定を行うためのものです。Terraform を使用することで、コードで定義されたインフラストラクチャを簡単に作成、変更、管理することができます。
resource “aws_rds_cluster_instance” “learn”
このコードは、Amazon RDS Aurora MySQLデータベースクラスターのマスターインスタンスを定義するためのものです。こちらも詳細について解説します。
resource "aws_rds_cluster_instance" "learn":aws_rds_cluster_instanceリソースを作成し、名前が"learn"であることを示しています。マスターインスタンスを1つ作成するため、countパラメータを使って1と設定しています。count: インスタンスの数を指定しています。この例では1つのマスターインスタンスを作成します。cluster_identifier: マスターインスタンスが所属するデータベースクラスターの識別子です。既存のデータベースクラスターのID (aws_rds_cluster.learn.id) を使用しています。identifier: インスタンスの一意の識別子を定義しています。${var.name_prefix}-rds-instance-${count.index}というフォーマットで名前が設定されます。ここではcount.indexを使用して、複数のインスタンスが作成される場合にそれぞれにユニークな名前を付けるためのカウンターを利用しています。engine,engine_version: データベースエンジンとエンジンバージョンを既存のクラスターから取得して設定します。instance_class: インスタンスのクラス(サイズ)を指定しています。この例では “db.t3.medium” を使用しています。db_subnet_group_name: インスタンスが配置されるサブネットグループの名前です。aws_db_subnet_group.learn.nameから取得されます。db_parameter_group_name: インスタンスに適用するパラメータグループの名前です。aws_db_parameter_group.learn.nameから取得されます。publicly_accessible: インスタンスにパブリックアクセスを有効にするかどうかを指定します。
このコードセクションでは、データベースクラスターのマスターインスタンスを定義しています。このインスタンスはクラスターのコアとなり、データベースの読み書き操作を処理します。
resource “aws_rds_cluster_instance” “learn-replica”
depends_on: リソースの依存関係を指定しています。リードレプリカインスタンスの作成前に、マスターインスタンス (aws_rds_cluster_instance.learn) が作成されることを保証しています。
おわりに
今日は、terraformを利用したRDSクラスタの作成についてご紹介しました。public subnetに配置しているので、次回は、private subnetに配置したいと思います。

何か質問や相談があれば、コメントをお願いします。また、エンジニア案件の相談にも随時対応していますので、お気軽にお問い合わせください。
それでは、また明日お会いしましょう(^^)
  
  
  
  


コメント