TerraformでAurora MySQLを構築し、RDS Data APIを使用する。

# はじめに
皆様お疲れ様です！
株式会社TechoesインフラチームのTです！

今回は9/26にAmazon Aurora MySQL で RDS Data API のサポートを開始したようなので早速Terraformを使用して実践してみようと思います。
# 実行環境
- Windows : 11 Home 23H2
- Terraform : v1.9.7
- aws cli : 2.11.22
# RDS Data APIについて
### 概要
RDS Data APIを利用することでHTTPSエンドポイントを介してRDS,Auroraに接続やDBドライバを管理することなくSQLの実行が可能になる。
接続時にSecret Managaerの情報を使用するため、認証情報を渡す必要がない

### 料金
最初の10億件のリクエストは100万回あたり0.42USD、10 億件を超えた場合は0.24USD
### 制約
- サポートしているエンジンが限定的
  　現時点で Aurora Mysqlと Aurora PostgreSQLのみをサポート
- サポートしているインスタンスクラスが限定的
　  tクラス(db.t3など)がサポート外
# Terraformについて
 HashiCorp社によって開発されたIacツールでAWSやGCPなどのクラウドサービスをコードによって作成、管理できるツール。
# 事前準備
※ IAMユーザー,アクセスキーは発行済みとします。
※ AWS CLIはインストール済みとします。
### Terraformのインストール
① 下記のリンクからファイルをダウンロード、解凍しましょう。

https://developer.hashicorp.com/terraform/install

② 解凍完了後にデフォルトでPATHの通っているC:\Windows 配下にterraform.exeを配置

③ powershellを起動しterraform -v で確認
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3492991/715c7004-5b5d-22d5-c2d9-2552b880897d.png)

### Terraform state用のS3バケットを作成とcloudshellのGIP確認
① AWSマネジメントコンソール上でS3バケットを作成

② CloudShell上で下記コマンドを実行
~~~
curl http://checkip.amazonaws.com/
~~~
※ バケット名、GIPは後々使うので控えておきましょう
### AWSアクセスキーの設定
今回はTerraformをローカル環境で実行するためアクセスキーの設定を行います。

① aws configureコマンドを実行して発行したアクセスキーを設定

② aws s3 lsコマンドを使用して先ほど作成したバケット名が取得できていることを確認
# 実践

### Terraformで環境構築
テンプレートは下記のものを使用します。

