今から始めるLambda⑦「LambdaからRDS(Aurora)に接続する 後編」
はじめに
前回の記事ではVPC内にAuroraのDBインスタンスを立ち上げるところまでを行いました。
今回は同一VPC内にあるLambdaからDBに接続をしたいと思います。
そのために使用するRDS Proxyのセットアップなども含めて紹介していきます。
前提としてRDSのインスタンスやVPCの作成は済んでいるものとします。
RDS Proxyとは
Amazon RDS Proxy は、Amazon Relational Database Service (RDS) 向けの高可用性フルマネージド型データベースプロキシで、アプリケーションのスケーラビリティやデータベース障害に対する回復力と安全性を高めます。
文字通りAWS RDSのプロキシです。
なぜ今回これが必要かというと、LambdaからRDSへの接続を考えた際に、Lambdaはリクエスト毎に起動し、都度コネクションを貼ってしまうためです。
いわゆる同時接続数問題というやつで、RDS Proxyが間に入って接続プールを管理してくれることで、これらが解消されます。
RDS Proxyが登場する以前はLambdaとRDSの組み合わせはアンチパターンに近い扱いをされていたようです(以下リンク参照)。
RDS Proxyの設定
さっそくRDS Proxyの設定を行っていきます。
シークレットの作成
RDS ProxyではDBの認証情報をシークレットで管理します。
そのためAWSのSecrets Managerを使用します。
前回作成したRDSのインスタンスの認証情報(デフォではpostgres)をシークレットに含めます。
名前はrds_proxy_secretとします。
内容のうちKVS_RESOURCEの値についてはAWSのコンソールから「Key Management Service (KMS) > AWS マネージド型キー > aws/secretsmanager」の値を使います。
aws secretsmanager create-secret \
--name "rds_proxy_secret" \
--description "rds_proxy_secret" \
--region us-east-2 \
--secret-string '{"username":"postgres","password":"postgres"}'
--kms-key-id 【KVS_RESOURCE】
レスポンスとしてJSONが返ってくれば成功です。
ロールの作成
今回使用するロールを作成します。
RDSアクセスするため、以下のようなJSONを用意しておきます。
名前はrds-proxy-policy.jsonとします。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "rds.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
上記を用いてロールを作成します。
ロール名はrds_proxy_roleとします。
aws iam create-role --role-name rds_proxy_role \
--assume-role-policy-document file://rds-proxy-policy.json
シークレットを利用するためのポリシー追加
作成したロールにさらにポリシーを追加します。
このポリシーは先ほど使用したシークレットの値を利用するためのものです。
以下のようなsecret-reader-policy.jsonを作成します。
KVS_RESOURCEの値についてはcreate-secretで使用したのと同じ値です。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": "secretsmanager:GetSecretValue",
"Resource": [
"【SECRET_ARN】"
]
},
{
"Sid": "VisualEditor1",
"Effect": "Allow",
"Action": "kms:Decrypt",
"Resource": "arn:aws:kms:【REGION】:【USER_ID】:key/【KVS_RESOURCE】",
"Condition": {
"StringEquals": {
"kms:ViaService": "secretsmanager.【REGION】.amazonaws.com"
}
}
}
]
}
このJSONを使ってポリシーをロールに追加します。
aws iam put-role-policy --role-name rds_proxy_role \
--policy-name secret-reader-policy --policy-document file://secret-reader-policy.json
CMKを使う
シークレットを暗号化、福号して使用するためCMKを使います。
CMKはAWS Key Management Service(以下KMS)を用いて生成します。
以下のようなrds-key.jsonを用意します。
{
"Id":"rds-kms",
"Version":"2012-10-17",
"Statement":
[
{
"Sid":"Enable IAM User Permissions",
"Effect":"Allow",
"Principal":{"AWS":"arn:aws:iam::【USER_ID】:root"},
"Action":"kms:*","Resource":"*"
},
{
"Sid":"Allow access for Key Administrators",
"Effect":"Allow",
"Principal":
{
"AWS":
["arn:aws:iam::【USER_ID】:user/【USER_NAME】","arn:aws:iam::【USER_ID】:role/rds_proxy_role"]
},
"Action":
[
"kms:Create*",
"kms:Describe*",
"kms:Enable*",
"kms:List*",
"kms:Put*",
"kms:Update*",
"kms:Revoke*",
"kms:Disable*",
"kms:Get*",
"kms:Delete*",
"kms:TagResource",
"kms:UntagResource",
"kms:ScheduleKeyDeletion",
"kms:CancelKeyDeletion"
],
"Resource":"*"
},
{
"Sid":"Allow use of the key",
"Effect":"Allow",
"Principal":{"AWS":"arn:aws:iam::【USER_ID】:role/rds_proxy_role"},
"Action":["kms:Decrypt","kms:DescribeKey"],
"Resource":"*"
}
]
}
以下コマンドでキーを作成します。
aws kms create-key --description "rds-key" --policy file://rds-key.json
プロキシの作成
いよいよRDS Proxyの作成を行います。
作成したシークレットやロールのARN、VPCのサブネットやセキュリティグループなどを指定しましょう。
プロキシの名前はtest-rds_proxyとします。
aws rds create-db-proxy \
--db-proxy-name test-rds-proxy \
--engine-family POSTGRESQL \
--auth '[{"Description": "test_rds_proxy auth", "AuthScheme": "SECRETS", "SecretArn": "【SECRET_ARN】", "IAMAuth": "DISABLED"}]' \
--role-arn 【ROLE_ARN】 \
--vpc-subnet-ids '["【SUBNET_1】","【SUBNET_2】"]' \
--vpc-security-group-ids 【SECURITY_GROUP_ID】
成功するとJSONが返ります。
プロキシとDBの紐付け
作成したプロキシとDBの紐付けを行います。
ここではDBはtest-aurora-postgres-clisterというクラスタ名であるとします。
aws rds register-db-proxy-targets \
--db-proxy-name test-rds-proxy \
--db-cluster-identifiers test-aurora-postgresql-cluster
こちらもJSONが返れば成功です。
状態のチェック
RDS ProxyとDBの紐付けには時間がかかるため、以下コマンドで状態をチェックします。
aws rds describe-db-proxy-targets --db-proxy-name test-rds-proxy
レスポンスの中のTargetHealthの値を参照しましょう。
しばらくはPENDING_PROXY_CAPACITYとなっているかと思うので待ちましょう。
AUTH_FAILUREと出た場合はロールの設定に問題がある場合が多いです。
他にもいくつかエラー時の表記があるので、下記リンクから対処法を確認しましょう。
最終的に値がAVAILABLEとなればOKです。
Lambdaの用意
これでRDS Proxyの使用準備ができました。
先ほど状態チェックで使用したコマンドのレスポンスのうちEndpointにあたる値が、実際のRDS Proxyのエンドポイントです。
前回の記事でのRDSインスタンスとの導通確認ではローカルからpsqlを使って接続できることを確認しました。
しかしながらRDS ProxyはVPC内からしかアクセスできない(以下参照)ので、今回はVPC内にあるLambdaからアクセスするものとします。
RDSプロキシは、データベースと同じ仮想プライベートクラウド(VPC)に存在する必要があります。データベースはアクセスできますが、プロキシはパブリックにアクセスできません。
Lambdaの内容
以下のようなLambdaをRDS ProxyがあるVPCの中に配置します。
環境変数に各種設定がされており、DB_HOSTには前述のEndpointの値が入ります。
また、layers等を用いてpgを利用できる状態にしておきます。
今回クエリを投げるTASKテーブルは、前回記事で既に作成済みのものとします。
const {Client} = require('pg');
exports.handler = async (event, context) => {
try{
// client設定
const client = new Client({
host: process.env.DB_HOST,
port: process.env.PORT,
user: process.env.USER,
password: process.env.PASSWORD,
});
// 接続
await client.connect(function(err) {
if (err) {
console.error('connection error', err.stack)
}
});
// クエリ実行
const res = await client.query('SELECT id, name FROM TASK');
console.log(res);
return {
statusCode: 200,
body: JSON.stringify(res.rows),
}
}catch (e) {
console.log("Error...");
console.log(e);
throw e;
}finally{
}
};
このLambda関数を実行することで、以下のレスポンスが返りました。
{
"statusCode": 200,
"body": "[{\"id\":0,\"name\":\"task1\"},{\"id\":1,\"name\":\"task2\"}]"
}
これでLambda関数からRDS Proxy経由で接続できていることがわかります。
まとめ
今回はRDS Proxyを経由してLambdaからRDSインスタンスにクエリを投げるところまでを実装しました。
簡略化した実装ですが、今回の内容を用いてAPI Gatewayと組み合わせることでDBから動的なレスポンスを返すREST APIは作れるようになります。
今回の内容が役立ちましたら幸いです。
参考
Discussion