RDS Proxyを作成して、lambdaで利用する

# 概要
RDS Proxyをセットアップする手順を記載する

# なぜRDS Proxyを使わないといけないか

https://ichi-station.com/aws-rds-proxy-lambda-aurora/

> LambdaからRDSへのアクセスはアンチパターンと言われています。（調べるまで知りませんでした）
> その理由は、Lambdaはリクエストごとに起動し、その都度RDSに対してコネクションを張ろうとするため、
> コネクション上限を超えたLambdaからのアクセスはエラーとなってしまうためです。
> Lambda起動の頻度が高いほどコネクションが多くなってしまうのですね。。。

lambdaが終了しても、コネクションが残ってしまい、接続数の上限に達してしまう

# 参考

### AWS

* RDS Proxyについて https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/rds-proxy.html
  * PostgreSQL関数lastvalを使用してはいけないw（Proxyを利用してるからしょうがない）
  * PostgreSQLの一部バージョンに対応していない
  * 10.10 and higher minor versions, and version 11.5 and higher minor versions
  * 12, 13系に未対応なので、気をつける（2021年10月27日）
  * https://stackoverflow.com/questions/67967745/rds-proxy-gives-empty-dropdown-for-database-when-using-rds-postgresql-13-2
* 使い方 https://aws.amazon.com/jp/blogs/news/using-amazon-rds-proxy-with-aws-lambda/

### RDS Proxyについての記事

* https://ichi-station.com/aws-rds-proxy-lambda-aurora/
* https://sebenkyo.com/2020/11/04/post-1590/
* https://qiita.com/curanosuke/items/9f17857efa24c138997f

### lambdaのコードをzipで圧縮してアップロード

* https://sig9.hatenablog.com/entry/2020/02/03/000000

# 前提

RDSにインスタンスとテーブルは作成されていること


### [検証用] RDSにインスタンスを作成し、テーブルの作成手順

※ 起動の手順は省略

MySQL

```
CREATE DATABASE stream_test;

USE stream_test;

CREATE TABLE `fruits` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL DEFAULT '',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

INSERT INTO `fruits` (name) VALUES ('orange'), ('apple');
```

PostgreSQL

```
CREATE DATABASE stream_test;

\c stream_test;

CREATE TABLE "fruits" (
  "id" SERIAL,
  "name" varchar(255) NOT NULL DEFAULT ''
);

INSERT INTO "fruits" (name) VALUES ('strawberry'), ('banana');
```


# 手順

1. シークレットマネージャーを登録する
1. RDS Proxyを登録する
1. Policyを作成する
1. lambdaを登録する
1. テストを実行する


# シークレットマネージャーを登録する

RDS Proxyで利用されるuserとpasswordをシークレットマネージャーに登録

* ユーザー名
* パスワード
* データベース


# RDS Proxyを登録する

* プロキシ識別子: 任意
* エンジンの互換性: 任意
* Transport Layer Security が必要: PostgreSQLの場合は必須
* データベース: 任意（PostgreSQLは、version 10 もしくは 11のみ対応）
* Secrets Manager シークレット: 前述のシークレット
* IAM ロール: IAMロールを作成
* IAM 認証: 無効（SSMを利用するので、無効）
* サブネット　: （変更しない）


# Policyを作成する

LambdaからRDS Proxyを参照できるPolicyを作成する

* タグ（任意）
  * キー: Name
  * 値 - オプション: 任意
* 名前: 任意（前述のタグと同じもの推奨）
* 説明: 任意( ex: `Secret manager available in lambda` )

JSON

```
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "secretsmanager:GetResourcePolicy",
                "secretsmanager:GetSecretValue",
                "secretsmanager:DescribeSecret",
                "secretsmanager:ListSecretVersionIds"
            ],
            "Resource": [
                "{secretのARN}"
            ]
        }
    ]
}
```


# lambdaを登録する

"一から作成" を選択

* 関数名: 任意
* ランタイム: Python3.8 （他の言語、バージョンでも良いが、今回利用するレイヤーが、3.8向けのものです）


### レイヤーの追加

Python3.8に対応したレイヤーを探して使う（2021年12月3日時点で、3.9向けのものは、このリポジトリでは準備されていない）
PostgreSQLを利用したので、 `psycopg2` を利用

今回は、こちらを参照

https://github.com/keithrozario/Klayers/blob/master/deployments/python3.8/arns/ap-northeast-1.csv

```
arn:aws:lambda:ap-northeast-1:770693421928:layer:Klayers-python38-aws-psycopg2:1
```

以下は他のサイトで見つけた `psycopg2`

```
arn:aws:lambda:ap-northeast-1:898466741470:layer:psycopg2-py38:1
```

### 設定

* 一般設定
  * タイムアウト: 30秒（任意です）

* IAMロール
  * ポリシーの追加
  * 前述で作成したPolcy
  * AWSLambdaVPCAccessExecutionRole

※ kinesisを利用する場合は、AmazonKinesisFullAccessを追加


* VPC
  * VPC: RDSのあるVPC
  * サブネット: 全て選択
  * セキュリティグループ: RDS Proxyと同じもの

* データベースプロキシ
  * 既存のデータベースプロキシの選択: 前述のRDS Proxy

* データベースプロキシ追加後、IAMロール
  * RDS ProxyのPolicyがあるか確認
  * ex) AWSLambdaRDSProxyExecutionRole-12345678-1234-1234-1234-123456789012

### lambda登録

```
import json
import os
import sys
import boto3
import psycopg2
import base64


# PostgreSQL Connection
def connect():
    ENDPOINT="{前述のRDS Proxyのエンドポイント}"
    PORT="5432"
    USR="{前述のRDS Proxyに登録したユーザ}"
    REGION="ap-northeast-1"
    DATABASE="{任意のデータベース名}"

    client = boto3.client('rds')
    token = client.generate_db_auth_token(DBHostname=ENDPOINT, Port=PORT, DBUsername=USR, Region=REGION)
    con = psycopg2.connect(host=ENDPOINT, database=DATABASE, user=USR, password=token)

    return con


# EXECUTE SELECT
def select_execute(con, sql, values):
    print(sql)
    with con.cursor() as cur:
        cur.execute(sql, values)
        records = cur.fetchall()

    return records


def lambda_handler(event, context):
    con = connect()

    sql = 'SELECT COUNT(1) AS count FROM fruits'
    records = select_execute(con, sql, [])

    return {
        'statusCode': 200,
        'body': f"calllog_id: {records[0][0]}"
    }

```


# テストを実行する

fruitsの個数が表示されれば、正常に完了です。

```
Test Event Name
test1

Response
{
  "statusCode": 200,
  "body": "fruits count: 2"
}

Function Logs
START RequestId: 6f6d853f-e213-41e4-a75c-fc6f1a927238 Version: $LATEST
SELECT COUNT(1) AS count FROM fruits
END RequestId: 6f6d853f-e213-41e4-a75c-fc6f1a927238
REPORT RequestId: 6f6d853f-e213-41e4-a75c-fc6f1a927238	Duration: 1437.65 ms	Billed Duration: 1438 ms	Memory Size: 128 MB	Max Memory Used: 74 MB	Init Duration: 330.97 ms

Request ID
6f6d853f-e213-41e4-a75c-fc6f1a927238

```