こんにちは。いっちー(@tetestkake_blog)です!
AWSを使っていると、LambdaからRDBへのアクセスで難儀することがあります。
それを解決するRDS ProxyがGA(一般提供)になりましたね!(ずいぶん前ですが)
この記事ではRDS Proxyを使用してLambdaからAuroraに接続する方法を解説します。
本文中やgithubにサンプルプログラムも載せておいたので、実装の際の参考にしてみてください。
RDS Proxyを使用することによりLambdaからRDSへのアクセスが改善されます!
LambdaからRDSへのアクセスはアンチパターン
LambdaからRDSへのアクセスはアンチパターンと言われています。(調べるまで知りませんでした)
その理由は、Lambdaはリクエストごとに起動し、その都度RDSに対してコネクションを張ろうとするため、
コネクション上限を超えたLambdaからのアクセスはエラーとなってしまうためです。
Lambda起動の頻度が高いほどコネクションが多くなってしまうのですね。。。
RDS Proxyはコネクションをいい感じに調整してくれる
そこで登場するのがRDS Proxyです。公式ドキュメントには以下のように説明があります。
Amazon RDS Proxy を使用すると、データベース接続のプーリングと共有をアプリケーションに許可してスケーラビリティを向上させることができます。RDS Proxy は、アプリケーション接続を維持しながら、スタンバイ DB インスタンスに自動的に接続することで、データベース障害に対するアプリケーションの耐障害性を高めます。RDS Proxy を使用すると、データベースに AWS Identity and Access Management (IAM) 認証を適用し、AWS Secrets Manager に認証情報を安全に保存することもできます。
https://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/UserGuide/rds-proxy.html
ざっくりまとめると、RDS Proxyはコネクションをいい感じに調整してくれるサービスです。
今回は以下の手順でLambda + RDS Proxy + Auroraの接続を確認します。
- 検証用Auroraを立てる
- Auroraに接続し、データをSELECTするLambda関数を作成
- RDS Proxyを作成
- RDS ProxyをLambdaに追加し、同じくSELECTできるか確認する
検証用Auroraを立てる
まずは検証用のAuroraを立てます。検証用なのでスペックは低めです。
Amazon RDS > データベースの作成
からデータベースを作成します。
項目は以下を設定しました。
作成を押してしばらく待つとRDSのステータスが「作成中」-> 「利用可能」に変わったので、作成成功です。
DBにはサンプルテーブルを作成してサンプルの値を入れておきます。(後からLambdaで参照する際に使用)
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` (`id`, `name`)
VALUES
(1,'orange'),
(2,'apple'),
(3,'banana');
Auroraに接続し、データをSELECTするLambda関数を作成
AWS Lambda > 関数の作成
からLambda関数を作成します。
コードは特定のテーブルから内容をSELECTするシンプルなPythonスクリプトを作成しました。
lambda_function.py
import sys
import json
import pymysql
# rds settings
# お試しなので直書き
# secrets managerなどから値を取得する方がベター
# インスタンス直
host = "<インスタンス直のエンドポイント>"
user = "<ユーザー名>"
passwd = "<パスワード>"
db_name = "<DB名>"
def lambda_handler(event, context):
print("start!!")
try:
conn = pymysql.connect(host=host, user=user, passwd=passwd, db=db_name, connect_timeout=5)
except Exception as e:
print("error")
print(e)
with conn.cursor() as cur:
cur.execute("SELECT * FROM fruits")
for row in cur:
print(row)
# TODO implement
return {
'statusCode': 200,
'body': json.dumps('Hello from Lambda!')
}
pymysqlはLambdaエディタかimportできないので、zip化してアップロード
DB接続用に使用しているpymysqlは、Lambdaのエディタからimport文を書いただけでは使えず、
以下の手順を踏む必要があります。
- pymysqlをローカルにインストール
- zip化してLambdaにアップロード
pymysqlをローカルにインストールするには以下のコマンドを使用します。
# カレントにインストール
pip3 install pymysql -t .
zipにまとめるには以下のコマンドを使用します。この際に作成したlambda_function.pyも含めます。(同じディレクトリに配置)
# zipにまとめる
zip -r app.zip /path/to/workdir/*
※仮にapp.zipという名前をつけてあります
こうしてできたzipファイルをコンソールからアップロードします。
Lambdaの該当関数 > コードタブ > アップロード元 > .zipファイル
からアップロードします。
「Test」ボタンから関数を実行し、以下の実行結果が表示されたらOKです。
start!!
(1, 'orange')
(2, 'apple')
(3, 'banana')
これでLambda -> RDSの接続は完成です。
この後は、間にRDS Proxyを挟んでLambda -> RDS Proxy -> RDSとします。
RDS Proxyを作成
さて、本題であるRDS Proxyを作成していきます。
手順は以下です。
- Secrets ManagerにRDSの接続情報を保存
- RDS Proxyがシークレットを読み取ることができるIAMロールを作成
- プロキシをLambda関数にアタッチ
Secrets ManagerにRDSの接続情報を設定
RDS ProxyがRDSにアクセスするために、Secrets ManagerにRDSの接続情報を設定します。
AWS Secrets Manager > シークレット > 新しいシークレットを保存する
から作成します。
ARNの値は後から使用するので控えておきます。
RDS Proxyがシークレットを読み取ることができるIAMロールを作成
RDS Proxyがシークレットを読み取ることができるIAMロールを作成していきます。
Identity and Access Management (IAM) > ポリシーの作成
から、ロールに紐付けるポリシーを作成します。
以下サンプルです。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"secretsmanager:GetResourcePolicy",
"secretsmanager:GetSecretValue",
"secretsmanager:DescribeSecret",
"secretsmanager:ListSecretVersionIds"
],
"Resource": [
"<SecretsManagerのARN>"
]
}
]
}
次に、
IAM > ロールの作成
からロールを作成します。
先ほど作成したポリシーをロールにアタッチします。また、以下の信頼関係を定義します。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "rds.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
信頼関係を定義しないと、RDS Proxyの設定画面にて作成したIAMが候補に上がってこないので注意が必要です!(私は小一時間ハマりました、、、)
RDS ProxyをLambda関数にアタッチ
RDS ProxyをLambda関数にアタッチします。いよいよですね。
lambda > 設定 > データベースプロキシ > データベースプロキシの追加
からRDS Proxyを追加します。
作成後にはプロキシ経由のエンドポイントが払い出されるのでメモしておきます。
構築物はここまででひとまず完了です!
RDS Proxyを経由したリクエストの確認
それでLambdaを実行してDBにアクセスできるか確認します。
その前にRDS Proxy経由のエンドポイントが払い出されたので、Lambdaのコードを変更します。
# インスタンス直
# host = "<インスタンス直のエンドポイント>"
# proxy経由
host = "<proxyのエンドポイント>"
これでLambdaを実行してみると、、、
(<class 'pymysql.err.OperationalError'>, OperationalError(2013, 'Lost connection to MySQL server during query'), <traceback object at 0x7ff4d20dba40>)
[ERROR] UnboundLocalError: local variable 'conn' referenced before assignment
上記のエラーが出ました。
こちらのエラーは何故かDBを再起動(停止→起動)したら治りました。
原因はわかっていません。。。(自分だけ!?)
繋がるようになったので、Lambdaを実行してみます。以下の結果が表示されたらOKです。
start!!
(1, 'orange')
(2, 'apple')
(3, 'banana')
PDS Proxy経由でRDSにアクセスできましたね!お疲れ様でした!
結局RDBを再起動する前の接続エラーの原因は不明でした。。。
まとめ
この記事ではRDS Proxyを使用してLambdaからAuroraに接続する方法に関して紹介しました。
RDS Proxyを使用するとコネクション周りを自動でいい感じに調整してくれるので、
LambdaからRDBに接続する際の参考にしてみてください!
参考資料
- AWS LambdaでAmazon RDS Proxyを使用する
- Lambda から RDS にアクセスする方法 (python)
- [Python] MySQLに接続してデータ操作を行う
- [AWS]RDS Proxyを使用してにLambdaからRDSへアクセスするお話
- [動画公開] RDSプロキシは未来を変えるか #devio2020
コメント