作りながら覚えるTerraform入門(7) - RDS編

作りながら覚えるTerraform入門シリーズの第7回です。
今回はRDSをマルチAZ構成で構築していきます。DBエンジンはMySQL8.0で進めていきます。

##### 作りながら覚えるTerraform入門シリーズ

1. [インストールと初期設定](https://qiita.com/m-oka-system/items/6103bbb9f103db1fea0e)
1. [基本編](https://qiita.com/m-oka-system/items/983653f611fe305820c8)
1. [VPC編](https://qiita.com/m-oka-system/items/bba8d32d57142a870ce5)
1. [EC2編](https://qiita.com/m-oka-system/items/8280f748f88ac2e28ad7)
1. [Route53 + ACM編](https://qiita.com/m-oka-system/items/34f3ac3d98210d06dbc8)
1. [ELB編](https://qiita.com/m-oka-system/items/8882431042a1d01d0b7c)
1. **RDS編** => 今回はコチラ

今回の学習ポイントは・・・
変更を無視する`ignore_changes`という`lifecycle`が登場しますが、RDSを構築する上でのAWS観点でのポイントがいくつかあると思いますので、参考になれば嬉しいです！

# サブネットグループの作成

DBインスタンスを作成する前に○○グループを順番に作成していきましょう。
まずは、サブネットグループを作成します。
`rds.tf`を作成して、以下のコードを貼り付けます。

```terraform:rds.tf
################################
# RDS
################################
# Subnet group
resource "aws_db_subnet_group" "subnet" {
  name = "${var.prefix}-db-subnet"

  subnet_ids = [
    aws_subnet.private_subnet_1a.id,
    aws_subnet.private_subnet_1c.id
  ]
}
```

これはプライベートサブネットのIDを指定しているだけなのでシンプルですね。

# パラメータグループの作成

続いて、パラメータグループを作成します。
`rds.tf`に以下のコードを追加します。

```terraform:rds.tf
# Parameter group
resource "aws_db_parameter_group" "mysql" {
  name   = "${var.prefix}-parameter-group"
  family = "mysql8.0"

  parameter {
    name         = "general_log"
    value        = "1"
    apply_method = "immediate"
  }

  parameter {
    name         = "slow_query_log"
    value        = "1"
    apply_method = "immediate"
  }

  parameter {
    name         = "long_query_time"
    value        = "0"
    apply_method = "immediate"
  }

  parameter {
    name         = "log_output"
    value        = "FILE"
    apply_method = "immediate"
  }
}
```

パラメータグループ自体の作成はファミリーを指定するくらいなので難しくありませんが、デフォルトのパラメータのままだとエラーログ以外が出力されないので、一部パラメータを修正しています。

| パラメータ      | 説明                                     |
| --------------- | ---------------------------------------- |
| general_log     | 一般クエリログの出力                          |
| slow_query_log  | スロークエリログの出力                   |
| long_query_time | スロークエリログを出力する秒数のしきい値　　 |
| log_output      | 出力の出力先                             |

`general_log`と`slow_query_log`は「1」を設定してログが出力されるようにしています。

`long_query_time`は「0秒」にすることで、重たいクエリを実行せずともログが出力されることを確認できるようにしています。本来は、時間のかかっているクエリを特定するために出力するログですが、ここでは単に「ログが出力されること」の動作確認を行うことを目的としています。

`log_output`は「FILE」に変更しています。デフォルトは「TABLE」に書き込まれます。
「TABLE」のままであってもコンソール画面からであれば一般クエリログ、スロークエリログ、エラーログを確認できますが、RDS作成時に設定するCloudWatch Logsへのエクスポートができるよう「FILE」に変更しています（TABLEのままだとエクスポートを有効化してもCloudWatch Logsへ転送されません）

整理すると、

- すべてのパラメータがデフォルトのままでもエラーログだけはコンソール画面から確認できる
- `general_log` `slow_query_log`を「1」にするとそれぞれのログもコンソール画面から確認できる
- `log_output`をFILEにするとテーブルではなくファイルにログが書き込みされる
- RDSのログエクスポートを有効にするとファイルとして出力されたログがCloudWatch Logsに転送される

ということになります。
このあたりTerraformあまり関係ありませんが、少しわかりにくいと感じたので補足させて頂きました。
それぞれのパラメータの`apply_method`はデフォルトが`immediate`「すぐに適用」なので省略しても構いません。

# オプショングループの作成

次は、オプショングループを作成します。
`rds.tf`に以下のコードを追加します。

```terraform:rds.tf
# Option group
resource "aws_db_option_group" "mysql" {
  name                 = "${var.prefix}-option-group"
  engine_name          = "mysql"
  major_engine_version = "8.0"

  option {
    option_name                    = "MEMCACHED"
    port                           = "11211"
    vpc_security_group_memberships = [aws_security_group.rds_sg.id]

    option_settings {
      name  = "BACKLOG_QUEUE_LIMIT"
      value = "1024"
    }

    option_settings {
      name  = "BINDING_PROTOCOL"
      value = "auto"
    }
  }
}
```

こちらもオプショングループ自体の作成はDBエンジン、バージョンを指定するくらいなので難しくありませんが、オプションを追加する場合のサンプルとして「memcached」を追加しています。これ自体に意味はありませんし接続確認もしません。

`option_settings{}`の中は省略してもデフォルト値が採用されるので省略できますが、2つだけサンプルで記載しています。コンソール画面で追加する場合の以下の画面のオプション設定に該当します。

![7E51F911-F3FC-4151-9107-DD53F69F7C24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/402286/9d88adc9-25dd-bec3-cea6-5a10e55b1f96.png)

# モニタリングロールの作成

次に、モニタリングロールを作成します。
モニタリングロールはRDSの「拡張モニタリング」を有効にする場合に必要なIAMロールで、RDS内部のCPU使用率などのメトリクスをCloudWatch Logsに転送するために利用されます。

コンソール画面から作成した場合、「デフォルト」を選択すると「rds-monitoring-role」の名前で自動的にIAMロールが作成されるので、すでに存在しているかもしれません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/402286/9b17475d-8554-3e2e-4a75-896ec5319cae.png)

ここでは「rds-enhanced-monitoring-role」の名前で新規に作成します。
`iam.tf`に以下のコードを追加します。

```terraform:iam.tf
data "aws_iam_policy" "rds_monitoring_role" {
  arn = "arn:aws:iam::aws:policy/service-role/AmazonRDSEnhancedMonitoringRole"
}

data "aws_iam_policy_document" "rds_monitoring_role" {
  statement {
    actions = ["sts:AssumeRole"]

    principals {
      type        = "Service"
      identifiers = ["monitoring.rds.amazonaws.com"]
    }
  }
}

resource "aws_iam_role" "rds_monitoring_role" {
  name               = "rds-enhanced-monitoring-role"
  assume_role_policy = data.aws_iam_policy_document.rds_monitoring_role.json
}

resource "aws_iam_role_policy_attachment" "rds_monitoring_role" {
  role       = aws_iam_role.rds_monitoring_role.name
  policy_arn = data.aws_iam_policy.rds_monitoring_role.arn
}
```

やっていることは[EC2編](https://qiita.com/m-oka-system/items/8280f748f88ac2e28ad7)の「IAMロールの作成」と変わりありません。「AmazonRDSEnhancedMonitoringRole」の管理ポリシーをデータソースを使って参照しアタッチしています。

# DBインスタンスの作成

最後に、DBインスタンスを作成します。
`rds.tf`に以下のコードを追加します。

```terraform:rds.tf
# DB Instance
resource "aws_db_instance" "mysql" {
  engine                                = "mysql"
  engine_version                        = "8.0.20"
  license_model                         = "general-public-license"
  identifier                            = "${var.prefix}-db-instance"
  username                              = "admin"
  password                              = "password"
  instance_class                        = "db.t3.medium"
  storage_type                          = "gp2"
  allocated_storage                     = 20
  max_allocated_storage                 = 100
  multi_az                              = true
  db_subnet_group_name                  = aws_db_subnet_group.subnet.name
  publicly_accessible                   = false
  vpc_security_group_ids                = [aws_security_group.rds_sg.id]
  port                                  = 3306
  iam_database_authentication_enabled   = false
  name                                  = "cloud"
  parameter_group_name                  = aws_db_parameter_group.mysql.name
  option_group_name                     = aws_db_option_group.mysql.name
  backup_retention_period               = 7
  backup_window                         = "19:00-20:00"
  copy_tags_to_snapshot                 = true
  storage_encrypted                     = true
  performance_insights_enabled          = true
  performance_insights_retention_period = 7
  monitoring_interval                   = 60
  monitoring_role_arn                   = aws_iam_role.rds_monitoring_role.arn
  enabled_cloudwatch_logs_exports       = ["error", "general", "slowquery"]
  auto_minor_version_upgrade            = false
  maintenance_window                    = "Sat:20:00-Sat:21:00"
  deletion_protection                   = false
  skip_final_snapshot                   = true
  apply_immediately                     = false

  tags = {
    Name = "${var.prefix}-db-instance"
  }

  lifecycle {
    ignore_changes = [password]
  }
}

output "rds_endpoint" {
  description = "The connection endpoint in address:port format."
  value       = aws_db_instance.mysql.endpoint
}
```

DBインスタンスはパラメータが多いですが、コンソール画面から作成する場合の並び順に合わせて記述していますので、画面と見比べながら読んでいくとそれほど難しくはないと思います。

`username` `password`をべた書きしていますが、変数化して`terraform.tfvars`を参照させたほうが良いです。ただ、コードから隠蔽しても`terraform.tfstate`には平文で書き込まれてしまいますので、仮のパスワードを設定しておいてあとで変更するのが良さそうです。`lifecycle`ブロックの`ignore_changes`では、Terraform管理下から`password`を除外して、パスワードが変更されても差分検知されないようにしています。

`instance_class`は`t3.medium`にしています。
パフォーマンスインサイトを有効にするには一定サイズ以上にする必要があるので`t3.medium`を選択しています。

`multi_az = true`を指定してマルチAZ構成にしています。
シングル構成にする場合は`false`にして、`availability_zone`でAZを指定できます。

```terraform
multi_az          = false
availability_zone = "ap-northeast-1a"
```

`enabled_cloudwatch_logs_exports`では「エラーログ」「一般クエリログ」「スロークエリログ」をCloudWatch Logsにエクスポートするようにしています。繰り返しになりますが、前述のパラメータグループでログ出力を有効化し、かつ、`log_output`を「FILE」にしておく必要があります。

`skip_final_snapshot`はDBインスタンスを削除する時にスナップショットを取得するかどうかの設定です。もし`true`にして取得する場合は`final_snapshot_identifier`のパラメータも合わせて指定する必要があります。

`output`ブロックではDBインスタンスのエンドポイントを出力させています。

今回の構成では本番環境を意識して、極力、RDSで利用できる機能を網羅できるような形にしています。（本来は削除保護も有効にすべきですが）**マルチAZでかつ、db.t3.mediumにしているのでやや高額になりますので注意しましょう。**

`db.t3.micro`で十分な場合はパフォーマンスインサイトを無効にすればOKですので、以下のように修正してください。

```terraform:rds.tf
# サイズはt3.micro
instance_class    = "db.t3.micro"

# シングルAZ構成
multi_az          = false
availability_zone = "ap-northeast-1a"

# パフォーマンスインサイトは無効（コメントアウト or 削除）
# performance_insights_enabled          = true
# performance_insights_retention_period = 7
```

それでは、`terraform apply`を実行して、もろもろ作成されていることを確認しましょう。
作成できたら、EC2から`mysql`コマンドで接続できることを確認します。

```bash
mysql -h cloud02-db-instance.cnio2sb0qm8v.ap-northeast-1.rds.amazonaws.com -u admin -p
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 23
Server version: 8.0.20 Source distribution

Copyright (c) 2000, 2021, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| cloud              |
| information_schema |
| innodb_memcache    |
| mysql              |
| performance_schema |
| test               |
+--------------------+
6 rows in set (0.00 sec)

mysql> exit
```

また、CloudWatchのロググループを開いて、ログが保存されていることを確認しましょう。
`/aws/rds/instance/<db_instance_identifier>/<log_type>`が各種ログのエクスポートで、`RDSOSMetrics`がCPU使用率などの拡張モニタリングのログになります。

![E67887BA-BBC4-4713-805B-B243454EB28A.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/402286/32fe8dd9-2562-7613-e079-a7c1ed23a5c6.png)


今回は以上です。
これまで作成したリソースは不要になった時点で`destory`しておきましょう。くれぐれも高額なRDSを起動しっぱなしにしないようにだけご注意ください。CloudWatchのロググループは自動的に削除されないので手動で削除してください。

```bash
terraform destroy -auto-approve
```

# まとめ

作りながら覚えるTerraform入門シリーズはひとまず終了ですが、また少し追記するかもしれません。
Terraformには [Modules](https://www.terraform.io/docs/language/modules/index.html) や [Workspaces](https://www.terraform.io/docs/language/state/workspaces.html) など、まだまだ便利な機能が他にもありますが、今回使用した機能だけでも十分にInfrastructure as Code（IaC）を体験できると思います。

まだまだ学習中の身ですが、悩める初学者の皆さんの参考になれば幸いです。

# 参考リンク

- [RDSのMySQL/MariaDBでログをCloudWatch Logsへ出力可能になりました](https://dev.classmethod.jp/articles/rds-mysql-mariadb-export-cloudwatch-logs/)
- [拡張モニタリングの使用](https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/UserGuide/USER_Monitoring.OS.html)
- [ignore_changes](https://www.terraform.io/docs/language/meta-arguments/lifecycle.html#ignore_changes)