enviroments
バケット名とGIPは事前準備で取得したものを入力します。
```terraform:main.tf
terraform {
  required_version = ">= 1.9.7"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = ">= 5.70.0"
    }
  }

  backend "s3" {
    bucket = "*****" #バケット名
    key    = "infra.tfstate"       #パス
    region = "ap-northeast-1"
  }
}

provider "aws" {
  region = "ap-northeast-1"
  default_tags {
    tags = {
      Project     = "RDS-DataAPI"
      Environment = local.env
    }
  }
}

locals {
  env                        = "test"
  aws_region                 = "ap-northeast-1"
  sysname                    = "rds-api-test"
  vpc_cidr_block             = "172.20.0.0/16"
  private_subnet_cidr_block1 = "172.20.1.0/24"
  private_subnet_cidr_block2 = "172.20.2.0/24"
  cloudshell_gip             = "***.***.***.***/32"
  aws_az1                    = "ap-northeast-1a"
  aws_az2                    = "ap-northeast-1c"
  instance_class             = "db.r6g.large"
  db_name                    = "test_db"
  engine                     = "aurora-mysql"
  engine_version             = "8.0.mysql_aurora.3.07.0"
}

module "resources" {
  source                     = "../../modules"
  sysname                    = local.sysname
  env                        = local.env
  vpc_cidr_block             = local.vpc_cidr_block
  private_subnet_cidr_block1 = local.private_subnet_cidr_block1
  private_subnet_cidr_block2 = local.private_subnet_cidr_block2
  aws_az1                    = local.aws_az1
  aws_az2                    = local.aws_az2
  instance_class             = local.instance_class
  cloudshell_gip             = local.cloudshell_gip
  db_name                    = local.db_name
  engine                     = local.engine
  engine_version             = local.engine_version
}
```
module
```terraform:main.tf
# -----------------------------------------------------------------------------
# VPC
# -----------------------------------------------------------------------------
resource "aws_vpc" "vpc" {
  cidr_block = var.vpc_cidr_block
}

# -----------------------------------------------------------------------------
# Subnet
# -----------------------------------------------------------------------------
resource "aws_subnet" "private-subnet-1" {
  vpc_id            = aws_vpc.vpc.id
  cidr_block        = var.private_subnet_cidr_block1
  availability_zone = var.aws_az1
}

resource "aws_subnet" "private-subnet-2" {
  vpc_id            = aws_vpc.vpc.id
  cidr_block        = var.private_subnet_cidr_block2
  availability_zone = var.aws_az2
}

# -----------------------------------------------------------------------------
# Subnet Group
# -----------------------------------------------------------------------------
resource "aws_db_subnet_group" "database_sg_group" {
  name       = "${var.sysname}-database-subnet-group"
  subnet_ids = [aws_subnet.private-subnet-1.id, aws_subnet.private-subnet-2.id]
}

# -----------------------------------------------------------------------------
# Security Group
# -----------------------------------------------------------------------------
resource "aws_security_group" "database_sg" {
  name   = "${var.sysname}-database-sg"
  vpc_id = aws_vpc.vpc.id

  egress {
    from_port   = 3306
    to_port     = 3306
    protocol    = "tcp"
    cidr_blocks = [var.cloudshell_gip]
  }
}

# -----------------------------------------------------------------------------
# Secrets Manager
# -----------------------------------------------------------------------------
resource "aws_secretsmanager_secret" "aurora_credentials" {
  name = "aurora-db-secret"
}

resource "random_password" "aurora_password" {
  length           = 41
  special          = true
  override_special = "!()*+,-.;<=>?[]^_{|}~"
  min_lower        = 1
  min_numeric      = 1
  min_special      = 1
  min_upper        = 1
}

resource "aws_secretsmanager_secret_version" "aurora_credentials_version" {
  secret_id = aws_secretsmanager_secret.aurora_credentials.id
  secret_string = jsonencode({
    username              = "root",
    password              = random_password.aurora_password.result,
    port                  = "3306",
    dbname                = var.db_name
    authentication_plugin = "caching_sha2_password"
  })
}

data "aws_secretsmanager_secret" "data_aurora_credentials" {
  arn = aws_secretsmanager_secret.aurora_credentials.arn
}

data "aws_secretsmanager_secret_version" "data_aurora_credentials_version" {
  secret_id = data.aws_secretsmanager_secret.data_aurora_credentials.id
}

locals {
  secret_data = jsondecode(data.aws_secretsmanager_secret_version.data_aurora_credentials_version.secret_string)
}

# -----------------------------------------------------------------------------
# RDS
# -----------------------------------------------------------------------------

resource "aws_rds_cluster_parameter_group" "parameter_group" {
  name   = "${var.sysname}-database-cluster-parameter-group"
  family = "aurora-mysql8.0"

  parameter {
    name  = "time_zone"
    value = "Asia/Tokyo"
  }
}
resource "aws_rds_cluster" "rds_cluster" {
  depends_on             = [aws_secretsmanager_secret_version.aurora_credentials_version]
  cluster_identifier     = "${var.sysname}-cluster"
  db_subnet_group_name   = aws_db_subnet_group.database_sg_group.name
  vpc_security_group_ids = [aws_security_group.database_sg.id]
  availability_zones     = [var.aws_az1, var.aws_az2]
  engine                 = var.engine
  engine_version         = var.engine_version
  database_name          = local.secret_data.dbname
  master_username        = local.secret_data.username
  master_password        = local.secret_data.password
  port                   = "3306"
  enable_http_endpoint   = true

  skip_final_snapshot             = true
  db_cluster_parameter_group_name = aws_rds_cluster_parameter_group.parameter_group.name

  lifecycle {
    ignore_changes = [availability_zones]
  }
}

resource "aws_rds_cluster_instance" "rds_instance" {
  identifier         = "${var.sysname}-instance"
  cluster_identifier = aws_rds_cluster.rds_cluster.id

  engine         = aws_rds_cluster.rds_cluster.engine
  engine_version = aws_rds_cluster.rds_cluster.engine_version

  instance_class       = var.instance_class
  db_subnet_group_name = aws_rds_cluster.rds_cluster.db_subnet_group_name
}
```
下記の記述がRDS Data APIを有効にするものです
```
  enable_http_endpoint   = true
```
Terraform init と Terraform apply を実行して環境作成
### RDS Data APIを使用してアクセス
環境を作成したのでさっそくCloudShellからアクセスしてみましょう。

:::note info
接続にClusterのarnとSecretManagerのarnが必要になるのでコンソール上で確認しておきましょう。
:::
#### Databaseの作成
```
aws rds-data execute-statement \
    --resource-arn <クラスターARN> \
    --secret-arn <シークレットARN> \
    --sql "CREATE DATABASE <データベース名>"
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3492991/82394277-b2a9-2647-01fe-62fb9cb7e370.png)

#### テーブルの作成
```
aws rds-data execute-statement \
    --resource-arn <クラスターARN> \
    --secret-arn <シークレットARN> \
    --database <データベース名> \
    --sql "CREATE TABLE test_table (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50));"
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3492991/6beef1b2-a494-f840-8f77-a29b2013e208.png)

#### データの挿入
```
aws rds-data execute-statement \
    --resource-arn <クラスターARN> \
    --secret-arn <シークレットARN> \
    --database <データベース名> \
    --sql "INSERT INTO test_table (name) VALUES ('test1'), ('test2');"
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3492991/61477535-1996-8b67-0397-af236dcc7124.png)

#### データの取得
```
aws rds-data execute-statement \
    --resource-arn <クラスターARN> \
    --secret-arn <シークレットARN> \
    --database <データベース名> \
    --sql "SELECT * FROM test_table;"
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3492991/05e29d30-8df0-1b60-1269-735ee7fefaf9.png)

# 所感
今回はTerraformでの環境構築でRDS Data APIを利用してみました。RDS Data APIを使用すればDBクライアントのインストールも必要ないのでデータベースへのアクセスがかなりシンプルになると思います。料金もリクエスト数によって課金されるので新規環境を作成する際は選択肢のひとつとして念頭に置いておきたいと思います。
# 参考記事

https://www.cloudbuilders.jp/articles/4907/