RDS Proxy を使って、AWS Lambda から接続してみた

# はじめに

AWS Lambda が良く語られますが、予測不可能なリクエストが来た時に、多くのインスタンスを横に並べてリクエストを捌く構成があります。AWS Lambda の場合は、1リクエスト1インスタンスとなるので、需要が高まったときには必然的に多くのインスタンスが立ち上がる構成になります。この時、インスタンス が RDS のコネクションを取得している場合、多くのデータベースコネクションを取得することにより、データベース側の負荷が高まってしまう課題がありました。

こういった問題を解決するための選択肢の一つとして、RDS Proxy と呼ばれるデータベースのコネクションをプールしてくれる機能があります。Amazon RDS に備わっている機能となっており、複数のインスタンス間でデータベースコネクションをプールしてくれます。

RDS Proxy を利用するメリットは次の通りです。

- アプリケーションのスケーラビリティ
- データベースのフェールオーバーに伴う、アプリケーション側のフェールオーバーにかかわる時間の短縮
- AWS IAM 認証により、クレデンシャル情報を Secret Manager に持ち、セキュリティの向上に寄与

https://aws.amazon.com/jp/rds/aurora/faqs/



今回は、RDS Proxy を構成してみて、AWS Lambda から接続する方法を確かめてみましょう。



# Aurora MySQL

Aurora MySQL 2.08 クラスターを既に稼働している環境を利用していきます。Writer Instance が 1 台、Reader Instance が 1 台存在しています。

![image-20220226172239143.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/ec71d893-1387-aed2-cfca-de8f704e2910.png)



# Secret Manager で Secret の作成

RDS Proxy を作成する際に、Secret Manager でデータベースへクレデンシャル情報を定義する必要があります。Secret Manager のページを開いて、Store a new secret を押します

![image-20220226181533963.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/184a9450-e746-1ddf-f4f7-bb1693c04262.png)



User name や Password を指定します

![image-20220226181637740.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/2b497ff0-f787-9661-05e9-7b3b6e3d601d.png)



対象の Database を選択して Next を押します

![image-20220226181715450.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/23c778bb-e63b-d012-3835-1015e6eef078.png)



適当に名前を入れて Next を押します

![image-20220226181802490.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/b942b65c-f43b-cebf-0122-e08094a12191.png)



ローテーションは OFF にしておきます

![image-20220226181824470.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/ea3f9d80-7f9b-9627-94b7-838142d640f3.png)



Next

![image-20220226181834044.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/a9ade9c0-e8cc-59c3-55ec-7f598fb52489.png)



Store を押して作成します

![image-20220226181849486.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/57162ac1-b7df-9e9d-5657-37985dbffb02.png)



作成されました

![image-20220226181914171.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/a8f57a97-1ea2-269a-ac06-67e0b2c52501.png)





# RDS Proxy の作成

RDS のページで、Proxies のメニューを選んだ後、Create proxy を押します。

![image-20220226173211816.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/aa591414-098e-2bf7-3c73-d5d19ede19bf.png)



いくつかのパラメータを指定します

- RDS Proxy の名前
- RDS Engine 
- RDS Proxy でプーリングしているコネクションを close するまでの期間を指定。アプリケーションが何もクエリーを発行しない idle 時間が 30 分を過ぎると close するように指定している。
  - 詳細は以下のドキュメントに記載
  - https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/AuroraUserGuide/rds-proxy-setup.html#rds-proxy-creating

![image-20220226173423905.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/f8c3057a-34a5-d09a-1f70-9756750e59e1.png)



いくつかのパラメータを指定します

- RDS Proxy が連携するデータベースを指定
- RDS Proxy がプーリングするコネクションの割合を指定
- RDS Proxy としての  Reader Endpoint を有効化

![image-20220226173752810.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/34fed595-3545-9822-1c96-91c42dc7f098.png)



RDS Proxy では、データベースへアクセスするユーザーアカウントを Secret Manager で用意する必要があります。先ほど作成した Secret を選択します。

![image-20220226182243527.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/4bdf7b50-3730-6d60-d32b-fa5a0781ac07.png)



Security Group を指定したのちに、Create proxy を押します

![image-20220226182307913.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/68d1f4ec-ff57-5473-c24d-d0b641c09f71.png)



RDS Proxy が Creating となりました

![image-20220226182336535.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/313c97e4-a7fd-019a-3c42-4ddb813d4f43.png)



一定時間後、Available に変わりました

