TerraformでAmazon Auroraクラスタを自動構築する(RDSプロキシ編)

# はじめに
Amazon Aurora記事第2弾。

[前回](https://qiita.com/neruneruo/items/a7c5f7fa80fbf9d6a828)に続き、今回は作成したAuroraクラスタにRDSプロキシを追加する。
RDSプロキシは、接続負荷を下げるためにLambdaからAuroraに接続するときは必須になる。一方で、常時接続するようなコネクションプールを持ったコンテナアプリケーションの場合は不要でありつつ、AutoScalingした際の管理の手間を省くには便利な機能だったりする(接続のオーバーヘッドがあるため、大量の処理を捌くようなワークロードには使いにくいと思われる)。

今回の記事では、前回記事で作ったTerraformのHCLに付け加える前提であるため、実際に触ってみる場合はまずは前回記事から読んでいただきたい。

# Secrets Manager
いきなりだが、RDSプロキシのユーザの接続管理にはSecrets Managerを使う。
これがないと構築ができないので、まずは作成しておこう。

```HCL
resource "aws_secretsmanager_secret" "dbproxy" {
  name = local.secret_manager_name

  recovery_window_in_days = 0
}

resource "aws_secretsmanager_secret_version" "dbproxy" {
  secret_id = aws_secretsmanager_secret.dbproxy.id

  secret_string = jsonencode({
    username : aws_rds_cluster.example.master_username
    password : aws_rds_cluster.example.master_password
    engine : aws_rds_cluster.example.engine
    host : aws_rds_cluster.example.endpoint
    port : aws_rds_cluster.example.port
    dbInstanceIdentifier : aws_rds_cluster.example.id
  })
}
```

```recovery_window_in_days``` については、設定しなくても良い(削除時に何日間残しておくかという設定)。
ただし、検証環境のように何度か作っては消して、とやるケースでは、設定していないと、再構築時にシークレットの名前が重複してエラーになってしまう。

もし、消せなくなってしまった場合は、[このページ](https://aws.amazon.com/jp/premiumsupport/knowledge-center/delete-secrets-manager-secret/)を参考にしつつ削除しておこう。デフォルトだと、削除予約中のシークレットはマネージメントコンソールでも参照できないので、ちょっと戸惑うことになる。

# IAM
さらに、RDSプロキシがSecrets ManagerにアクセスするためにIAMの設定が必要になる。
以下のような感じで設定しておこう。```resources```はもう少し真面目に設定しておいた方が良いかもしれない。

```HCL
resource "aws_iam_role" "dbproxy" {
  name               = local.iam_dbproxy_role_name
  assume_role_policy = data.aws_iam_policy_document.dbproxy_assume.json
}

data "aws_iam_policy_document" "dbproxy_assume" {
  statement {
    effect = "Allow"

    actions = [
      "sts:AssumeRole",
    ]

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

resource "aws_iam_role_policy" "dbproxy" {
  name   = local.iam_dbproxy_policy_name
  role   = aws_iam_role.dbproxy.id
  policy = data.aws_iam_policy_document.dbproxy_custom.json
}

data "aws_iam_policy_document" "dbproxy_custom" {
  statement {
    effect = "Allow"

    actions = [
      "secretsmanager:GetResourcePolicy",
      "secretsmanager:GetSecretValue",
      "secretsmanager:DescribeSecret",
      "secretsmanager:ListSecretVersionIds"
    ]

    resources = [
      "arn:aws:secretsmanager:*:*:*",
    ]
  }
  statement {
    effect = "Allow"

    actions = [
      "kms:Decrypt",
    ]

    resources = [
      "*",
    ]
  }
}
```

# Aurora
## RDSプロキシの基本設定
さて、あとはRDSプロキシの設定だけだ。
リソースは以下のものを使う。

- aws_db_proxy
- aws_db_proxy_default_target_group
- aws_db_proxy_target

```aws_db_proxy``` の```role_arn```には、↑で作成したロールのARNを設定しておこう。
また、```auth```の```secret_arn ```には、↑で作成したSecrets ManagerのARNを設定する。
vpc_security_group_idsはMySQLであればインバウンドの3306ポートが空いていれば良い。
```debug_logging```は、いったんtrueで構築するが、接続テストまで終わったらfalseにしよう。設定が間違っていた時等、CloudWatch Logsでエラー状況が確認できるので便利だ。このCloudWatch Logsは```terraform destroy```で自動削除はされない上に、デフォルトでは失効しない設定なので注意が必要だ。

ちなみに、[Terraformの公式ドキュメント](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/db_proxy_default_target_group)の通りに設定すると、MySQLのクライアントでSQL実行時にUnknown Errorが発生する。どうやら、```init_query```と```session_pinning_filters```が悪さをするようだ。ここは、ちゃんと理解をした上で適宜必要に応じて設定をしていこう。

```HCL
resource "aws_db_proxy" "example" {
  depends_on = [
    aws_rds_cluster_instance.example,
  ]

  name                   = local.rds_proxy_name
  debug_logging          = true
  engine_family          = "MYSQL"
  idle_client_timeout    = 60
  require_tls            = false
  role_arn               = aws_iam_role.dbproxy.arn
  vpc_security_group_ids = [aws_security_group.aurora.id]
  vpc_subnet_ids         = data.aws_subnet_ids.my_vpc.ids

  auth {
    auth_scheme = "SECRETS"
    iam_auth    = "DISABLED"
    secret_arn  = aws_secretsmanager_secret.dbproxy.arn
  }
}

resource "aws_db_proxy_default_target_group" "example" {
  db_proxy_name = aws_db_proxy.example.name

  connection_pool_config {
    connection_borrow_timeout    = 120
    max_connections_percent      = 100
    max_idle_connections_percent = 50
  }
}

resource "aws_db_proxy_target" "example" {
  db_proxy_name         = aws_db_proxy.example.name
  db_cluster_identifier = aws_rds_cluster.example.id
  target_group_name     = aws_db_proxy_default_target_group.example.name
}
```

これで、RDSプロキシが設定され接続可能になる。
RDSプロキシはVPC内からしかアクセスができないため、接続確認はEC2等を立てて確認しよう。

## ReadOnlyなエンドポイントの追加
さて、作成したプロキシエンドポイントをマネージメントコンソールで確認してみると、以下の通り、Writerインスタンスに対してもプロキシを作ってしまっていることが分かる。

![キャプチャ4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/b7a187e0-2e4d-9309-25ae-65ef6198c4bb.png)

あくまでも参照処理のみさせたい場合、これではよろしくないので、自分でエンドポイントを作成しよう。

```HCL
resource "aws_db_proxy_endpoint" "example" {
  db_proxy_name          = aws_db_proxy.example.name
  db_proxy_endpoint_name = "${local.rds_proxy_name}-ro"
  vpc_security_group_ids = [aws_security_group.aurora.id]
  vpc_subnet_ids         = data.aws_subnet_ids.my_vpc.ids
  target_role            = "READ_ONLY"
}
```

これまた[Terraform公式ドキュメント](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/db_proxy_endpoint)の罠なのだが、例には```vpc_security_group_ids```が設定されていないものの、これを設定していないと通信ができずにエラーになる。ちゃんと3306ポートを通しておくようにしよう。

![キャプチャ5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/0831aff7-7a42-af0b-6436-714f6ce02ae1.png)

試しに接続して```CREATE TABLE```してみると…

```
MySQL [COMPANY]> CREATE TABLE EMPLOYEE (
    ->   id CHAR(5) PRIMARY KEY,
    ->   name CHAR(20) NOT NULL,
    ->   age INTEGER
    -> );
ERROR 1290 (HY000): The MySQL server is running with the --read-only option so it cannot execute this statement
```

ちゃんとエラーになってくれた。
これで、ReadOnlyなプロキシエンドポイントから安全に接続できるようになったぞ！