AWS

VPC内のLambdaからRDS Proxy経由でRDSに接続する

スポンサーリンク
RDS Proxy経由でVPC内のLambdaからRDSに接続する AWS
スポンサーリンク
スポンサーリンク

VPC内のLambdaからRDS Proxy経由でRDSに接続する

VPC内にLambdaを配置し、RDSに接続する構成を考えます。
LambdaからRDSにアクセスする際は、直接接続するのではなく、RDS Proxyを介する形で接続することがベストプラクティスとなります。

最新のサーバーレスアーキテクチャに構築されたアプリケーションなどのアプリケーションの多くは、データベースサーバーへの接続を多数開くことができます。このとき、データベース接続の開閉が高頻度で実行されて、データベースメモリやコンピューティングリソースを消耗する可能性があります。Amazon RDS Proxy では、アプリケーションがデータベースと確立した接続をプールおよび共有でき、データベースの効率とアプリケーションのスケーラビリティが向上します。

Amazon RDS Proxy

今回はRDS Proxyを使用して、LambdaからRDSに接続する構成を確認します。

構築する環境

Amazon.co.jp: AWS認定資格試験テキスト AWS認定ソリューションアーキテクト - アソシエイト 改訂第2版 : NRIネットコム株式会社, 佐々木 拓郎, 林 晋一郎, 金澤 圭: 本
Amazon.co.jp: AWS認定資格試験テキスト AWS認定ソリューションアーキテクト - アソシエイト 改訂第2版 : NRIネットコム株式会社, 佐々木 拓郎, 林 晋一郎, 金澤 圭: 本
Diagram of connect from Lambda in VPC via RDS Proxy

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テンプレートを配置しています。

awstut-fa/037 at main · awstut-an-r/awstut-fa
Contribute to awstut-an-r/awstut-fa development by creating an account on GitHub.

テンプレートファイルのポイント解説

プロキシ本体とターゲットグループを作成する

本ページは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 のスタート方法 - Amazon Relational Database Service
RDS Proxy を作成し、それを使用してデータベースに接続する方法について説明します。

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)

コードの内容を簡単にご紹介します。

  1. 環境変数を参照して、RDS Proxy経由でDBインスタンスに接続する。
  2. 初回のみ、現在日時を保存するためのテーブルを作成する。
  3. 現在日時を取得し、テーブルに保存する。
  4. テーブルに保存されている日時データを取得し、ユーザに返す。

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 Detail 1
RDS Proxy Detail 2

RDS Proxyが正常に作成されています。

エンドポイントが作成されています。
このエンドポイントに対してLambda関数はアクセスすることになります。

RDS ProxyのターゲットグループにDBインスタンスが関連づいています。
このRDS Proxyに接続すると、関連づいているDBインスタンスに接続することになります。

認証方式はSecrets Managerを使用する方法で設定されています。

動作確認

準備が整いましたので、API Gatewayにアクセスします。

RDS Proxy test 1
RDS Proxy test 2

正常にレスポンスが返ってきました。
アクセスする度に日時データの表示が増えていきます。
このようにLambdaからDBインスタンスにアクセスする際に、RSD Proxyを介して接続することができます。

まとめ

LambdaからRDS Proxyを経由してDBインスタンスに接続する方法を確認しました。

タイトルとURLをコピーしました