![image-20220226182923199.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/607317c7-dd4d-cbb2-5d1a-191285337758.png)



# RDS Proxy の Endpoint を確認

RDS Proxy の詳細画面を開くと、RDS Proxy としての Endpoint を確認できます。

![image-20220226183318985.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/f86b6047-aa21-fcd7-9666-eda5c06e57c1.png)



# MySQL Client から RDS Proxy を経由して接続

RDS Proxy が作成できたので、接続確認のために、MySQL Client から接続をしてみます。

```
mysql -u admin -h rds-proxy-test01.proxy-chuxmuzmrpgx.ap-northeast-1.rds.amazonaws.com -p
```



SELECT は問題なくできます。

```
mysql> SELECT * FROM sugi01.sample;
+----+-------+
| id | value |
+----+-------+
|  1 |     1 |
|  2 |     2 |
|  3 |     3 |
|  4 |     4 |
|  5 |     5 |
|  6 |     6 |
|  7 |     7 |
|  8 |     8 |
|  9 |     9 |
| 10 |    10 |
+----+-------+
10 rows in set (0.01 sec)
```



INSERT も OK です。

```
mysql> INSERT INTO sugi01.sample VALUES(11, 11);
Query OK, 1 row affected (0.02 sec)

mysql> SELECT * FROM sugi01.sample;
+----+-------+
| id | value |
+----+-------+
|  1 |     1 |
|  2 |     2 |
|  3 |     3 |
|  4 |     4 |
|  5 |     5 |
|  6 |     6 |
|  7 |     7 |
|  8 |     8 |
|  9 |     9 |
| 10 |    10 |
| 11 |    11 |
+----+-------+
11 rows in set (0.01 sec)

mysql>
```



ReadOnly の Endpoint にアクセスしてみます

```
mysql -u admin -h rds-proxy-test01-read-only.endpoint.proxy-chuxmuzmrpgx.ap-northeast-1.rds.amazonaws.com -p
```



INSERT は想定通りエラーになりました

```
mysql> INSERT INTO sugi01.sample VALUES(11, 11);
ERROR 1290 (HY000): The MySQL server is running with the --read-only option so it cannot execute this statement
mysql>
```





# Lambda Function を作成

次は、Lambda Function から RDS Proxy に接続する方法を確認していきましょう。今回は、SAM を使って Lambda Function を Deploy していきます。



## SAM Project 作成

SAM Project を作成していきます。今回の記事の本筋ではないため、SAM の説明は省略していきます。

SAM Project を作成して、Hello World としての動作確認を行い、問題ない場合は一旦 Deploy を行います。

```
sam init
cd hello-rdsproxy
sam build
sam local invoke
sam deploy --guided
```





## MySQL を Docker で動かす

ローカルで MySQL を Docker を使って動かします。動作確認用です。

```
docker run \
--name mydb \
-itd \
--hostname my-mysql \
-e MYSQL_ROOT_PASSWORD=mypassword \
-e BIND-ADDRESS=0.0.0.0 \
-p 3306:3306 \
public.ecr.aws/docker/library/mysql:8.0
```



MySQL に接続して、サンプルデータを格納します。

```
mysql -u root -h 127.0.0.1 --port=3306 -pmypassword

CREATE DATABASE sugi01;
USE sugi01;

CREATE TABLE sample2(
  id INT(11) NOT NULL AUTO_INCREMENT,
  name VARCHAR(10) NOT NULL,
  PRIMARY KEY (id)
);

INSERT INTO sample2(name) VALUES ("MySQL Data");
```



## Python の動作確認 in Local

Function の環境変数を定義していきます。`samconfig.toml` に、次の値を追記します。

- `default.deploy.parameters` : RDS Proxy に接続するための Endpoint や Username などを入力
- `default.local_invoke.parameters` : Docker でうごかしている  MySQL に接続するための情報を入力。IP アドレスは、Linux マシンの IP アドレスを指定

```toml
[default.deploy.parameters]
<省略>
parameter_overrides = "MysqlHostnameKey=rds-proxy-test01.proxy-chuxmuzmrpgx.ap-northeast-1.rds.amazonaws.com MysqlPortKey=3306 MysqlUsernameKey=admin MysqlPasswordKey=secret"

[default.local_invoke.parameters]
parameter_overrides = "MysqlHostnameKey=10.0.0.163 MysqlPortKey=3306 MysqlUsernameKey=root MysqlPasswordKey=mypassword"
```



