はじめに
AWS Lambda が良く語られますが、予測不可能なリクエストが来た時に、多くのインスタンスを横に並べてリクエストを捌く構成があります。AWS Lambda の場合は、1リクエスト1インスタンスとなるので、需要が高まったときには必然的に多くのインスタンスが立ち上がる構成になります。この時、インスタンス が RDS のコネクションを取得している場合、多くのデータベースコネクションを取得することにより、データベース側の負荷が高まってしまう課題がありました。
こういった問題を解決するための選択肢の一つとして、RDS Proxy と呼ばれるデータベースのコネクションをプールしてくれる機能があります。Amazon RDS に備わっている機能となっており、複数のインスタンス間でデータベースコネクションをプールしてくれます。
RDS Proxy を利用するメリットは次の通りです。
- アプリケーションのスケーラビリティ
- データベースのフェールオーバーに伴う、アプリケーション側のフェールオーバーにかかわる時間の短縮
- AWS IAM 認証により、クレデンシャル情報を Secret Manager に持ち、セキュリティの向上に寄与
今回は、RDS Proxy を構成してみて、AWS Lambda から接続する方法を確かめてみましょう。
Aurora MySQL
Aurora MySQL 2.08 クラスターを既に稼働している環境を利用していきます。Writer Instance が 1 台、Reader Instance が 1 台存在しています。
Secret Manager で Secret の作成
RDS Proxy を作成する際に、Secret Manager でデータベースへクレデンシャル情報を定義する必要があります。Secret Manager のページを開いて、Store a new secret を押します
User name や Password を指定します
対象の Database を選択して Next を押します
適当に名前を入れて Next を押します
ローテーションは OFF にしておきます
Next
Store を押して作成します
作成されました
RDS Proxy の作成
RDS のページで、Proxies のメニューを選んだ後、Create proxy を押します。
いくつかのパラメータを指定します
- RDS Proxy の名前
- RDS Engine
- RDS Proxy でプーリングしているコネクションを close するまでの期間を指定。アプリケーションが何もクエリーを発行しない idle 時間が 30 分を過ぎると close するように指定している。
いくつかのパラメータを指定します
- RDS Proxy が連携するデータベースを指定
- RDS Proxy がプーリングするコネクションの割合を指定
- RDS Proxy としての Reader Endpoint を有効化
RDS Proxy では、データベースへアクセスするユーザーアカウントを Secret Manager で用意する必要があります。先ほど作成した Secret を選択します。
Security Group を指定したのちに、Create proxy を押します
RDS Proxy が Creating となりました
一定時間後、Available に変わりました
RDS Proxy の Endpoint を確認
RDS Proxy の詳細画面を開くと、RDS Proxy としての Endpoint を確認できます。
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 アドレスを指定
[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
に追記します。
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行だけ出力
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 を指定しましょう。
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 のメトリックを見てみると、データベースコネクションが増えている様子が見えます。
参考URL
RDS Proxy の開始方法
https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/AuroraUserGuide/rds-proxy-setup.html#rds-proxy-creating