VPC内のLambdaからRDS Proxy経由でRDSに接続する
VPC内にLambdaを配置し、RDSに接続する構成を考えます。
LambdaからRDSにアクセスする際は、直接接続するのではなく、RDS Proxyを介する形で接続することがベストプラクティスとなります。
最新のサーバーレスアーキテクチャに構築されたアプリケーションなどのアプリケーションの多くは、データベースサーバーへの接続を多数開くことができます。このとき、データベース接続の開閉が高頻度で実行されて、データベースメモリやコンピューティングリソースを消耗する可能性があります。Amazon RDS Proxy では、アプリケーションがデータベースと確立した接続をプールおよび共有でき、データベースの効率とアプリケーションのスケーラビリティが向上します。
Amazon RDS Proxy
今回はRDS Proxyを使用して、LambdaからRDSに接続する構成を確認します。
構築する環境
VPC内に3つのサブネットを作成します。
1つはLambda用です。通常はVPC外に作成するLambdaですが、今回のようにVPC内に作成することもできます。
残り2つはRDS用です。今回は1つDBインスタンスを作成します。
Lambda関数は直接DBインスタンスのエンドポイントに接続するのではなく、RDS Proxyエンドポイントに接続します。
RDS ProxyにターゲットグループとしてDBインスタンスを登録し、RDS Proxyを経由して、DBインスタンスに接続することができるようにします。
なお関数はPython3.8で作成します。
API Gatewayを作成し、バックエンドとしてLambda関数を設定します。
API Gatewayのエンドポイントにアクセスすることで、Lambda関数が実行されるように設定するということです。
今回作成する構成の挙動を簡単に説明します。
DBインスタンスに日時データを保存するテーブルを作成します。
API GatewayにHTTPリクエストが届くと、バックエンドのLambdaが呼び出され、現在日時を取得します。
取得した日時データをRDS Proxy経由でDBインスタンスに保存します。
加えてDBインスタンスに保存されている全データを取得し、HTTPレスポンスとして返します。
CloudFormationテンプレートファイル
上記の構成をCloudFormationで構築します。
以下のURLにCloudFormationテンプレートを配置しています。
https://github.com/awstut-an-r/awstut-fa/tree/main/037
テンプレートファイルのポイント解説
プロキシ本体とターゲットグループを作成する
本ページはRDS Proxyに関する内容を取り上げます。
RDSそのものに関しては、以下のページをご確認ください。
まずRDS Proxy本体を確認します。
Resources:
DBProxy:
Type: AWS::RDS::DBProxy
Properties:
Auth:
- IAMAuth: DISABLED
AuthScheme: SECRETS
SecretArn: !Ref Secret
DBProxyName: !Sub "${Prefix}-DBProxy"
EngineFamily: !Ref DBProxyEngineFamily
IdleClientTimeout: 120
RequireTLS: false
RoleArn: !GetAtt DBProxyRole.Arn
VpcSecurityGroupIds:
- !Ref DBProxySecurityGroup
VpcSubnetIds:
- !Ref DBSubnet1
- !Ref DBSubnet2
Code language: YAML (yaml)
AuthプロパティでRDS(DBインスタンス)に接続する際の認証方式を設定します。
今回はSecrets Managerのシークレットを使用して認証を行うように設定します。
EngineFamilyプロパティは接続先のDBインスタンスのタイプを指定するものです。
今回はMySQLタイプのDBインスタンスですので、「MYSQL」を設定します。
VpcSecurityGroupIdsプロパティでRDS Proxyに適用するセキュリティグループを設定します。
今回はMySQLタイプのDBインスタンスに接続しますので、Lambdaサブネットからの3306/tcpのインバウンド通信を許可するセキュリティグループを適用します。
VpcSubnetIdsプロパティで、RDS Proxyを関連づけるサブネットを指定します。
対象となるサブネットはDBインスタンスが設置されているものとなります。
RDS Proxyの接続先となるDBインスタンスの指定は、ターゲットグループで定義します。
Resources:
DBProxyTargetGroup:
Type: AWS::RDS::DBProxyTargetGroup
Properties:
DBProxyName: !Ref DBProxy
DBInstanceIdentifiers:
- !Ref DBInstance
TargetGroupName: default
ConnectionPoolConfigurationInfo:
MaxConnectionsPercent: 100
MaxIdleConnectionsPercent: 50
ConnectionBorrowTimeout: 120
Code language: YAML (yaml)
DBInstanceIdentifiersプロパティでDBインスタンスを指定します。
Secrets Managerのシークレットを確認します。
Resources:
Secret:
Type: AWS::SecretsManager::Secret
Properties:
Name: !Sub "${Prefix}-Secret"
SecretString: !Sub '{"username":"${DBMasterUsername}","password":"${DBMasterUserPassword}"}'
Code language: YAML (yaml)
DBインスタンスに設定したユーザー名・パスワードをJSON形式で指定し、シークレットを作成します。
RDS Proxyに関連づけるIAMロールを作成します。
Resources:
DBProxyRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action: sts:AssumeRole
Principal:
Service:
- rds.amazonaws.com
Policies:
- PolicyName: !Sub "${Prefix}-DBProxyPolicy"
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action: secretsmanager:GetSecretValue
Resource: !Ref Secret
- Effect: Allow
Action: kms:Decrypt
Resource: '*' # use default key.
Code language: YAML (yaml)
先ほど定義したシークレットから暗号化された値を取得する権限と、そのために使用するKMSキーの復号化の権限を付与します。
以下のページを参照して設定しました。
RDS Proxyに接続するLambda関数
Lambda関数を確認します。
Resources:
Function:
Type: AWS::Lambda::Function
Properties:
Environment:
Variables:
DB_ENDPOINT_PORT: !Ref MySQLPort
DB_NAME: !Ref DBName
DB_PASSWORD: !Ref DBMasterUserPassword
DB_PROXY_ENDPOINT_ADDRESS: !Ref DBProxyEndpointAddress
DB_TABLENAME: !Ref DBTableName
DB_USER: !Ref DBMasterUsername
REGION: !Ref AWS::Region
Code:
S3Bucket: !Ref CodeS3Bucket
S3Key: !Ref CodeS3Key
FunctionName: !Sub "${Prefix}-function"
Handler: index.lambda_handler
Runtime: python3.8
Role: !GetAtt FunctionRole.Arn
Timeout: 10
VpcConfig:
SecurityGroupIds:
- !Ref FunctionSecurityGroup
SubnetIds:
- !Ref FunctionSubnet
Code language: YAML (yaml)
今回はS3バケットにアップロードしたデプロイパッケージ(Zipファイル)からLambda関数を作成します。
Lambda関数の基本的な事項は以下のページをご確認ください。
ポイントは2つあります。
1つ目は環境変数の設定です。
EnvironmentおよびVariablesプロパティで関数内で使用可能な変数を定義できます。
今回はRDS ProxyおよびDBインスタンスに接続するためのRDS Proxyエンドポイント、ユーザー名やパスワード、テーブル名等を定義します。
2つ目はVPCに関する設定です。
今回はVPC内にLambda関数を設定しますので、設置するサブネットと適用するセキュリティグループを指定します。
セキュリティグループの内容ですが、HTTPS(443/tcp)のインバウンド通信を許可する内容です。
関数で実行するスクリプトを確認します。
import boto3
import datetime
import json
import mysql.connector
import os
db_name = os.environ['DB_NAME']
db_password = os.environ['DB_PASSWORD']
db_proxy_endpoint_address = os.environ['DB_PROXY_ENDPOINT_ADDRESS']
db_tablename = os.environ['DB_TABLENAME']
db_user = os.environ['DB_USER']
region = os.environ['REGION']
def lambda_handler(event, context):
conn = mysql.connector.connect(
host=db_proxy_endpoint_address,
port=db_endpoint_port,
user=db_user,
password=db_password,
database=db_name
)
cur = conn.cursor()
table_sql = 'create table if not exists {db}.{tbl} (dt datetime);'.format(
db=db_name,
tbl=db_tablename
)
cur.execute(table_sql)
now = datetime.datetime.now()
now_str = now.strftime('%Y-%m-%d %H:%M:%S')
write_sql = 'insert into {tbl} values ("{now}");'.format(
tbl=db_tablename,
now=now_str
)
cur.execute(write_sql)
cur.close()
conn.commit()
cur = conn.cursor()
read_sql = 'select * from {tbl};'.format(tbl=db_tablename)
cur.execute(read_sql)
content = [record[0].strftime('%Y-%m-%d %H:%M:%S') for record in cur]
cur.close()
conn.close()
return {
'statusCode': 200,
'body': json.dumps(content, indent=2)
}
Code language: Python (python)
本ページはRDS Proxyを中心に取り上げますので、Lambda関数そのものの詳細は割愛します。
RDS Proxyに接続するためのポイントを確認します。
os.environから環境変数を取得します。
これらは先述のLambda関数の定義時に設定した変数です。
特に重要な変数はRDS Proxyのエンドポイントです。
DBインスタンスに作成されるエンドポイントではなく、RDS Proxyエンドポイントに向かって接続する形になります。
PythonからMySQLに接続するために、MySQL公式のドライバであるmysql-connector-pythonを使用します。
デプロイするZipファイルを作成する際に、以下のコマンドでローカルにパッケージをダウンロードし、まとめてデプロイします。
pip3 install mysql-connector-python -t .
zip -r deploy.zip .
Code language: Bash (bash)
コードの内容を簡単にご紹介します。
- 環境変数を参照して、RDS Proxy経由でDBインスタンスに接続する。
- 初回のみ、現在日時を保存するためのテーブルを作成する。
- 現在日時を取得し、テーブルに保存する。
- テーブルに保存されている日時データを取得し、ユーザに返す。
API GatewayのバックエンドにLambdaを設定する
API Gatewayを作成するためは、以下の5つのリソースを作成する必要があります。
- API Gateway本体
- ステージ
- 統合
- ルート
- IAMロール/パーミッション
詳細は以下のページをご確認ください。
環境構築
CloudFormationを使用して、本環境を構築し、実際の挙動を確認します。
CloudFormationスタックを作成し、スタック内のリソースを確認する
CloudFormationスタックを作成します。
スタックの作成および各スタックの確認方法については、以下のページをご確認ください。
各スタックのリソースを確認した結果、今回作成された主要リソースの情報は以下の通りです。
- DBインスタンス:fa-037-dbinstance
- RDS Proxy:fa-037-dbproxy
- API Gatewayのエンドポイント:https://oeebpxb1r2.execute-api.ap-northeast-1.amazonaws.com/
AWS Management Consoleからも、リソースの作成状況を確認します。
RDS Proxyを確認します。
RDS Proxyが正常に作成されています。
エンドポイントが作成されています。
このエンドポイントに対してLambda関数はアクセスすることになります。
RDS ProxyのターゲットグループにDBインスタンスが関連づいています。
このRDS Proxyに接続すると、関連づいているDBインスタンスに接続することになります。
認証方式はSecrets Managerを使用する方法で設定されています。
動作確認
準備が整いましたので、API Gatewayにアクセスします。
正常にレスポンスが返ってきました。
アクセスする度に日時データの表示が増えていきます。
このようにLambdaからDBインスタンスにアクセスする際に、RSD Proxyを介して接続することができます。
まとめ
LambdaからRDS Proxyを経由してDBインスタンスに接続する方法を確認しました。