Lambda Function に環境編素を渡すための設定を、 `template.yaml` に追記します。

```yaml
Parameters:
  MysqlHostnameKey:
    Type: String
    Default: default1
  MysqlPortKey:
    Type: String
    Default: default2
  MysqlUsernameKey:
    Type: String
    Default: default3
  MysqlPasswordKey:
    Type: String
    Default: default4

Globals:
  Function:
    Timeout: 10
    MemorySize: 1024
    Environment:
      Variables:
        MYSQL_HOSTNAME: !Ref MysqlHostnameKey
        MYSQL_PORT: !Ref MysqlPortKey
        MYSQL_USERNAME: !Ref MysqlUsernameKey
        MYSQL_PASSWORD: !Ref MysqlPasswordKey
```



MySQL 接続に必要なライブラリをインストールします

```
cd ~/samdir/hello-rdsproxy
pip install mysql-connector-python
```



`requirement.txt` に、次のライブラリを追記します。

```
mysql-connector-python
```



Python のソースコードはこんな感じです。

- 環境変数から接続先の MySQL の情報を取得
- sugi01.sample2 のデータを取得して、1行だけ出力

```python
import json
import os
import mysql.connector as mydb


def lambda_handler(event, context):

    hostname = os.environ['MYSQL_HOSTNAME']
    port = os.environ['MYSQL_PORT']
    username = os.environ['MYSQL_USERNAME']
    password = os.environ['MYSQL_PASSWORD']

    connection = mydb.connect(
        host=hostname,
        user=username,
        passwd=password,
        db='sugi01')

    cursor = connection.cursor()

    cursor.execute("SELECT * FROM sample2")
    selected = cursor.fetchone()
    print(selected)

    return {
        "statusCode": 200,
        "body": json.dumps({
            "message": selected,
            # "location": ip.text.replace("\n", "")
        }),
    }

```



Build を行います

```
sam build
```



ローカルで動作確認です。

```
sam local invoke
```



実行例です。正常にローカルの MySQL と接続できています。

```
> sam local invoke
Invoking app.lambda_handler (python3.9)
Skip pulling image and use local one: public.ecr.aws/sam/emulation-python3.9:rapid-1.40.0-x86_64.

Mounting /home/ec2-user/samdir/hello-rdsproxy/.aws-sam/build/HelloWorldFunction as /var/task:ro,delegated inside runtime container
START RequestId: 87a77126-7673-4228-bde8-073b187965db Version: $LATEST
(1, 'MySQL Data')
END RequestId: 87a77126-7673-4228-bde8-073b187965db
REPORT RequestId: 87a77126-7673-4228-bde8-073b187965db  Init Duration: 0.06 ms  Duration: 156.75 ms     Billed Duration: 157 ms Memory Size: 1024 MB    Max Memory Used: 1024 MB
{"statusCode": 200, "body": "{\"message\": [1, \"MySQL Data\"]}"}⏎
```



## Deploy

Lambda Function をデプロイしましょう

まず、SAM の `template.yaml` を編集して、Lambda を VPC 内部にデプロイする必要があります。次のように、Security Group と Subnet を指定しましょう。

```yaml
Resources:
  HelloWorldFunction:
    Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
    Properties:
      CodeUri: hello_world/
      Handler: app.lambda_handler
      Runtime: python3.9
      VpcConfig:
        SecurityGroupIds:
          - sg-00515cac8ae3971b6
        SubnetIds:
          # Subnet IDを書く
          - subnet-0d9a1ae8571fa7c30
          - subnet-0355f72e6534092bf
          - subnet-0c4b7c352a610baf4
```



SAM deploy を行っていきます。

```
sam deploy
```



# 動作確認

無事に Deploy が完了しました！次のようにアクセスすると、正常にデータが返ってきています。

```
> curl https://xxxxxx.execute-api.ap-northeast-1.amazonaws.com/Prod/hello/
{"message": [1, "MySQL Data"]}⏎      
```



負荷を掛けてみましょう

```
watch -n 0.1 curl https://xxxxxx.execute-api.ap-northeast-1.amazonaws.com/Prod/hello/
```



RDS Proxy のメトリックを見てみると、データベースコネクションが増えている様子が見えます。

![image-20220226213452182.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1002774/7b4afc80-36f9-70ba-ff8e-907ea511be67.png)



# 参考URL

RDS Proxy の開始方法
https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/AuroraUserGuide/rds-proxy-setup.html#rds-proxy-creating